/*
* Kendo UI Web v2012.1.322 (http://kendoui.com)
* Copyright 2012 Telerik AD. All rights reserved.
*
* Kendo UI Web commercial licenses may be obtained at http://kendoui.com/web-license
* If you do not own a commercial license, this file shall be governed by the
* GNU General Public License (GPL) version 3.
* For GPL requirements, please review: http://www.gnu.org/copyleft/gpl.html
*/
;(function($, undefined) {
    /**
     * @name kendo
     * @namespace This object contains all code introduced by the Kendo project, plus helper functions that are used across all widgets.
     */
    var kendo = window.kendo = window.kendo || {},
        extend = $.extend,
        each = $.each,
        proxy = $.proxy,
        isArray = $.isArray,
        noop = $.noop,
        isFunction = $.isFunction,
        math = Math,
        Template,
        JSON = window.JSON || {},
        support = {},
        boxShadowRegExp = /(\d+?)px\s*(\d+?)px\s*(\d+?)px\s*(\d+?)?/i,
        FUNCTION = "function",
        STRING = "string",
        NUMBER = "number",
        OBJECT = "object",
        NULL = "null",
        BOOLEAN = "boolean",
        UNDEFINED = "undefined",
        getterCache = {},
        setterCache = {},
        globalize = window.Globalize;

    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 (typeof proto[member] === OBJECT && !(proto[member] instanceof Array) && proto[member] !== null) {
                // 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;
    };

    var Observable = Class.extend(/** @lends kendo.Observable.prototype */{
        /**
         * Creates an observable instance.
         * @constructs
         * @class Represents a class that can trigger events, along with methods that subscribe handlers to these events.
         */
        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;

            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);
                        }
                    }
                    events = that._events[eventName] = that._events[eventName] || [];
                    events.push(handler);
                }
            }

            return that;
        },

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

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

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

                e.sender = that;

                e.preventDefault = function () {
                    isDefaultPrevented = true;
                }

                e.isDefaultPrevented = function() {
                    return isDefaultPrevented;
                }

                //Do not cache the length of the events array as removing events attached through one will fail
                for (idx = 0; idx < events.length; idx++) {
                    events[idx].call(that, e);
                }
            }

            return isDefaultPrevented;
        },

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

            if (events) {
                if (handler) {
                    for (idx = 0, length = events.length; idx < length; idx++) {
                        if (events[idx] === handler) {
                            events.splice(idx, 1);
                        }
                    }
                } else {
                    that._events[eventName] = [];
                }
            }

            return that;
        }
    });

    /**
     * @name kendo.Template.Description
     *
     * @section
     * <p>
     *  Templates offer way of creating HTML chunks. Options such as HTML encoding and compilation for optimal
     *  performance are available.
     * </p>
     *
     * @exampleTitle Basic template
     * @example
     * var inlineTemplate = kendo.template("Hello, #= firstName # #= lastName #");
     * var inlineData = { firstName: "John", lastName: "Doe" };
     * $("#inline").html(inlineTemplate(inlineData));
     *
     * @exampleTitle Output:
     * @example
     * Hello, John Doe!
     *
     * @exampleTitle Encoding HTML
     * @example
     * var encodingTemplate = kendo.template("HTML tags are encoded as follows: ${ html }");
     * var encodingData = { html: "<strong>lorem ipsum</strong>" };
     * $("#encoding").html(encodingTemplate(encodingData));
     *
     * @exampleTitle Output:
     * @example
     * HTML tags are encoded as follows: <strong>lorem ipsum</strong>
     *
     */

     function compilePart(part, stringPart) {
         if (stringPart) {
             return "'" +
                 part.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;

    /**
     * @name kendo.Template
     * @namespace
     */
    Template = /** @lends kendo.Template */ {
        paramName: "data", // name of the parameter of the generated template
        useWithBlock: true, // whether to wrap the template in a with() block
        /**
         * Renders a template for each item of the data.
         * @ignore
         * @name kendo.Template.render
         * @static
         * @function
         * @param {String} [template] The template that will be rendered
         * @param {Array} [data] Data items
         * @returns {String} The rendered template
         */
        render: function(template, data) {
            var idx,
                length,
                html = "";

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

            return html;
        },
        /**
         * Compiles a template to a function that builds HTML. Useful when a template will be used several times.
         * @ignore
         * @name kendo.Template.compile
         * @static
         * @function
         * @param {String} [template] The template that will be compiled
         * @param {Object} [options] Compilation options
         * @returns {Function} The compiled template
         */
        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;",
                parts,
                part,
                idx;

            if (isFunction(template)) {
                if (template.length === 2) {
                    //looks like jQuery.template
                    return function(d) {
                        return template($, { data: d }).join("");
                    }
                }
                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 {
                return new Function(argumentName, functionBody);
            } catch(e) {
                throw new Error(kendo.format("Invalid template:'{0}' Generated code:'{1}'", template, functionBody));
            }
        }
    };

    //JSON stringify
(function() {
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        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,
        formatters,
        toString = {}.toString,
        hasOwnProperty = {}.hasOwnProperty;

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

        /** @ignore */
        Date.prototype.toJSON = function (key) {
            var that = this;

            return isFinite(that.valueOf()) ?
                that.getUTCFullYear()     + "-" +
                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 = /** @ignore */ Boolean.prototype.toJSON = function (key) {
            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 (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 formatRegExp = /{(\d+)(:[^\}]+)?}/g,
        dateFormatRegExp = /dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|HH|H|hh|h|mm|m|fff|ff|f|tt|ss|s|"[^"]*"|'[^']*'/g,
        standardFormatRegExp =  /^(n|c|p|e)(\d*)$/i,
        literalRegExp = /["'].*?["']/g,
        EMPTY = "",
        POINT = ".",
        COMMA = ",",
        SHARP = "#",
        ZERO = "0",
        PLACEHOLDER = "??",
        EN = "en-US";

    //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
            }
        }
    }};

    /**
     * @name kendo.ui.Globalization
     * @namespace
     */
     /**
     * @name kendo.ui.Globalization.Description
     *
     * @section Globalization is the process of designing and developing an
     * application that works in multiple cultures. The culture defines specific information
     * for the number formats, week and month names, date and time formats and etc.
     *
     * @section Kendo exposes <strong><em>culture(cultureName)</em></strong> method which allows to select the culture
     * script coresponding to the "culture name". kendo.culture() method uses the passed culture name
     * to select culture from the culture scripts that you have included and then sets the current culture.
     * If there is no such culture, the default one is used.
     *
     * <h3>Define current culture settings</h3>
     *
     * @exampleTitle Include culture scripts and select culture
     * @example
     *
     * <script src="jquery.js" ></script>
     * <script src="kendo.all.min.js"></script>
     * <script src="kendo.culture.en-GB.js"></script>
     * <script type="text/javascript">
     *    //set current culture to the "en-GB" culture script.
     *    kendo.culture("en-GB");
     * </script>
     *
     * @exampleTitle Get current culture
     * @example
     * var cultureInfo = kendo.culture();
     *
     * @section
     * <h3>Format number or date object</h3>
     *
     * Kendo exposes methods which can format number or date object using specific format string and the current specified culture:
     * @section
     * <h4><code>kendo.toString(object, format)</code> - returns a string representation of the current object using specific format.</h4>
     * @exampleTitle Formats number and date objects
     * @example
     * //format number using standard number format
     * kendo.toString(10.12, "n"); //10.12
     * kendo.toString(10.12, "n0"); //10
     * kendo.toString(10.12, "n5"); //10.12000
     * kendo.toString(10.12, "c"); //$10.12
     * kendo.toString(0.12, "p"); //12.00 %
     * //format number using custom number format
     * kendo.toString(19.12, "00##"); //0019
     * //format date
     * kendo.toString(new Date(2010, 9, 5), "yyyy/MM/dd" ); // "2010/10/05"
     * kendo.toString(new Date(2010, 9, 5), "dddd MMMM d, yyyy" ); // "Tuesday October 5, 2010"
     * kendo.toString(new Date(2010, 10, 10, 22, 12), "hh:mm tt" ); // "10:12 PM"
     *
     * @section
     * <h4><code>kendo.format</code> - replaces each format item in a specified string with the text equivalent of a corresponding object's value.</h4>
     *  @exampleTitle String format
     *  @example
     *  kendo.format("{0} - {1}", 12, 24); //12 - 24
     *  kendo.format("{0:c} - {1:c}", 12, 24); //$12.00 - $24.00
     *
     * @section
     * <h3>Parsing a string</h3>
     *
     * Kendo exposes methods which converts the specified string to date or number object:
     * <ol>
     *    <li>
     *       <code>kendo.parseInt(string, [culture])</code> - converts a string to a whole number using the specified culture (current culture by default).
     *        @exampleTitle Parse string to integer
     *        @example
     *
     *        //assumes that current culture defines decimal separator as "."
     *        kendo.parseInt("12.22"); //12
     *
     *        //assumes that current culture defines decimal separator as ",", group separator as "." and currency symbol as "€"
     *        kendo.parseInt("1.212,22 €"); //1212
     *    </li>
     *    <li>
     *       <code>kendo.parseFloat(string, [culture])</code> - converts a string to a number with floating point using the specified culture (current culture by default).
     *        @exampleTitle Parse string to float
     *        @example
     *
     *        //assumes that current culture defines decimal separator as "."
     *        kendo.parseFloat("12.22"); //12.22
     *
     *        //assumes that current culture defines decimal separator as ",", group separator as "." and currency symbol as "€"
     *        kendo.parseFloat("1.212,22 €"); //1212.22
     *    </li>
     *    <li>
     *       <code>kendo.parseDate(string, [formats], [culture])</code> - converts a string to a JavaScript Date object, taking into account the given format/formats (or the given culture's set of default formats).
     *       Current culture is used if one is not specified.
     *        @exampleTitle Parse string to float
     *        @example
     *
     *        //current culture is "en-US"
     *        kendo.parseDate("12/22/2000"); //Fri Dec 22 2000
     *        kendo.parseDate("2000/12/22", "yyyy/MM/dd"); //Fri Dec 22 2000
     *    </li>
     * </ol>
     *
     * @section
     * <h3>Number formatting</h3>
     * The purpose of number formatting is to convert Number object to a human readable string using culture's specific settings. <code>kendo.format</code> and <code>kendo.toString</code>
     * methods support standard and custom numeric formats:
     * <h4>Standard numeric formats</h4>
     *<strong>n</strong> for number
     *       @exampleTitle Formatting using "n" format
     *       @example
     *       kendo.culture("en-US");
     *       kendo.toString(1234.567, "n"); //1,234.57
     *
     *       kendo.culture("de-DE");
     *       kendo.toString(1234.567, "n3"); //1.234,567
     *@section
     *<strong>c</strong> for currency
     *       @exampleTitle Formatting using "c" format
     *       @example
     *       kendo.culture("en-US");
     *       kendo.toString(1234.567, "c"); //$1,234.57
     *
     *       kendo.culture("de-DE");
     *       kendo.toString(1234.567, "c3"); //1.234,567 €
     *@section
     *<strong>p</strong> for percentage (number is multiplied by 100)
     *       @exampleTitle Formatting using "p" format
     *       @example
     *       kendo.culture("en-US");
     *       kendo.toString(0.222, "p"); //22.20 %
     *
     *       kendo.culture("de-DE");
     *       kendo.toString(0.22, "p3"); //22.000 %
     *@section
     *<strong>e</strong> for exponential
     *       @exampleTitle Formatting using "e" format
     *       @example
     *       kendo.toString(0.122, "e"); //1.22e-1
     *       kendo.toString(0.122, "e4"); //1.2200e-1
     *
     * @section
     * <h4>Custom numeric formats</h4>
     * You can create custom numeric format string using one or more custom numeric specifiers. Custom numeric format string is any tha is not a standard numeric format.
     * <div class="details-list">
     *   <h4 class="details-title">Format specifiers</h4>
     *   <dl>
     *     <dt>
     *       "0" - zero placeholder
     *     </dt>
     *     <dd>Replaces the zero with the corresponding digit if one is present; otherwise, zero appears in the result string - <code>kendo.toString(1234.5678, "00000") -> 01235</code></dd>
     *     <dt>
     *       "#" - digit placeholder
     *     </dt>
     *     <dd>Replaces the pound sign with the corresponding digit if one is present; otherwise, no digit appears in the result string - <code>kendo.toString(1234.5678, "#####") -> 1235</code></dd>
     *     <dt>
     *       "." - Decimal placeholder
     *     </dt>
     *     <dd>Determines the location of the decimal separator in the result string - <code>kendo.tostring(0.45678, "0.00") -> 0.46 </code>(en-us)</dd>
     *     <dt>
     *       "," - group separator placeholder
     *     </dt>
     *     <dd>Insert localized group separator between each group - <code>kendo.tostring(12345678, "##,#") -> 12,345,678</code>(en-us)</dd>
     *     <dt>
     *       "%" - percentage placeholder
     *     </dt>
     *     <dd>Multiplies a number by 100 and inserts a localized percentage symbol in the result string</dd>
     *     <dt>
     *       "e" - exponential notation
     *     </dt>
     *     <dd><code>kendo.toString(0.45678, "e0") -> 5e-1</code></dd>
     *     <dt>
     *       ";" - section separator
     *     </dt>
     *     <dd>Defines sections wih separate format strings for positive, negative, and zero numbers</dd>
     *     <dt>
     *       "string"/'string' - Literal string delimiter
     *     </dt>
     *     <dd>Indicates that the enclosed characters should be copied to the result string</dd>
     *   </dl>
     * </div>
     *
     * @section
     * <h3>Date formatting</h3>
     * The purpose of date formatting is to convert Date object to a human readable string using culture's specific settings. <code>kendo.format</code> and <code>kendo.toString</code>
     * methods support standard and custom date formats:
     * <h4>Standard date formats</h4>
     * <div class="details-list">
     *   <h4 class="details-title">Format specifiers</h4>
     *   <dl>
     *     <dt>
     *       "d" - short date pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "d") -> 11/6/2000</code></dd>
     *     <dt>
     *       "D" - long date pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "D") -> Monday, November 06, 2000</code></dd>
     *     <dt>
     *       "F" - Full date/time pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "D") -> Monday, November 06, 2000 12:00:00 AM</code></dd>
     *     <dt>
     *       "g" - General date/time pattern (short time)
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "g") -> 11/6/2000 12:00 AM</code></dd>
     *     <dt>
     *       "G" - General date/time pattern (long time)
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "G") -> 11/6/2000 12:00:00 AM</code></dd>
     *     <dt>
     *       "M/m" - Month/day pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "m") -> November 06</code></dd>
     *     <dt>
     *       "u" - Universal sortable date/time pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "u") -> 2000-11-06 00:00:00Z</code></dd>
     *     <dt>
     *       "Y/y" - Year/month pattern
     *     </dt>
     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "y") -> November, 2000</code></dd>
     *   </dl>
     * </div>
     *
     *@section
     * <h4>Custom date formats</h4>
     * <div class="details-list">
     *   <h4 class="details-title">Format specifiers</h4>
     *   <dl>
     *     <dt>
     *       "d"
     *     </dt>
     *     <dd>The day of the month, from 1 through 31</dd>
     *     <dt>
     *       "dd"
     *     </dt>
     *     <dd>The day of the month, from 01 through 31.</dd>
     *     <dt>
     *       "ddd"
     *     </dt>
     *     <dd>iThe abbreviated name of the day of the week</dd>
     *     <dt>
     *       "dddd"
     *     </dt>
     *     <dd>The full name of the day of the week</dd>
     *     <dt>
     *       "f"
     *     </dt>
     *     <dd>The tenths of a second in a date and time value</dd>
     *     <dt>
     *       "ff"
     *     </dt>
     *     <dd>The hundredths of a second in a date and time value</dd>
     *     <dt>
     *       "fff"
     *     </dt>
     *     <dd>The milliseconds in a date and time value</dd>
     *     <dt>
     *       "M"
     *     </dt>
     *     <dd>The month, from 1 through 12</dd>
     *     <dt>
     *       "MM"
     *     </dt>
     *     <dd>The month, from 01 through 12</dd>
     *     <dt>
     *       "MMM"
     *     </dt>
     *     <dd>The abbreviated name of the month</dd>
     *     <dt>
     *       "MMMM"
     *     </dt>
     *     <dd>The full name of the month</dd>
     *     <dt>
     *       "h"
     *     </dt>
     *     <dd>The hour, using a 12-hour clock from 1 to 12</dd>
     *     <dt>
     *       "hh"
     *     </dt>
     *     <dd>The hour, using a 12-hour clock from 01 to 12</dd>
     *     <dt>
     *       "H"
     *     </dt>
     *     <dd>The hour, using a 24-hour clock from 1 to 23</dd>
     *     <dt>
     *       "HH"
     *     </dt>
     *     <dd>The hour, using a 24-hour clock from 01 to 23</dd>
     *     <dt>
     *       "m"
     *     </dt>
     *     <dd>The minute, from 0 through 59</dd>
     *     <dt>
     *       "mm"
     *     </dt>
     *     <dd>The minute, from 00 through 59</dd>
     *     <dt>
     *       "s"
     *     </dt>
     *     <dd>The second, from 0 through 59</dd>
     *     <dt>
     *       "ss"
     *     </dt>
     *     <dd>The second, from 00 through 59</dd>
     *     <dt>
     *       "tt"
     *     </dt>
     *     <dd>The AM/PM designator</dd>
     *   </dl>
     * </div>
     *
     * @section
     * <p><h3>Widgets that depend on current culture are:</h3>
     *    <ul>
     *        <li> Calendar </li>
     *        <li> DatePicker </li>
     *        <li> TimePicker </li>
     *        <li> NumericTextBox </li>
     *    </ul>
     * </p>
     */

    kendo.culture = function(cultureName) {
        if (cultureName !== undefined) {
            var cultures = kendo.cultures,
                culture = cultures[cultureName] || cultures[EN];

            culture.calendar = culture.calendars.standard;
            cultures.current = culture;
        } else {
            return kendo.cultures.current;
        }
    };

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

    function pad(number) {
        return number < 10 ? "0" + number : number;
    }

    function formatDate(date, format) {
        var calendar = kendo.cultures.current.calendar,
            days = calendar.days,
            months = calendar.months;

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

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

            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 = date.getFullYear();
            } 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 = math.floor(date.getMilliseconds() / 10);
            } else if (match === "fff") {
                result = date.getMilliseconds();
            } else if (match === "tt") {
                result = date.getHours() < 12 ? calendar.AM[0] : calendar.PM[0]
            }

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

    //number formatting
    function formatNumber(number, format) {
        var culture = kendo.cultures.current,
            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,
            decimalIndex,
            sharpIndex,
            zeroIndex,
            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 = number.toFixed(precision);
            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.
        format = format.split(";");
        if (negative && format[1]) {
            //make number positive and get negative format
            number = -number;
            format = format[1];
        } 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];
        }

        if (format.indexOf("'") > -1 || format.indexOf("\"") > -1) {
            format = format.replace(literalRegExp, function(match) {
                literals.push(match);
                return PLACEHOLDER;
            });
        }

        isCurrency = format.indexOf("$") != -1;
        isPercent = format.indexOf("%") != -1;

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

        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;
        }

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

        if (decimalIndex != -1) {
            sharpIndex = format.lastIndexOf(SHARP);
            zeroIndex = format.lastIndexOf(ZERO);

            if (zeroIndex != -1) {
                value = number.toFixed(zeroIndex - decimalIndex);
                number = number.toString();
                number = number.length > value.length && sharpIndex > zeroIndex ? number : value;
            }
        } else {
            number = number.toFixed(0);
        }

        sharpIndex = format.indexOf(SHARP);
        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;

            //add group separator to the number if it is longer enough
            if (integerLength >= groupSize && format.indexOf(COMMA) != -1) {
                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);

            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;
                } else if (ch === COMMA) {
                    continue;
                }
            }

            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;
            }

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

        return number;
    }

    function toString(value, fmt) {
        if (fmt) {
            if (value instanceof Date) {
                return formatDate(value, fmt);
            } else if (typeof value === NUMBER) {
                return formatNumber(value, fmt);
            }
        }

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

    if (globalize) {
        toString = proxy(globalize.format, globalize);
    }

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

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

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

    kendo.toString = toString;
    })();


(function() {

    var nonBreakingSpaceRegExp = /\u00A0/g,
        exponentRegExp = /[eE][-+]?[0-9]+/,
        formatsSequence = ["G", "g", "d", "F", "D", "y", "m", "T", "t"];

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

    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 = 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) {
                var i = 0,
                    length = names.length,
                    name, nameLength;

                for (; i < length; i++) {
                    name = names[i];
                    nameLength = name.length;

                    if (value.substr(valueIdx, nameLength) == name) {
                        valueIdx += nameLength;
                        return i + 1;
                    }
                }
                return null;
            },
            checkLiteral = function() {
                if (value.charAt(valueIdx) == format[idx]) {
                    valueIdx++;
                }
            },
            calendar = culture.calendar,
            year = null,
            month = null,
            day = null,
            hours = null,
            minutes = null,
            seconds = null,
            milliseconds = null,
            idx = 0,
            valueIdx = 0,
            literal = false,
            date = new Date(),
            defaultYear = date.getFullYear(),
            shortYearCutOff = 30,
            ch, count, AM, PM, pmHour, length, pattern;

        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");
                    day = count < 3 ? getNumber(2) : getIndexByName(calendar.days[count == 3 ? "namesAbbr" : "names"]);

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

                    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 < 3 ? 2 : 4);
                    if (year === null) {
                        year = defaultYear;
                    }
                    if (year < shortYearCutOff) {
                        year = (defaultYear - defaultYear % 100) + year;
                    }
                } 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");
                    milliseconds = getNumber(count);
                    if (milliseconds === null || outOfRange(milliseconds, 0, 999)) {
                        return null;
                    }
                } else if (ch === "t") {
                    count = lookAhead("t");
                    pmHour = getIndexByName(calendar.PM);
                } else if (ch === "'") {
                    checkLiteral();
                    literal = true;
                } else {
                    checkLiteral();
                }
            }
        }

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

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

        return new Date(year, month, day, hours, minutes, seconds, milliseconds);
    }

    kendo.parseDate = function(value, formats, culture) {
        if (value instanceof Date) {
            return value;
        }

        var idx = 0,
            date = null,
            length, property, patterns;

        if (!culture) {
            culture = kendo.culture();
        } else if (typeof culture === STRING) {
            kendo.culture(culture);
            culture = kendo.culture();
        }

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

            for (; idx < length; idx++) {
                formats[idx] = patterns[formatsSequence[idx]];
            }
            formats[idx] = "ddd MMM dd yyyy HH:mm:ss";

            idx = 0;
        }

        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) {
        if (!value && value !== 0) {
           return null;
        }

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

        value = value.toString();
        culture = kendo.cultures[culture] || kendo.cultures.current;

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

        //handle exponential number
        if (exponentRegExp.test(value)) {
            value = parseFloat(value);
            if (isNaN(value)) {
                value = null;
            }
            return value;
        }

        if (value.indexOf(symbol) > -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) {
            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;
        }

        return value;
    };

    if (globalize) {
        kendo.parseDate = proxy(globalize.parseDate, globalize);
        kendo.parseFloat = proxy(globalize.parseFloat, globalize);
    }
})();

    function wrap(element) {
        var browser = $.browser;

        if (!element.parent().hasClass("k-animation-container")) {
            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)),
                left = (-radius[1]) + blur,
                right = (+radius[1]) + blur,
                bottom = (+radius[2]) + blur;

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

            element.wrap(
                         $("<div/>")
                         .addClass("k-animation-container")
                         .css({
                             width: element.outerWidth(),
                             height: element.outerHeight(),
                             marginLeft: -left,
                             paddingLeft: left,
                             paddingRight: right,
                             paddingBottom: bottom
                         }));
        } else {
            var wrap = element.parent(".k-animation-container");

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

            wrap.css({
                    width: element.outerWidth(),
                    height: element.outerHeight()
                });
        }

        if (browser.msie && math.floor(browser.version) <= 7) {
            element.css({
                zoom: 1
            });
        }

        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 property,
            propValue,
            propType,
            destProp;

        for (property in source) {
            propValue = source[property];
            propType = typeof propValue;
            if (propType === OBJECT && propValue !== null && propValue.constructor !== Array) {
                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;
    }

    /**
     * Contains results from feature detection.
     * @name kendo.support
     * @namespace Contains results from feature detection.
     */
    (function() {
        /**
         * Indicates the width of the browser scrollbar. A value of zero means that the browser does not show a visual representation of a scrollbar (i.e. mobile browsers).
         * @name kendo.support.scrollbar
         * @property {Boolean}
         */
        support.scrollbar = function() {
            var div = document.createElement("div"),
                result;

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

            result = div.offsetWidth - div.scrollWidth;

            document.body.removeChild(div);
            return result;
        };

        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>";

            /**
             * Indicates whether the browser supports setting of the &lt;tbody&gt; innerHtml.
             * @name kendo.support.tbodyInnerHtml
             * @property {Boolean}
             */
            support.tbodyInnerHtml = true;
        } catch (e) {
            support.tbodyInnerHtml = false;
        }

        /**
         * Indicates whether the browser supports touch events.
         * @name kendo.support.touch
         * @property {Boolean}
         */
        support.touch = "ontouchstart" in window;
        support.pointers = navigator.msPointerEnabled;

        /**
         * Indicates whether the browser supports CSS transitions.
         * @name kendo.support.transitions
         * @property {Boolean}
         */
        var transitions = support.transitions = false;
        var transforms = support.transforms = false;

        /**
         * Indicates whether the browser supports hardware 3d transitions.
         * @name kendo.support.hasHW3D
         * @property {Boolean}
         */
        support.hasHW3D = ("WebKitCSSMatrix" in window && "m11" in new WebKitCSSMatrix()) || "MozPerspective" in document.documentElement.style;
        support.hasNativeScrolling = typeof document.documentElement.style.webkitOverflowScrolling == "string";

        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 + "-",
                    prefix: prefix,
                    event: (lowPrefix === "o" || lowPrefix === "webkit") ? lowPrefix : ""
                };

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

                return false;
            }
        });

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

        support.detectOS = function (ua) {
            var os = false, match = [],
                agentRxs = {
                    fire: /(Silk)\/(\d+)\.(\d+(\.\d+)?)/,
                    android: /(Android)\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|PlayBook).*?Version\/(\d+)\.(\d+(\.\d+)?)/
                },
                osRxs = {
                    ios: /^i(phone|pad|pod)$/i,
                    android: /^android|fire$/i
                },
                testOs = function (agent) {
                    for (var os in osRxs) {
                        if (osRxs.hasOwnProperty(os) && osRxs[os].test(agent))
                            return os;
                    }
                    return agent;
                };

            for (var agent in agentRxs) {
                if (agentRxs.hasOwnProperty(agent)) {
                    match = ua.match(agentRxs[agent]);
                    if (match) {
                        os = {};
                        os.device = agent;
                        os.name = testOs(agent);
                        os[os.name] = true;
                        os.majorVersion = match[2];
                        os.minorVersion = match[3].replace("_", ".");
                        os.flatVersion = os.majorVersion + os.minorVersion.replace(".", "");
                        os.flatVersion = os.flatVersion + (new Array(4 - os.flatVersion.length).join("0")); // Pad with zeroes
                        os.appMode = window.navigator.standalone || (/file|local/).test(window.location.protocol) || typeof window.PhoneGap !== UNDEFINED; // Use file protocol to detect appModes.

                        break;
                    }
                }
            }
            return os;
        };

        /**
         * Parses the mobile OS type and version from the browser user agent.
         * @name kendo.support.mobileOS
         */
        support.mobileOS = support.detectOS(navigator.userAgent);

        support.zoomLevel = function() {
            return support.touch ? (document.documentElement.clientWidth / window.innerWidth) : 1;
        };

        /**
         * Indicates the browser device pixel ratio.
         * @name kendo.support.devicePixelRatio
         * @property {Float}
         */
        support.devicePixelRatio = window.devicePixelRatio === undefined ? 1 : window.devicePixelRatio;

        /**
         * Indicates whether the browser supports input placeholder.
         * @name kendo.support.placeholder
         * @property {Boolean}
         */
        support.placeholder = "placeholder" in document.createElement("input");
        support.stableSort = (function() {
            var sorted = [0,1,2,3,4,5,6,7,8,9,10,11,12].sort(function() { return 0 } );
            return sorted[0] === 0 && sorted[1] === 1 && sorted[2] === 2 && sorted[3] === 3 && sorted[4] === 4 &&
                sorted[5] === 5 && sorted[6] === 6 && sorted[7] === 7 && sorted[8] === 8 &&
                sorted[9] === 9 && sorted[10] === 10 && sorted[11] === 11 && sorted[12] === 12;
        })();

    })();

    /**
     * Exposed by jQuery.
     * @ignore
     * @name jQuery.fn
     * @namespace Handy jQuery plug-ins that are used by all Kendo widgets.
     */

    function size(obj) {
        var size = 0, key;
        for (key in obj) {
            obj.hasOwnProperty(key) && size++;
        }

        return size;
    }

    function isNodeEmpty(element) {
        return $.trim($(element).contents().filter(function () { return this.nodeType != 8 }).html()) === "";
    }

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

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

        if (support.touch && mobileOS.ios && mobileOS.flatVersion < 410) { // Extra processing only in broken iOS'
            var offset = type == "offset" ? result : element.offset(),
                positioned = (result.left == offset.left && result.top == offset.top);

            if (positioned) {
                return {
                    top: result.top - window.scrollY,
                    left: result.left - window.scrollX
                };
            }
        }

        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;
    }

    var fx = {
        promise: function (element, options) {
            if (options.show) {
                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();
        },

        transitionPromise: function(element, destination, options) {
            var container = kendo.wrap(element);
            container.append(destination);

            element.hide();
            destination.show();

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

            return element;
        }
    };

    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,
            show: 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) {
        element.each(function (idx, el) { // fire separate queues on every element to separate the callback elements
            el = $(el);
            el.queue(function () {
                fx.promise(el, prepareAnimationOptions(options, duration, reverse, complete));
            });
        });

        return element;
    }

    function animateTo(element, destination, options, duration, reverse, complete) {
        return fx.transitionPromise(element, destination, prepareAnimationOptions(options, duration, reverse, complete));
    }

    extend($.fn, /** @lends jQuery.fn */{
        kendoStop: function(clearQueue, gotoEnd) {
            return this.stop(clearQueue, gotoEnd);
        },

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

        kendoAnimateTo: function(destination, options, duration, reverse, complete) {
            return animateTo(this, destination, options, duration, reverse, complete);
        }
    });

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

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

        return element;
    }

    extend($.fn, /** @lends jQuery.fn */{
        kendoAddClass: function(classes, options){
            return toggleClass(this, classes, options, true);
        },
        kendoRemoveClass: function(classes, options){
            return toggleClass(this, classes, options, false);
        },
        kendoToggleClass: function(classes, options, toggle){
            return toggleClass(this, classes, options, toggle);
        }
    });

    var ampRegExp = /&/g,
        ltRegExp = /</g,
        gtRegExp = />/g;
    /**
     * Encodes HTML characters to entities.
     * @name kendo.htmlEncode
     * @function
     * @param {String} value The string that needs to be HTML encoded.
     * @returns {String} The encoded string.
     */
    function htmlEncode(value) {
        return ("" + value).replace(ampRegExp, "&amp;").replace(ltRegExp, "&lt;").replace(gtRegExp, "&gt;");
    }

    var touchLocation = function(e) {
        return {
            idx: 0,
            x: e.pageX,
            y: e.pageY
        };
    };

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

    if (support.touch) {
        /** @ignore */
        touchLocation = function(e, id) {
            var changedTouches = e.changedTouches || e.originalEvent.changedTouches;

            if (id) {
                var output = null;
                each(changedTouches, function(idx, value) {
                    if (id == value.identifier) {
                        output = {
                            idx: value.identifier,
                            x: value.pageX,
                            y: value.pageY
                        };
                    }
                });
                return output;
            } else {
                return {
                    idx: changedTouches[0].identifier,
                    x: changedTouches[0].pageX,
                    y: changedTouches[0].pageY
                };
            }
        };

        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) : null;
        };

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

    if (support.touch) {
        support.mousedown = "touchstart";
        support.mouseup = "touchend";
        support.mousemove = "touchmove";
        support.mousecancel = "touchcancel";
    } else {
        support.mousemove = "mousemove";
        support.mousedown = "mousedown";
        support.mouseup = "mouseup";
        support.mousecancel = "mouseleave";
    }

    var wrapExpression = function(members) {
        var result = "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, /** @lends kendo */ {
        /**
         * @name kendo.ui
         * @namespace Contains all classes for the Kendo UI widgets.
         */
        ui: {
            /**
             * Shows an overlay with a loading message, indicating that an action is in progress.
             * @name kendo.ui.progress
             * @function
             * @param {jQueryObject} container The container that will hold the overlay
             * @param {Boolean} toggle Whether the overlay should be shown or hidden
             */
            progress: function(container, toggle) {
                var mask = container.find(".k-loading-mask");

                if (toggle) {
                    if (!mask.length) {
                        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%")
                            .prependTo(container)
                            .css({ top: container.scrollTop(), left: container.scrollLeft() });
                    }
                } else if (mask) {
                    mask.remove();
                }
            }
        },
        fx: fx,
        data: {},
        mobile: {},
        keys: {
            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,
            F10: 121,
            F12: 123
        },
        support: support,
        animate: animate,
        ns: "",
        attr: function(value) {
            return "data-" + kendo.ns + value;
        },
        wrap: wrap,
        deepExtend: deepExtend,
        size: size,
        isNodeEmpty: isNodeEmpty,
        getOffset: getOffset,
        parseEffects: parseEffects,
        toggleClass: toggleClass,
        directions: directions,
        Observable: Observable,
        Class: Class,
        Template: Template,
        /**
         * Shorthand for {@link kendo.Template.compile}.
         * @name kendo.template
         * @function
         */
        template: proxy(Template.compile, Template),
        /**
         * Shorthand for {@link kendo.Template.render}.
         * @name kendo.render
         * @function
         */
        render: proxy(Template.render, Template),
        stringify: proxy(JSON.stringify, JSON),
        touchLocation: touchLocation,
        eventTarget: eventTarget,
        htmlEncode: htmlEncode,
        isLocalUrl: function(url) {
            return url && !localUrlRe.test(url);
        },
        /** @ignore */
        expr: function(expression, safe) {
            expression = expression || "";

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

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

            return expression;
        },
        /** @ignore */
        getter: function(expression, safe) {
            return getterCache[expression] = getterCache[expression] || new Function("d", "return " + kendo.expr(expression, safe));
        },
        /** @ignore */
        setter: function(expression) {
            return setterCache[expression] = setterCache[expression] || new Function("d,value", "d." + expression + "=value");
        },
        /** @ignore */
        accessor: function(expression) {
            return {
                get: kendo.getter(expression),
                set: kendo.setter(expression)
            };
        },
        /** @ignore */
        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 "[" + kendo.attr("role") + "=" + role + "]";
        },

        /** @ignore */
        logToConsole: function(message) {
            if (typeof(console) != "undefined" && console.log) {
                console.log(message);
            }
        }
    });

    var Widget = Observable.extend( /** @lends kendo.ui.Widget.prototype */ {
        /**
         * Initializes widget. Sets `element` and `options` properties.
         * @constructs
         * @class Represents a UI widget. Base class for all Kendo widgets
         * @extends kendo.Observable
         */
        init: function(element, options) {
            var that = this;

            that.element = $(element);

            Observable.fn.init.call(that);

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

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

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

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

        events: [],

        options: {
            prefix: ""
        },

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

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

    kendo.notify = noop;

    var templateRegExp = /template$/i,
        jsonRegExp = /^(?:\{.*\}|\[.*\])$/,
        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 (!isNaN(parseFloat(value))) {
            value = parseFloat(value);
        } else if (jsonRegExp.test(value)) {
           value = $.parseJSON(value);
        }

        return value;
    }

    function parseOptions(element, options) {
        var result = {},
            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, namespace) {
        var result,
            option,
            widget,
            idx,
            length,
            role,
            dataSource;

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

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

        if (!role) {
            return;
        }

        dataSource = parseOption(element, "dataSource");

        widget = (namespace || kendo.ui).roles[role];

        if (!widget) {
            return;
        }

        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);
            }
        }

        result = $(element).data("kendo" + widget.fn.options.name);

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

        return result;
    }

    kendo.init = function(element, namespace) {
        $(element).find("[data-" + kendo.ns + "role]").andSelf().each(function(){
            kendo.initWidget(this, {}, namespace);
        });
    }

    kendo.parseOptions = parseOptions;

    extend(kendo.ui, /** @lends kendo.ui */{
        Widget: Widget,
        roles: {},
        /**
         * Helper method for writing new widgets.
         * Exposes a jQuery plug-in that will handle the widget creation and attach its client-side object in the appropriate data-* attribute.
         * @name kendo.ui.plugin
         * @function
         * @param {kendo.ui.Widget} widget The widget function.
         * @param {Object} register <kendo.ui> The object where the reference to the widget is recorded.
         * @param {Object} prefix <""> The plugin function prefix, e.g. "Mobile" will register "kendoMobileFoo".
         * @example
         * function TextBox(element, options);
         * kendo.ui.plugin(TextBox);
         *
         * // initialize a new TextBox for each input, with the given options object.
         * $("input").kendoTextBox({ });
         * // get the TextBox object and call the value API method
         * $("input").data("kendoTextBox").value();
         */
        plugin: function(widget, register, prefix) {
            var name = widget.fn.options.name;

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

            register[name] = widget;

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

            name = "kendo" + prefix + name;
            // expose a jQuery plugin
            $.fn[name] = function(options) {
                $(this).each(function() {
                    new widget(this, options);
                });
                return this;
            }
        }
    });

    var MobileWidget = Widget.extend(/** @lends kendo.mobile.ui.Widget.prototype */{
        /**
         * Initializes mobile widget. Sets `element` and `options` properties.
         * @constructs
         * @class Represents a mobile UI widget. Base class for all Kendo mobile widgets.
         * @extends kendo.ui.Widget
         */
        init: function(element, options) {
            Widget.fn.init.call(this, element, options);
            this.wrapper = this.element;
        },

        options: {
            prefix: "Mobile"
        },

        events: [],

        viewShow: $.noop,

        viewInit: function(view) {
            this.view = view;
        }
    });

    /**
     * @name kendo.mobile
     * @namespace This object contains all code introduced by the Kendo mobile suite, plus helper functions that are used across all mobile widgets.
     */
    $.extend(kendo.mobile, {
        init: function(element) {
            kendo.init(element, kendo.mobile.ui);
        },

        /**
         * @name kendo.mobile.ui
         * @namespace Contains all classes for the Kendo Mobile UI widgets.
         */
        ui: {
            Widget: MobileWidget,
            roles: {},
            plugin: function(widget) {
                kendo.ui.plugin(widget, kendo.mobile.ui, "Mobile");
            }
        }
    });

    /**
     * Enables kinetic scrolling on touch devices
     * @name kendo.touchScroller
     * @function
     * @param {Selector} element The container element to enable scrolling for.
     */
    kendo.touchScroller = function(element, options) {
        if (support.touch && kendo.mobile.ui.Scroller) {
            element.kendoMobileScroller(options);
            return element.data("kendoMobileScroller");
        } else {
            return false;
        }
    }
})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.fx
     * @namespace This object contains the fx library that is used by all widgets using animation.
     * If this file is not included, all animations will be disabled but the basic functionality preserved.
     */
    var kendo = window.kendo,
        fx = kendo.fx,
        each = $.each,
        extend = $.extend,
        proxy = $.proxy,
        size = kendo.size,
        browser = $.browser,
        support = kendo.support,
        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") && $.browser.msie,
        matrix3d = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1 ],
        matrix3dRegExp = /matrix3?d?\s*\(.*,\s*([\d\w\.\-]+),\s*([\d\w\.\-]+),\s*([\d\w\.\-]+)/,
        cssParamsRegExp = /^(-?[\d\.\-]+)?[\w\s]*,?\s*(-?[\d\.\-]+)?[\w\s]*/i,
        translateXRegExp = /translatex?$/i,
        oldEffectsRegExp = /(zoom|fade|expand)(\w+)/,
        singleEffectRegExp = /(zoom|fade|expand)/,
        transformProps = ["perspective", "rotate", "rotateX", "rotateY", "rotateZ", "rotate3d", "scale", "scaleX", "scaleY", "scaleZ", "scale3d", "skew", "skewX", "skewY", "translate", "translateX", "translateY", "translateZ", "translate3d", "matrix", "matrix3d"],
        cssPrefix = transforms.css,
        round = Math.round,
        BLANK = "",
        PX = "px",
        NONE = "none",
        AUTO = "auto",
        SCALE = "scale",
        WIDTH = "width",
        HEIGHT = "height",
        HIDDEN = "hidden",
        ORIGIN = "origin",
        ABORT_ID = "abortId",
        OVERFLOW = "overflow",
        TRANSLATE = "translate",
        TRANSITION = cssPrefix + "transition",
        TRANSFORM = cssPrefix + "transform";

    kendo.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
        }
    };

    extend($.fn, {
        kendoStop: function(clearQueue, gotoEnd) {
            if (transitions) {
                return kendo.fx.stopQueue(this, clearQueue || false, gotoEnd || false);
            } else {
                return this.stop(clearQueue, gotoEnd);
            }
        }
    });

    /* jQuery support for scale transform animation (FF 3.5/3.6, Opera 10.x */
    $.fn.scale = function (val) {
        if (typeof val == "undefined") {
            return animationProperty(this, "scale");
        } else {
            $(this).css(TRANSFORM, "scale(" + val + ")");
        }
        return this;
    };

    var curProxy = $.fx.prototype.cur;
    $.fx.prototype.cur = function () {
        if (this.prop == "scale") {
            return parseFloat($(this.elem).scale());
        }

        return curProxy.apply(this, arguments);
    };

    $.fx.step.scale = function (fx) {
        $(fx.elem).scale(fx.now);
    };

    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, NONE).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 = {};

                effect.length > 1 && (effectBody["direction"] = (mirror && redirectedEffect ? kendo.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 = kendo.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 getComputedStyles(element, properties) {
        var styles = {};

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

                each(properties, function(idx, value) {
                    styles[value] = computedStyle.getPropertyValue(value);
                });
            } else
                if (element.currentStyle) { // Not really needed
                    var style = element.currentStyle;

                    each(properties, function(idx, value) {
                        styles[value] = style[value.replace(/\-(\w)/g, function (strMatch, g1) { return g1.toUpperCase() })];
                    });
                }
        } else {
            styles = document.defaultView.getComputedStyle(element, "");
        }

        return styles;
    }

    function slideToSlideIn(options) {
      options.effects.slideIn = options.effects.slide;
      delete options.effects.slide;
      return options;
    }

    function parseTransitionEffects(options) {
        var effects = options.effects,
            mirror;

        if (effects === "zoom") {
            effects = "zoomIn fadeIn";
        }
        if (effects === "slide") {
            effects = "slide:left";
        }
        if (effects === "fade") {
            effects = "fadeIn";
        }
        if (effects === "overlay") {
            effects = "slideIn:left";
        }
        if (/^overlay:(.+)$/.test(effects)) {
            effects = "slideIn:" + RegExp.$1;
        }

        mirror = options.reverse && /^(slide:)/.test(effects);

        if (mirror) {
            delete options.reverse;
        }

        options.effects = $.extend(kendo.parseEffects(effects, mirror), {show: true});

        return options;
    }

    if (transitions) {

        function keys(obj) {
            var acc = [];
            for (var propertyName in obj)
                acc.push(propertyName);
            return acc;
        }

        function removeTransitionStyles(element) {
            element.css(TRANSITION, NONE);

            if (!browser.safari) {
                element.css(HEIGHT);
            }
        }

        function activateTask(currentTransition) {
            var element = currentTransition.object;

            if (!currentTransition) return;

            element.css(currentTransition.setup);
            element.css(TRANSITION);
            element.data(ABORT_ID, setTimeout(function() {

                removeTransitionStyles(element);
                element.dequeue();
                currentTransition.complete.call(element);

            }, currentTransition.duration));

            element.css(currentTransition.CSS);
        }

        extend(kendo.fx, {
            transition: function(element, properties, options) {

                options = extend({
                        duration: 200,
                        ease: "ease-out",
                        complete: null,
                        exclusive: "all"
                    },
                    options
                );

                options.duration = $.fx ? $.fx.speeds[options.duration] || options.duration : options.duration;

                var transforms = [],
                    cssValues = {},
                    key;

                for (key in properties)
                    if (transformProps.indexOf(key) != -1)
                        transforms.push(key + "(" + properties[key] + ")");
                    else
                        cssValues[key] = properties[key];

                if (transforms.length)
                    cssValues[TRANSFORM] = transforms.join(" ");

                var currentTask = {
                    keys: keys(cssValues),
                    CSS: cssValues,
                    object: element,
                    setup: {},
                    duration: options.duration,
                    complete: options.complete
                };
                currentTask.setup[TRANSITION] = options.exclusive + " " + options.duration + "ms " + options.ease;

                var oldKeys = element.data("keys") || [];
                $.merge(oldKeys, currentTask.keys);
                element.data("keys", $.unique(oldKeys));

                activateTask(currentTask);
            },

            stopQueue: function(element, clearQueue, gotoEnd) {

                if (element.data(ABORT_ID)) {
                    clearTimeout(element.data(ABORT_ID));
                    element.removeData(ABORT_ID);
                }

                var that = this,
                    taskKeys = element.data("keys"),
                    retainPosition = (gotoEnd === false && taskKeys);

                if (retainPosition) {
                    var cssValues = getComputedStyles(element[0], taskKeys);
                }

                removeTransitionStyles(element);

                if (retainPosition) {
                    element.css(cssValues);
                }

                element.removeData("keys");

                if (that.complete) {
                    that.complete.call(element);
                }

                element.stop(clearQueue);
                return element;
            }

        });
    }

    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];

                if (translateXRegExp.test(property)) {
                    computed = parseInteger(match[2]);
                } else if (property.toLowerCase() == "translatey") {
                    computed = parseInteger(match[3]);
                } else if (property.toLowerCase() == "scale") {
                    computed = parseFloat(match[1]);
                }
            }

            return computed;
        } else
            return element.css(property);
    }

    kendo.fx.promise = function(element, options) {
        var promises = [], effects;

        effects = kendo.parseEffects(options.effects);
        options.effects = effects;

        element.data("animating", true);

        var props = { keep: [], restore: [] }, css = {},
            methods = { setup: [], teardown: [] }, properties = {},

            // create a promise for each effect
            promise = $.Deferred(function(deferred) {
                if (size(effects)) {
                    var opts = extend({}, options, { complete: deferred.resolve });

                    each(effects, function(effectName, settings) {
                        var effect = kendo.fx[effectName];

                        if (effect) {
                            var dir = kendo.directions[settings.direction];
                            if (settings.direction && dir) {
                                settings.direction = (options.reverse ? dir.reverse : settings.direction);
                                element.data(effectName, settings);
                            }

                            opts = extend(true, opts, settings);

                            each(methods, function (idx) {
                                if (effect[idx])
                                    methods[idx].push(effect[idx]);
                            });

                            each(props, function(idx) {
                                if (effect[idx])
                                    $.merge(props[idx], effect[idx]);
                            });

                            if (effect["css"])
                                css = extend(css, effect.css);
                        }
                    });

                    if (methods.setup.length) {
                        each ($.unique(props.keep), function(idx, value) {
                            if (!element.data(value))
                                element.data(value, element.css(value));
                        });

                        if (options.show) {
                            css = extend(css, { display: element.data("olddisplay") || "block" }); // Add show to the set
                        }

                        var buffer = css.transform;
                        delete css.transform;
                        if (transforms) {
                            css[TRANSFORM] = buffer;
                        }

                        element.css(css);
                        element.css("overflow"); // Nudge Chrome

                        each (methods.setup, function() { properties = extend(properties, this(element, opts)) });

                        if (kendo.fx["animate"]) {
                            options.init();
                            kendo.fx.animate(element, properties, opts);
                        }

                        return;
                    }
                } else if (options.show) {
                    element.css({ display: element.data("olddisplay") || "block" }).css("display");
                    options.init();
                }

                deferred.resolve();
            }).promise();

        promises.push(promise);

        //wait for all effects to complete
        $.when.apply(null, promises).then(function() {
            element
                .removeData("animating")
                .dequeue(); // call next animation from the queue

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

            if (size(effects)) {
                var restore = function() {
                    each ($.unique(props.restore), function(idx, value) {
                        element.css(value, element.data(value));
                    });
                };

                if ($.browser.msie) {
                    setTimeout(restore, 0); // Again jQuery callback in IE.
                }
                else {
                    restore();
                }

                each(methods.teardown, function() { this(element, options); }); // call the internal completion callbacks
            }

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

    kendo.fx.transitionPromise = function(element, destination, options) {
        kendo.fx.animateTo(element, destination, options);
        return element;
    };

    extend(kendo.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 {
                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 (hasZoom) {
                                    var half = (1 - params[1]) / 2;
                                    extend(single, {
                                                       zoom: +params[1],
                                                       marginLeft: element.width() * half,
                                                       marginTop: element.height() * half
                                                   });
                                } else 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))
                                                !isNaN(dX) && extend(single, { right: originalPosition.right - dX });
                                            else
                                                !isNaN(dX) && extend(single, { left: originalPosition.left + dX });

                                            if (!isNaN(originalPosition.bottom))
                                                !isNaN(dY) && extend(single, { bottom: originalPosition.bottom - dY });
                                            else
                                                !isNaN(dY) && extend(single, { top: originalPosition.top + dY });
                                        } else {
                                            !isNaN(dX) && extend(single, { marginLeft: originalPosition.left + dX });
                                            !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, extend({ 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.
                });
            }
        },

        animateTo: function(element, destination, options) {
            var direction,
                commonParent = element.parents().filter(destination.parents()).first(),
                originalOverflow = commonParent.css(OVERFLOW);

            options = parseTransitionEffects(options);
            commonParent.css(OVERFLOW, "hidden");

            $.each(options.effects, function(name, definition) {
                direction = direction || definition.direction;
            });

            function complete() {
                destination[0].style.cssText = "";
                element[0].style.cssText = ""; // Removing the whole style attribute breaks Android.
                commonParent.css(OVERFLOW, originalOverflow);
                options.completeCallback && options.completeCallback();
            }

            options.complete = $.browser.msie ? function() { setTimeout(complete) } : complete;

            if ("slide" in options.effects) {
              element.kendoAnimate(options);
              destination.kendoAnimate(slideToSlideIn(options));
            } else {
              (options.reverse ? element : destination).kendoAnimate(options);
            }
        },

        fade: {
            css: {
                opacity: function() {
                    var fade = $(this).data("fade"),
                        inDirection = fade ? fade.direction == "in" : false;

                    return inDirection && !this.style.opacity ? 0 : undefined;
                }
            },
            setup: function(element, options) {
                return extend({ opacity: options.effects.fade.direction == "out" ? 0 : 1 }, options.properties)
            }
        },
        zoom: {
            css: {
                transform: function() {
                    var zoom = $(this).data("zoom"),
                        inDirection = zoom ? zoom.direction == "in" : false;

                    return inDirection && transitions ? "scale(.01)" : undefined;
                },
                zoom: function() {
                    var zoom = $(this).data("zoom"),
                        inDirection = zoom ? zoom.direction == "in" : false;

                    return inDirection && hasZoom ? ".01" : undefined;
                }
            },
            setup: function(element, options) {
                var reverse = options.effects.zoom.direction == "out";

                if (hasZoom) {
                    var version = $.browser.version,
                        style = element[0].currentStyle,
                        width = style.width.indexOf("%") != -1 ? element.parent().width() : element.width(),
                        height = style.height.indexOf("%") != -1 ? element.parent().height() : parseInt(style.height, 10),
                        half = version < 9 && options.effects.fade ? 0 : (1 - (parseInt(element.css("zoom"), 10) / 100)) / 2; // Kill margins in IE7/8 if using fade

                    element.css({
                        marginLeft: width * (version < 8 ? 0 : half),
                        marginTop: height * half
                    });
                }

                return extend({ scale: reverse ? .01 : 1 }, options.properties)
            }
        },
        slide: {
            setup: function(element, options) {
                var direction = kendo.directions[options.effects.slide.direction],
                    extender = {}, offset, reverse = options.reverse,
                    property = transitions && options.transition !== false ? direction.transition : direction.property,
                    divisor = options.divisor || 1;

                if (!reverse) {
                    var origin = element.data(ORIGIN);
                    offset = (direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()) / divisor);
                    !origin && origin !== 0  && element.data(ORIGIN, parseFloat(animationProperty(element, property)));
                }

                extender[property] = reverse ? (element.data(ORIGIN) || 0) : (element.data(ORIGIN) || 0) + offset + PX;

                return extend(extender, options.properties);
            }
        },
        slideMargin: {
            setup: function(element, options) {
                var origin = element.data(ORIGIN),
                    offset = options.offset, margin,
                    extender = {}, reverse = options.reverse;

                !reverse && !origin && origin !== 0 && element.data(ORIGIN, parseInt(element.css("margin-" + options.axis), 10));

                margin = (element.data(ORIGIN) || 0);
                extender["margin-" + options.axis] = !reverse ? margin + offset : margin;
                return extend(extender, options.properties);
            }
        },
        slideTo: {
            setup: function(element, options) {
                var offset = (options.offset+"").split(","),
                    extender = {}, reverse = options.reverse;

                if (transitions && options.transition !== false) {
                    extender["translate"] = !reverse ? offset + PX : 0;
                } else {
                    extender["left"] = !reverse ? offset[0] : 0;
                    extender["top"] = !reverse ? offset[1] : 0;
                }
                element.css("left");

                return extend(extender, options.properties);
            }
        },
        slideIn: {
            setup: function(element, options) {
                var dir = kendo.directions[options.effects.slideIn.direction],
                    reverse = options.reverse,
                    direction = reverse ? kendo.directions[dir.reverse] : dir,
                    offset = -direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()),
                    extender = {};

                if (transitions && options.transition !== false) {
                    element.css(TRANSFORM, direction.transition + "(" + (!reverse ? offset : 0) + "px)");
                    extender[direction.transition] = reverse ? offset + PX : 0;
                    element.css(TRANSFORM);
                } else {
                    !reverse && element.css(direction.property, offset + PX);
                    extender[direction.property] = reverse ? offset + PX : 0;
                    element.css(direction.property); // Read a style to force Chrome to apply the change.
                }

                return extend(extender, options.properties);
            }
        },
        expand: {
            keep: [ OVERFLOW ],
            css: { overflow: HIDDEN },
            restore: [ OVERFLOW ],
            setup: function(element, options) {
                var reverse = options.reverse,
                    direction = options.effects.expand.direction,
                    property = (direction ? direction == "vertical" : true) ? HEIGHT : WIDTH,
                    setLength = element[0].style[property],
                    oldLength = element.data(property),
                    length = parseInteger(oldLength || setLength) || round(element.css(property, AUTO )[property]()),
                    completion = {};

                completion[property] = (reverse ? 0 : length) + PX;
                element.css(property, reverse ? length : 0).css(property);
                if (oldLength === undefined) {
                    element.data(property, setLength);
                }

                return extend(completion, options.properties);
            },
            teardown: function(element, options) {
                var direction = options.effects.expand.direction,
                    property = (direction ? direction == "vertical" : true) ? 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!
                }
            }
        },
        simple: {
            setup: function(element, options) {
                return options.properties;
            }
        }
    });

    kendo.fx.expandVertical = kendo.fx.expand; // expandVertical is deprecated.

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

    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() {
            this._started = true;
            animationFrame(this._tickProxy);
        },

        cancel: function() {
            this._started = false;
            this.onCancel();
        },

        _tick: function() {
            var that = this;
            if (!that._started) { return; }

            that.tick();

            if (!that.done()) {
                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 = options.duration || 300;

            that.tick = that._easeProxy(options.ease || Ease.easeOutQuad);

            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;
})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        extend = $.extend,
        odataFilters = {
            eq: "eq",
            neq: "ne",
            gt: "gt",
            gte: "ge",
            lt: "lt",
            lte: "le",
            contains : "substringof",
            endswith: "endswith",
            startswith: "startswith"
        },
        mappers = {
            pageSize: $.noop,
            page: $.noop,
            filter: function(params, filter) {
                if (filter) {
                    params.$filter = toOdataFilter(filter);
                }
            },
            sort: function(params, orderby) {
                params.$orderby = $.map(orderby, function(value) {
                    var order = value.field.replace(/\./g, "/");

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

                    return order;
                }).join(",");
            },
            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) {
        var result = [],
            logic = filter.logic || "and",
            idx,
            length,
            field,
            type,
            format,
            operator,
            value,
            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);
            } else {
                field = field.replace(/\./g, "/"),

                filter = odataFilters[operator];

                if (filter && value !== undefined) {
                    type = $.type(value);
                    if (type === "string") {
                        format = "'{1}'";
                        value = value.replace(/'/g, "''");
                    } else if (type === "date") {
                        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})";
                        }
                    } 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) {
                    var params,
                        value,
                        option,
                        dataType;

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

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

                        for (option in options) {
                            if (mappers[option]) {
                                mappers[option](params, options[option]);
                            } 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;
                }
            }
        }
    });
})(jQuery);
(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,
                data = options.data;

            if (model) {
                if (isPlainObject(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 = kendo.data.Model.define(model);
                }

                that.model = model;
            }

            if (total) {
                total = that.getter(total);
                that.total = function(data) {
                    return parseInt(total(data));
                };
            }

            if (data) {
                data = that.xpathToMember(data);
                that.data = function(value) {
                    var record, field, result = that.evaluate(value, data),
                        idField,
                        modelInstance;

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

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

                        return map(result, function(value) {
                            if (value) {
                                record = {};
                                for (field in model.fields) {
                                    record[field] = modelInstance._parse(field, model.fields[field].field(value));
                                }
                                return record;
                            }
                        });
                    }

                    return result;
                };
            }
        },
        total: function(result) {
            return this.data(result).length;
        },
        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
        }
    });
})(jQuery);
(function($, undefined) {
    var extend = $.extend,
        proxy = $.proxy,
        isFunction = $.isFunction,
        isPlainObject = $.isPlainObject,
        isEmptyObject = $.isEmptyObject,
        isArray = $.isArray,
        grep = $.grep,
        ajax = $.ajax,
        map,
        each = $.each,
        noop = $.noop,
        kendo = window.kendo,
        Observable = kendo.Observable,
        Class = kendo.Class,
        STRING = "string",
        FUNCTION = "function",
        CREATE = "create",
        READ = "read",
        UPDATE = "update",
        DESTROY = "destroy",
        CHANGE = "change",
        GET = "get",
        MULTIPLE = "multiple",
        SINGLE = "single",
        ERROR = "error",
        REQUESTSTART = "requestStart",
        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\((.*?)\)\/$/,
        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);
        },

        wrapAll: function(source, target) {
            var idx, length;

            target = target || [];

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

            return target;
        },

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

            if (object !== null && toString.call(object) === "[object Object]") {
                var observable = object instanceof that.type || object instanceof Model;
                if (!observable) {
                    object = object instanceof ObservableObject ? object.toJSON() : object;
                    object = new that.type(object);
                }

                object.bind(CHANGE, function(e) {
                    that.trigger(CHANGE, {
                        field: e.field,
                        items: [this],
                        action: "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,

        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;

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

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

            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;
        }
    });

    var ObservableObject = Observable.extend({
        init: function(value) {
            var that = this,
                member,
                field,
                type;

            Observable.fn.init.call(this);

            for (field in value) {
                member = value[field];
                if (field.charAt(0) != "_") {
                    type = toString.call(member);

                    member = that.wrap(member, field);
                }
                that[field] = member;
            }

            that.uid = kendo.guid();
        },

        shouldSerialize: function(field) {
            return this.hasOwnProperty(field) && field !== "_events" && typeof this[field] !== FUNCTION
            && field !== "uid";
        },

        toJSON: function() {
            var result = {}, field;

            for (field in this) {
                if (this.shouldSerialize(field)) {
                    result[field] = this[field];
                }
            }

            return result;
        },

        get: function(field, call) {
            var that = this, result, getter;

            that.trigger(GET, { field: field });

            if (field === "this") {
                result = that;
            } else {
                getter = kendo.getter(field, true);

                result = getter(that);

                if (call && typeof result === FUNCTION) {
                    result = result.call(that);
                }
            }

            return result;
        },

        _set: function(field, value) {
            var that = this;
            if (field.indexOf(".")) {
                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;
                    }
                    path += ".";
                }
            }

            kendo.setter(field)(that, value);
        },

        set: function(field, value) {
            var that = this,
                current = that[field],
                setter;

            if (current != value) {
                if (!that.trigger("set", { field: field, value: value })) {

                    that._set(field, that.wrap(value, field));

                    that.trigger(CHANGE, { field: field });
                }
            }
        },

        wrap: function(object, field) {
            var that = this,
                type = toString.call(object);

            if (object !== null && type === "[object Object]" && !(object instanceof DataSource)) {
                if (!(object instanceof ObservableObject)) {
                    object = new ObservableObject(object);
                }

                (function(field) {
                    object.bind(GET, function(e) {
                        e.field = field + "." + e.field;
                        that.trigger(GET, e);
                    });

                    object.bind(CHANGE, function(e) {
                        e.field = field + "." + e.field;
                        that.trigger(CHANGE, e);
                    });
                })(field);
            } else if (object !== null && type === "[object Array]") {
                object = new ObservableArray(object);

                (function(field) {
                    object.bind(CHANGE, function(e) {
                        that.trigger(CHANGE, { field: field, index: e.index, items: e.items, action: e.action});
                    });
                })(field);
            }
            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) {
            if (typeof value === STRING) {
                var date = dateRegExp.exec(value);
                if (date) {
                    return new Date(parseInt(date[1]));
                }
            }
            return kendo.parseDate(value);
        },

        "boolean": function(value) {
            if (typeof value === STRING) {
                return value.toLowerCase() === "true";
            }
            return !!value;
        },

        "string": function(value) {
            return value + "";
        },

        "default": function(value) {
            return value;
        }
    };

    var defaultValues = {
        "string": "",
        "number": 0,
        "date": new Date(),
        "boolean": false,
        "default": ""
    }

    var Model = ObservableObject.extend({
        init: function(data) {
            var that = this;

            if (!data || $.isEmptyObject(data)) {
                data = $.extend({}, that.defaults, data);
            }

            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,
            parse;

            field = (that.fields || {})[field];
            if (field) {
                parse = field.parse;
                if (!parse && field.type) {
                    parse = parsers[field.type.toLowerCase()];
                }
            }

            return parse ? parse(value) : value;
        },

        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))) {
                    ObservableObject.fn.set.call(that, field, value, initiator);
                    that.dirty = true;
                }
            }
        },

        accept: function(data) {
            var that = this;

            extend(that, data);

            if (that.idField) {
                that.id = that.get(that.idField);
            }
            that.dirty = false;
        },

        isNew: function() {
            return this.id === this._defaultId;
        }
    });

    Model.define = function(options) {
        var model,
            proto = extend({}, { defaults: {} }, options),
            id = proto.id;

        if (id) {
            proto.idField = id;
        }

        if (proto.id) {
            delete proto.id;
        }

        if (id) {
            proto.defaults[id] = proto._defaultId = "";
        }

        for (var name in proto.fields) {
            var field = proto.fields[name],
            type = field.type || "default",
            value = null;

            name = field.field || name;

            if (!field.nullable) {
                value = proto.defaults[name] = field.defaultValue !== undefined ? field.defaultValue : defaultValues[type.toLowerCase()];
            }

            if (options.id === name) {
                proto._defaultId = value;
            }

            proto.defaults[name] = value;

            field.parse = field.parse || parsers[type];
        }

        model = Model.extend(proto);

        if (proto.fields) {
            model.fields = proto.fields;
            model.idField = proto.idField;
        }

        return model;
    }

    var Comparer = {
        selector: function(field) {
            return isFunction(field) ? field : getter(field);
        },

        asc: function(field) {
            var selector = this.selector(field);
            return function (a, b) {
                a = selector(a);
                b = selector(b);

                return a > b ? 1 : (a < b ? -1 : 0);
            };
        },

        desc: function(field) {
            var selector = this.selector(field);
            return function (a, b) {
                a = selector(a);
                b = selector(b);

                return a < b ? 1 : (a > b ? -1 : 0);
            };
        },

        create: function(descriptor) {
            return Comparer[descriptor.dir.toLowerCase()](descriptor.field);
        },

        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 PositionComparer = {
        selector: function(field) {
            return isFunction(field) ? field : getter(field);
        },

        asc: function(field) {
            var selector = this.selector(field);
            return function (a, b) {
                var valueA = selector(a);
                var valueB = selector(b);

                if (valueA === valueB) {
                    return a.__position - b.__position;
                }
                return valueA > valueB ? 1 : (valueA < valueB ? -1 : 0);
            };
        },

        desc: function(field) {
            var selector = this.selector(field);
            return function (a, b) {
                var valueA = selector(a);
                var valueB = selector(b);

                if (valueA === valueB) {
                    return a.__position - b.__position;
                }

                return valueA < valueB ? 1 : (valueA > valueB ? -1 : 0);
            };
        },

        create: function(descriptor) {
            return PositionComparer[descriptor.dir.toLowerCase()](descriptor.field);
        },

        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;
             }
        }
    };

    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, "\\");
        }

        function operator(op, a, b, ignore) {
            var date;

            if (b != undefined) {
                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 + ".lastIndexOf('" + b + "') == " + a + ".length - " + (b || "").length;
            },
            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"
            }
        };
    })();

    function Query(data) {
        this.data = data || [];
    }

    Query.normalizeFilter = normalizeFilter;

    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"
    };

    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;
        }
    }

    function normalizeAggregate(expressions) {
        return expressions = 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));
        },
        orderBy: function (selector) {
            var result = this.data.slice(0),
            comparer = isFunction(selector) || !selector ? Comparer.asc(selector) : selector.compare;

            return new Query(result.sort(comparer));
        },
        orderByDescending: function (selector) {
            return new Query(this.data.slice(0).sort(Comparer.desc(selector)));
        },
        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 } ]);
                    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, PositionComparer).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 = {};

            if (aggregates && aggregates.length) {
                for(idx = 0, len = this.data.length; idx < len; idx++) {
                    calculateAggregate(result, aggregates, this.data[idx], idx, len);
                }
            }
            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) {
        aggregates = aggregates || [];
        var idx,
        aggr,
        functionName,
        fieldAccumulator,
        len = aggregates.length;

        for (idx = 0; idx < len; idx++) {
            aggr = aggregates[idx];
            functionName = aggr.aggregate;
            var field = aggr.field;
            accumulator[field] = accumulator[field] || {};
            accumulator[field][functionName] = functions[functionName.toLowerCase()](accumulator[field][functionName], item, kendo.accessor(field), index, length);
        }
    }

    var functions = {
        sum: function(accumulator, item, accessor) {
            return accumulator = (accumulator || 0) + accessor.get(item);
        },
        count: function(accumulator, item, accessor) {
            return (accumulator || 0) + 1;
        },
        average: function(accumulator, item, accessor, index, length) {
            accumulator = (accumulator || 0) + accessor.get(item);
            if(index == length - 1) {
                accumulator = accumulator / length;
            }
            return accumulator;
        },
        max: function(accumulator, item, accessor) {
            var accumulator =  (accumulator || 0),
            value = accessor.get(item);
            if(accumulator < value) {
                accumulator = value;
            }
            return accumulator;
        },
        min: function(accumulator, item, accessor) {
            var value = accessor.get(item),
            accumulator = (accumulator || value)
            if(accumulator > value) {
                accumulator = value;
            }
            return accumulator;
        }
    };

    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;
    }

    function process(data, options) {
        var query = new Query(data),
        options = options || {},
        group = options.group,
        sort = normalizeGroup(group || []).concat(normalizeSort(options.sort || [])),
        total,
        filter = options.filter,
        skip = options.skip,
        take = options.take;

        if (filter) {
            query = query.filter(filter);
            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()
        };
    }

    function calculateAggregates(data, options) {
        var query = new Query(data),
        options = options || {},
        aggregates = options.aggregate,
        filter = options.filter;

        if(filter) {
            query = query.filter(filter);
        }
        return query.aggregate(aggregates);
    }

    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: noop
    });

    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;

            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() : operation.data;

            options = extend(true, {}, operation, options);
            parameters = extend(data, options.data);

            options.data = that.parameterMap(parameters, type);

            if (isFunction(options.url)) {
                options.url = options.url(parameters);
            }

            return options;
        }
    });

    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 Cache() {
        this._store = {};
    }

    Cache.prototype = /** @ignore */ {
        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)];
        }
    }

    var DataReader = Class.extend({
        init: function(schema) {
            var that = this, member, get, model;

            schema = schema || {};

            for (member in schema) {
                get = schema[member];

                that[member] = typeof get === STRING ? getter(get) : get;
            }

            if (isPlainObject(that.model)) {
                that.model = model = kendo.data.Model.define(that.model);

                var dataFunction = that.data,
                getters = {};

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

                that.data = function(data) {
                    var record,
                        getter,
                        idx,
                        length,
                        modelInstance = new that.model();

                    data = dataFunction(data);

                    if (!isEmptyObject(getters)) {
                        for (idx = 0, length = data.length; idx < length; idx++) {
                            record = data[idx];
                            for (getter in getters) {
                                record[getter] = modelInstance._parse(getter, getters[getter](record));
                            }
                        }
                    }

                    return data;
                }
            }
        },
        parse: identity,
        data: identity,
        total: function(data) {
            return data.length;
        },
        groups: identity,
        status: function(data) {
            return data.status;
        },
        aggregates: function() {
            return {};
        }
    });

    var DataSource = Observable.extend({
        init: function(options) {
            var that = this, id, model, transport;

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

            extend(that, {
                _map: {},
                _prefetch: {},
                _data: [],
                _ranges: [],
                _view: [],
                _pristine: [],
                _destroyed: [],
                _pageSize: options.pageSize,
                _page: options.page  || (options.pageSize ? 1 : undefined),
                _sort: normalizeSort(options.sort),
                _filter: normalizeFilter(options.filter),
                _group: normalizeGroup(options.group),
                _aggregate: options.aggregate
            });

            Observable.fn.init.call(that);

            transport = options.transport;

            if (transport) {
                transport.read = typeof transport.read === STRING ? { url: transport.read } : transport.read;

                if (options.type) {
                    transport = extend(true, {}, kendo.data.transports[options.type], transport);
                    options.schema = extend(true, {}, kendo.data.schemas[options.type], options.schema);
                }

                that.transport = isFunction(transport.read) ? transport: new RemoteTransport(transport);
            } else {
                that.transport = new LocalTransport({ data: options.data });
            }

            that.reader = new kendo.data.readers[options.schema.type || "json" ](options.schema);

            model = that.reader.model || {};

            that._data = that._observe(that._data);

            id = model.id;

            if (id) {
                that.id = function(record) {
                    return id(record);
                };
            }

            that.bind([ERROR, CHANGE, REQUESTSTART], options);
        },

        options: {
            data: [],
            schema: {},
            serverSorting: false,
            serverPaging: false,
            serverFiltering: false,
            serverGrouping: false,
            serverAggregates: false,
            sendAllFields: true,
            batch: false
        },

        get: function(id) {
            var idx, length, data = 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._data;

            for (idx = 0, length = data.length; idx < length; idx++) {
                if (data[idx].uid == id) {
                    return data[idx];
                }
            }
        },

        sync: function() {
            var that = this,
            idx,
            length,
            created = [],
            updated = [],
            destroyed = that._destroyed,
            data = that._data;

            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 = that._send("create", created);

            promises.push.apply(promises ,that._send("update", updated));
            promises.push.apply(promises ,that._send("destroy", destroyed));

            $.when.apply(null, promises)
            .then(function() {
                var idx,
                length;

                for (idx = 0, length = arguments.length; idx < length; idx++){
                    that._accept(arguments[idx]);
                }

                that._change();
            });

        },

        _accept: function(result) {
            var that = this,
            models = result.models,
            response = result.response,
            idx = 0,
            pristine = that.reader.data(that._pristine),
            type = result.type,
            length;

            if (response) {
                response = that.reader.data(that.reader.parse(response));
            } else {
                response = $.map(models, function(model) { return model.toJSON(); } );
            }

            if (!$.isArray(response)) {
                response = [response];
            }

            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(models[idx]);
                    } else if (type === "update") {
                        extend(pristine[that._pristineIndex(models[idx])], response[idx]);
                    }
                } else {
                    pristine.splice(that._pristineIndex(models[idx]), 1);
                }
            }
        },

        _pristineIndex: function(model) {
            var that = this,
            idx,
            length,
            pristine = that.reader.data(that._pristine);

            for (idx = 0, length = pristine.length; idx < length; idx++) {
                if (pristine[idx][model.idField] === model.id) {
                    return idx;
                }
            }
            return -1;
        },

        _promise: function(data, models, type) {
            var that = this,
            transport = that.transport;

            return $.Deferred(function(deferred) {
                transport[type].call(transport, extend({
                    success: function(response) {
                        deferred.resolve({
                            response: response,
                            models: models,
                            type: type
                        });
                    },
                    error: function(response) {
                        deferred.reject(response);
                        that.trigger(ERROR, response);
                    }
                }, data)
                );
            }).promise();
        },

        _send: function(method, data) {
            var that = this,
            idx,
            length,
            promises = [],
            transport = that.transport;

            if (that.options.batch) {
                if (data.length) {
                    promises.push(that._promise( { data: { models: toJSON(data) } }, data , method));
                }
            } else {
                for (idx = 0, length = data.length; idx < length; idx++) {
                    promises.push(that._promise( { data: data[idx].toJSON() }, [ data[idx] ], method));
                }
            }

            return promises;
        },

        add: function(model) {
            return this.insert(this._data.length, model);
        },

        insert: function(index, model) {
            if (!model) {
                model = index;
                index = 0;
            }

            if (!(model instanceof Model)) {
                if (this.reader.model) {
                    model = new this.reader.model(model);
                } else {
                    model = new ObservableObject(model);
                }
            }

            this._data.splice(index, 0, model);

            return model;
        },

        cancelChanges: function(model) {
            var that = this,
                pristineIndex,
                pristine = that.reader.data(that._pristine),
                index;

            if (model instanceof kendo.data.Model) {
                index = that.indexOf(model);
                pristineIndex = that._pristineIndex(model);
                if (index != -1) {
                    if (pristineIndex != -1 && !model.isNew()) {
                        extend(true, that._data[index], pristine[pristineIndex]);
                    } else {
                        that._data.splice(index, 1);
                    }
                }
            } else {
                that._data = that._observe(pristine);
                that._change();
            }
        },

        read: function(data) {
            var that = this, params = that._params(data);

            that._queueRequest(params, function() {
                that.trigger(REQUESTSTART);
                that._ranges = [];
                that.transport.read({
                    data: params,
                    success: proxy(that.success, that),
                    error: proxy(that.error, that)
                });
            });
        },

        indexOf: function(model) {
            var idx, length, data = this._data;

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

        _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;
            }
            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);
            }
        },

        remove: function(model) {
            var idx, length, data = this._data;

            for (idx = 0, length = data.length; idx < length; idx++) {
                if (data[idx].uid == model.uid) {
                    model = data[idx];
                    data.splice(idx, 1);
                    return model;
                }
            }
        },

        error: function(xhr, status, errorThrown) {
            this.trigger(ERROR, { xhr: xhr, status: status, errorThrown: errorThrown });
        },

        success: function(data) {
            var that = this,
            options = {},
            result,
            hasGroups = that.options.serverGrouping === true && that._group && that._group.length > 0;

            data = that.reader.parse(data);

            that._pristine = isPlainObject(data) ? $.extend(true, {}, data) : data.slice(0);

            that._total = that.reader.total(data);

            if (that._aggregate && that.options.serverAggregates) {
                that._aggregateResult = that.reader.aggregates(data);
            }

            if (hasGroups) {
                data = that.reader.groups(data);
            } else {
                data = that.reader.data(data);
            }

            that._data = that._observe(data);

            var start = that._skip || 0,
            end = start + that._data.length;

            that._ranges.push({ start: start, end: end, data: that._data });
            that._ranges.sort( function(x, y) { return x.start - y.start; } );

            that._dequeueRequest();
            that._process(that._data);
        },

        _observe: function(data) {
            var that = this,
                model = that.reader.model,
                wrap = false;

            if (model && data.length) {
                wrap = !(data[0] instanceof model);
            }

            if (data instanceof ObservableArray) {
                if (wrap) {
                    data.type = that.reader.model;
                    data.wrapAll(data, data);
                }
            } else {
                data = new ObservableArray(data, that.reader.model);
            }

            return data.bind(CHANGE, proxy(that._change, that));
        },

        _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 = that._total || that.reader.total(that._pristine);
                if (action === "add") {
                    total++;
                } else if (action === "remove") {
                    total--;
                } else if (action !== "itemchange" && !that.options.serverPaging) {
                    total = that.reader.total(that._pristine);
                }

                that._total = total;
                that._process(that._data, e);
            }
        },

        _process: function (data, e) {
            var that = this,
            options = {},
            result,
            hasGroups = that.options.serverGrouping === true && that._group && that._group.length > 0;

            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 = calculateAggregates(data, options);
            }

            result = process(data, options);

            that._view = result.data;

            if (result.total !== undefined && !that.options.serverFiltering) {
                that._total = result.total;
            }

            that.trigger(CHANGE, e);
        },

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

        data: function(value) {
            var that = this;
            if (value !== undefined) {
                that._data = this._observe(value);

                that._total = that._data.length;

                that._process(that._data);
            } else {
                return that._data;
            }
        },

        view: function() {
            return this._view;
        },

        query: function(options) {
            var that = this,
            result,
            remote = that.options.serverSorting || that.options.serverPaging || that.options.serverFiltering || that.options.serverGrouping || that.options.serverAggregates;

            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);
                }
            }

            if (remote || (that._data === undefined || that._data.length == 0)) {
                that.read(options);
            } else {
                that.trigger(REQUESTSTART);
                result = process(that._data, options);

                if (!that.options.serverFiltering) {
                    if (result.total !== undefined) {
                        that._total = result.total;
                    } else {
                        that._total = that._data.length;
                    }
                }

                that._view = result.data;
                that._aggregateResult = calculateAggregates(that._data, options);
                that.trigger(CHANGE);
            }
        },

        fetch: function(callback) {
            var that = this;

            if (callback && isFunction(callback)) {
                that.one(CHANGE, callback);
            }

            that._query();
        },

        _query: function(options) {
            var that = this;

            that.query(extend({}, {
                page: that.page(),
                pageSize: that.pageSize(),
                sort: that.sort(),
                filter: that.filter(),
                group: that.group(),
                aggregate: that.aggregate()
            }, options));
        },

        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 });
                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 this._total || 0;
        },

        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;
        },

        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;

            data = that._findRange(skip, math.min(skip + take, that.total()));

            if (data.length) {
                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;
                try {
                    that.options.serverPaging = true;
                    that.options.serverSorting = true;
                    that._process(data);
                } finally {
                    that.options.serverPaging = paging;
                    that.options.serverSorting = sorting;
                }

                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,
                length;

            for (skipIdx = 0, length = ranges.length; skipIdx < length; skipIdx++) {
                range = ranges[skipIdx];
                if (start >= range.start && start <= range.end) {
                    var count = 0;

                    for (takeIdx = skipIdx; takeIdx < length; takeIdx++) {
                        range = ranges[takeIdx];

                        if (range.data.length && start + count >= range.start /*&& count + count <= range.end*/) {
                            rangeData = range.data;
                            rangeEnd = range.end;

                            if (!remote) {
                                processed = process(range.data, { sort: that.sort(), filter: that.filter() });
                                rangeData = processed.data;

                                if (processed.total !== undefined) {
                                    rangeEnd = processed.total;
                                }
                            }

                            startIndex = 0;
                            if (start + count > range.start) {
                                startIndex = (start + count) - range.start;
                            }
                            endIndex = rangeData.length;
                            if (rangeEnd > end) {
                                endIndex = endIndex - (rangeEnd - end);
                            }
                            count += endIndex - startIndex;
                            data = data.concat(rangeData.slice(startIndex, endIndex));

                            if (end <= range.end && count == end - start) {
                                return data;
                            }
                        }
                    }
                    break;
                }
            }
            return [];
        },

        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() {
            var that = this;
            return that._take || that._pageSize;
        },

        prefetch: function(skip, take, callback) {
            var that = this,
            size = math.min(skip + take, that.total()),
            range = { start: skip, end: size, data: [] },
            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() {
                        that.transport.read({
                            data: options,
                            success: function (data) {
                                that._dequeueRequest();
                                var found = false;
                                for (var i = 0, len = that._ranges.length; i < len; i++) {
                                    if (that._ranges[i].start === skip) {
                                        found = true;
                                        range = that._ranges[i];
                                        break;
                                    }
                                }
                                if (!found) {
                                    that._ranges.push(range);
                                }

                                data = that.reader.parse(data);
                                range.data = that._observe(that.reader.data(data));
                                range.end = range.start + range.data.length;
                                that._ranges.sort( function(x, y) { return x.start - y.start; } );
                                that._total = that.reader.total(data);
                                if (callback) {
                                    callback();
                                }
                            }
                        });
                    });
                }, 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;
        }
    });

    DataSource.create = function(options) {
        options = options && options.push ? { data: options } : 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;

        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];

            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;
    }

    extend(true, kendo.data, /** @lends kendo.data */ {
        readers: {
            json: DataReader
        },
        Query: Query,
        DataSource: DataSource,
        ObservableObject: ObservableObject,
        ObservableArray: ObservableArray,
        LocalTransport: LocalTransport,
        RemoteTransport: RemoteTransport,
        Cache: Cache,
        DataReader: DataReader,
        Model: Model
    });
})(jQuery);
(function ($, undefined) {
    var kendo = window.kendo,
        Observable = kendo.Observable,
        ObservableObject = kendo.data.ObservableObject,
        ObservableArray = kendo.data.ObservableArray,
        toString = {}.toString,
        data = kendo.data,
        Class = kendo.Class,
        GET = "get",
        innerText,
        CHANGE = "change";

    (function() {
        var a = document.createElement("a");
        if (a.innerText !== undefined) {
            innerText = "innerText"
        } else if (a.textContent !== undefined) {
            innerText = "textContent";
        }
    })();

    var Binding = Observable.extend( {
        init: function(root, source, path) {
            var that = this;

            Observable.fn.init.call(that);

            that.root = root;
            that.source = source;
            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);
            }
        },

        change: function(e) {
            var dependency,
                idx,
                ch,
                that = this;

            if (that.path === "this") {
                that.trigger("change", e);
            } else {
                for (dependency in that.dependencies) {
                    idx = dependency.indexOf(e.field);

                    if (idx === 0) {
                       ch = dependency.charAt(e.field.length);

                       if (!ch || ch === "." || ch === "[") {
                            that.trigger("change", e);
                            break;
                       }
                    }
                }
            }
        },

        start: function() {
            if (this.observable) {
                this.source.bind("get", this._access);
            }
        },

        stop: function() {
            if (this.observable) {
                this.source.unbind("get", this._access);
            }
        },

        get: function() {
            var that = this,
                result = that.source;

            that.start();

            if (that.observable) {
                result = that.source.get(that.path);

                if (typeof result === "function") {
                    result = $.proxy(result, that.source);

                    result = result();
                }
            }

            that.stop();

            return result;
        },

        set: function(value) {
            this.source.set(this.path, value);
        },

        destroy: function() {
            if (this.observable) {
                this.source.unbind("change", this._change);
            }
        }
    });

    var EventBinding = Binding.extend( {
        get: function() {
            var source = this.source,
                handler = source.get(this.path);

            if (handler === undefined) {
                handler = this.root.get(this.path);
                source = this.root;
            }

            return $.proxy(handler, source);
        }
    });

    var TemplateBinding = Binding.extend( {
        init: function(root, source, path, template) {
            var that = this;

            Binding.fn.init.call(that, source, path);

            that.template = template;
        },

        render: function(value) {
            var html;

            this.start();

            html = kendo.render(this.template, value);

            this.stop();

            return html;
        }
    });

    var Binder = Class.extend({
        init: function(element, bindings, options) {
            this.element = element;
            this.bindings = bindings;
            this.options = options;
        },

        destroy: function() {
        }
    });

    var binders = {};

    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.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 binding = this.bindings.events[key],
                handler = this.handlers[key] = binding.get();

            $(this.element).bind(key, binding.source, handler);
        },

        destroy: function() {
            var element = $(this.element),
                handler;

            for (handler in this.handlers) {
                element.unbind(handler, this.handlers[handler]);
            }
        }
    });

    binders.text = Binder.extend({
        refresh: function() {
            this.element[innerText] = this.bindings["text"].get();
        }
    });

    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).bind(this.eventName, this._change);
        },

        change: function() {
            this.bindings["value"].set(this.element.value);
        },

        refresh: function() {
            this.element.value = this.bindings["value"].get();
        },

        destroy: function() {
            $(this.element).unbind(this.eventName, this._change);
        }
    });

    binders.source = Binder.extend({
        init: function(element, bindings, options) {
            Binder.fn.init.call(this, element, bindings, options);

            var source = bindings["source"].get();
        },

        refresh: function(e) {
            var that = this,
                source = that.bindings.source.get();

            if (source instanceof ObservableArray) {
                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;
        },

        destroy: function() {
            var source = this.bindings.source.get();

            source.unbind("change", this._change);
        },

        add: function(index, items) {
            var element = this.container(),
                idx,
                length,
                child,
                clone = element.cloneNode(false),
                reference = element.children[index];

            $(clone).html(kendo.render(this.template(), items));

            if (clone.children.length) {
                for (idx = 0, length = items.length; idx < length; idx++) {
                    child = clone.children[0];
                    element.insertBefore(child, reference || null);
                    if (this.bindings.template) {
                        bindElement(child, this.bindings.source.root, items[idx]);
                    } else {
                        bindElement(child, items[idx]);
                    }
                }
            }
        },

        remove: function(index, items) {
            var idx,
            element = this.container();

            for (idx = 0; idx < items.length; idx++) {
                element.removeChild(element.children[index]);
            }
        },

        render: function() {
            var source = this.bindings["source"].get();
            var idx, length;
            var element = this.container();
            var template = this.template();

            if (!(source instanceof ObservableArray) && toString.call(source) !== "[object Array]") {
                source = new ObservableArray([source]);
            }

            if (this.bindings.template) {
                $(element).html(this.bindings.template.render(source));

                if (element.children.length) {
                    for (idx = 0, length = source.length; idx < length; idx++) {
                        bindElement(element.children[idx], this.bindings.source.root, source[idx]);
                    }
                }
            }
            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) {
                        if (value !== false && value !== true) {
                            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();
                var element = this.element;

                if (element.type == "checkbox") {
                    if (value instanceof ObservableArray) {
                        if (value.indexOf(this.value(element)) >= 0) {
                            value = true;
                        }
                    }

                    element.checked = value === true;
                } else if (element.type == "radio") {
                    if (element.value == value) {
                        element.checked = true;
                    }
                }
            },

            value: function() {
                var element = this.element;
                var value = element.value;

                if (element.type == "checkbox") {
                    if (value == "on" || value == "off") {
                        value = element.checked;
                    }
                }

                return value;
            },
            destroy: function() {
                $(this.element).unbind("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,
                    option, 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();
                    for (var 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;
                            }
                        }
                    }
                }

                var value = this.bindings["value"].get();
                if (value instanceof ObservableArray) {
                    value.splice.apply(value, [0, value.length].concat(values));
                } else if (value instanceof ObservableObject || !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,
                    optionValue;

                if (!(values instanceof ObservableArray)) {
                    values = new ObservableArray([value]);
                }

                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;
                        }
                    }
                }
            },
            destroy: function() {
                $(this.element).unbind("change", this._change);
            }
        })
    }

    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] = binding.get();

                this.widget.bind(key, handler);
            },

            destroy: function() {
                var handler;

                for (handler in this.handlers) {
                    this.element.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: Binder.extend({
            init: function(widget, bindings, options) {
                Binder.fn.init.call(this, widget.element[0], bindings, options);

                this.widget = widget;
                this._dataBinding = $.proxy(this.dataBinding, this);
                this._dataBound = $.proxy(this.dataBound, this);
            },

            dataBinding: function() {
                var idx,
                    length,
                    widget = this.widget,
                    items = widget.items();

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

            dataBound: function() {
                var idx,
                    length,
                    widget = this.widget,
                    items = widget.items(),
                    dataSource = widget.dataSource,
                    view = dataSource.view(),
                    groups = dataSource.group() || [];

                if (items.length) {
                    if (groups.length) {
                        view = flattenGroups(view);
                    }

                    for (idx = 0, length = view.length; idx < length; idx++) {
                        bindElement(items[idx], this.bindings.source.root, view[idx]);
                    }
                }
            },

            refresh: function(e) {
                var that = this,
                    widget = that.widget;

                e = e || {};

                if (!e.action) {
                    that.destroy();

                    widget.bind("dataBinding", that._dataBinding);
                    widget.bind("dataBound", that._dataBound);

                    if (widget.dataSource instanceof kendo.data.DataSource) {
                        var source = this.bindings.source.get();
                        if (source instanceof kendo.data.DataSource) {
                            widget.setDataSource(source);
                        } else {
                            widget.dataSource.data(source);
                        }
                    }
                }
            },

            destroy: function() {
                var widget = this.widget;

                widget.unbind("dataBinding", this._dataBinding);
                widget.unbind("dataBound", this._dataBound);
            }
        }),

        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.bind("change", this._change);

                var value = this.bindings.value.get();
                this._valueIsObservableObject = value == null || value instanceof ObservableObject;
            },

            change: function() {
                var value = this.widget.value();
                var idx, length;

                var field = this.options.dataValueField || this.options.dataTextField;

                if (field) {
                    var source,
                        isObservableObject = this._valueIsObservableObject;

                    if (this.bindings.source) {
                        source = this.bindings.source.get();
                    }

                    if (value === "" && isObservableObject) {
                        value = null;
                    } else {
                        if (!source || source instanceof kendo.data.DataSource) {
                            source = this.widget.dataSource.view();
                        }

                        for (idx = 0, length = source.length; idx < length; idx++) {
                            if (source[idx].get(field) == value) {
                                if (isObservableObject) {
                                    value = source[idx];
                                } else {
                                    value = source[idx].get(field);
                                }
                                break;
                            }
                        }
                    }
                }

                this.bindings.value.set(value);
            },

            refresh: function() {
                var field = this.options.dataValueField || this.options.dataTextField;
                var value = this.bindings.value.get();

                if (field && value instanceof ObservableObject) {
                    value = value.get(field);
                }

                this.widget.value(value);
            },

            destroy: function() {
                this.widget.unbind("change", this._change);
            }
        })
    };

    var BindingTarget = Class.extend( {
        init: function(target, options) {
            this.target = target;
            this.options = options;
            this.toDestroy = [];
        },

        bind: function(bindings) {
            var that = this,
                nodeName = this.target.nodeName.toLowerCase(),
                key,
                specificBinders = binders[nodeName] || {};

            for (key in bindings) {
                this.applyBinding(key, bindings, specificBinders);
            }
        },

        applyBinding: function(name, bindings, specificBinders) {
            var binder = specificBinders[name] || binders[name],
                toDestroy = this.toDestroy,
                binding = bindings[name];

            if (binder) {
                binder = new binder(this.target, bindings, this.options);

                toDestroy.push(binder);

                if (binding instanceof Binding) {
                    binding.bind("change", function(e){
                        binder.refresh(e);
                    });
                    binder.refresh();
                    toDestroy.push(binding);
                } else {
                    for (var attribute in binding) {
                        (function(attribute) {
                            binding[attribute].bind("change", function() {
                                binder.refresh(attribute);
                            });
                        })(attribute);
                        binder.refresh(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;

            for (binding in bindings) {
                if (binding == "value") {
                    hasValue = true;
                } else if (binding == "source") {
                    hasSource = true;
                } else {
                    this.applyBinding(binding, bindings);
                }
            }

            if (hasSource) {
                this.applyBinding("source", bindings);
            }

            if (hasValue) {
                this.applyBinding("value", bindings);
            }
        },

        applyBinding: function(name, bindings) {
            var binder = binders.widget[name],
                toDestroy = this.toDestroy,
                binding = bindings[name];

            if (binder) {
                binder = new binder(this.target, bindings, this.target.options);

                toDestroy.push(binder);


                if (binding instanceof Binding) {
                    binding.bind("change", function(e) {
                        binder.refresh(e);
                    });

                    binder.refresh();

                    toDestroy.push(binding);
                } else {
                     for (var attribute in binding) {
                        (function(attribute) {
                            binding[attribute].bind("change", function() {
                                binder.refresh(attribute);
                            });
                        })(attribute);
                        binder.refresh(attribute);
                        toDestroy.push(binding[attribute])
                    }

                }
            } else {
                throw new Error("The " + name + " binding is not supported by the " + this.target.options.name + " widget");
            }
        }
    });

    function flattenGroups(data) {
        var idx, lenght, result = [];

        for (idx = 0, length = data.length; idx < length; idx++) {
            if (data[idx].hasSubgroups) {
                result = result.concat(flattenGroups(data[idx].items));
            } else {
                result = result.concat(data[idx].items);
            }
        }
        return result;
    }

    function bindingTargetForRole(role, element, namespace) {
        var type = namespace.roles[role];

        if (type) {
            return new WidgetBindingTarget(kendo.initWidget(element, type.options, namespace));
        }
    }

    var keyValueRegExp = /\w+:({([^}]*)}|[^,}]+)/g;
    var 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, root, source, type) {
        var binding,
            result = {};

        for (binding in bindings) {
            result[binding] = new type(root, source, bindings[binding]);
        }

        return result;
    }

    function bindElement(element, root, source, namespace) {
        var role = element.getAttribute("data-" + kendo.ns + "role"),
            idx,
            length,
            bind = element.getAttribute("data-" + kendo.ns + "bind"),
            children = element.children,
            deep = true,
            bindings,
            options = {},
            target;

        if (!namespace) {
            namespace = kendo.ui;
        }

        if (role || bind) {
            unbindElement(element);
        }

        if (role) {
            target = bindingTargetForRole(role, element, namespace);
        }

        if (bind) {
            bind = parseBindings(bind.replace(whiteSpaceRegExp, ""));

            if (!target) {
                options = kendo.parseOptions(element, { textField: "", valueField: "", template: "", valueUpdate: "change" });
                target = new BindingTarget(element, options);
            }

            target.source = source;

            bindings = createBindings(bind, root, source, Binding);

            if (options.template) {
                bindings.template = new TemplateBinding(root, source, "", options.template);
            }

            if (bindings.click) {
                bind.events = bind.events || {};
                bind.events.click = bind.click;
                delete bindings.click;
            }

            if (bindings.source) {
                deep = false;
            }

            if (bind.attr) {
                bindings.attr = createBindings(bind.attr, root, source, Binding);
            }

            if (bind.style) {
                bindings.style = createBindings(bind.style, root, source, Binding);
            }

            if (bind.events) {
                bindings.events = createBindings(bind.events, root, source, EventBinding);
            }

            target.bind(bindings);
        }

        if (target) {
            element.kendoBindingTarget = target;
        }

        if (deep && children) {
            for (idx = 0; idx < children.length; idx++) {
                bindElement(children[idx], root, source, namespace);
            }
        }
    }

    function bind(dom, object, namespace) {
        var idx, length;

        object = kendo.observable(object);
        dom = $(dom);

        for (idx = 0, length = dom.length; idx < length; idx++ ) {
            bindElement(dom[idx], object, object, namespace);
        }
    }

    function unbindElement(element) {
        var bindingTarget = element.kendoBindingTarget;

        if (bindingTarget) {
            bindingTarget.destroy();

            if ($.support.deleteExpando) {
                delete element.kendoBindingTarget;
            } else if (element.removeAttribute) {
                element.removeAttribute("kendoBindingTarget");
            } else {
                element.kendoBindingTarget = null;
            }
        }
    }

    function unbindElementTree(element) {
        var idx,
            length,
            children = element.children;

        unbindElement(element);

        if (children) {
            for (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;
    };

})(jQuery);
;(function($, undefined) {
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        INVALIDMSG = "k-invalid-msg",
        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])",
        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] !== undefined;
            }
            return false;
        },
        nameSpecialCharRegExp = /(\[|\]|\$|\.|\:|\+)/g;

    /**
     *  @name kendo.ui.Validator.Description
     *
     *  @section
     *  <p>
     *     Validator offers an easy way to do client-side form validation.
     *     Built around the HTML5 form validation attributes it supports variety of built-in validation rules, but also provides a convenient way for setting custom rules handling.
     *  </p>
     *  @exampleTitle <b>Validator</b> initialization to validate input elements inside a container
     *  @example
     *  <div id="myform">
     *   <input type="text" name="firstName" required />
     *   <input type="text" name="lastName" required />
     *   <button id="save" type="button">Save</button>
     *  </div>
     *
     *  <script>
     *   $(document).ready(function(){
     *       var validatable = $("#myform").kendoValidator().data("kendoValidator");
     *       $("#save").click(function() {
     *          if (validatable.validate()) {
     *              save();
     *          }
     *       });
     *   });
     *   </script>
     *  @section <h4>Validation Rules</h4>
     *
     *  @exampleTitle <strong>required</strong>- element should have a value
     *  @example
     *  <input type="text" name="firstName" required />
     *
     *  @exampleTitle <strong>pattern</strong>- constrains the value to match a specific regular expression
     *  @example
     *  <input type="text" name="twitter" pattern="https?://(?:www\.)?twitter\.com/.+i" />
     *
     *  @exampleTitle <strong>max/min</strong>- constrain the minimum and/or maximum numeric values that can be entered
     *  @example
     *  <input type="number" name="age" min="1" max="42" />
     *
     *  @exampleTitle <strong>step</strong>- when used in combination with the min and max attributes, constrains the granularity of values that can be entered
     *  @example
     *  <input type="number" name="age" min="1" max="100" step="2" />
     *
     *  @exampleTitle <strong>url</strong>- constrain the value to being a valid URL
     *  @example
     *  <input type="url" name="url" />
     *
     *  @exampleTitle <strong>email</strong>- constrain the value to being a valid email
     *  @example
     *  <input type="email" name="email" />
     *
     *  @section
     *  <p>Beside the built-in validation rules, KendoUI Validator also provides a convenient way for setting custom rules through its rules configuration option. </p>
     *
     *  @exampleTitle
     *  @example
     *  $("#myform").kendoValidator({
     *      rules: {
     *        custom: function(input) {
     *          // Only Tom will be a valid value for FirstName input
     *          return input.is("[name=firstname]") && input.val() === "Tom";
     *        }
     *      }
     * });
     *
     *  @section <h4>Validation Messages</h4>
     *  <p>There are several ways to control the messages which appears if validation fails:</p>
     *
     *  @exampleTitle Set the validation messages for all input elements, through configuration options
     *  @example
     *   $("#myform").kendoValidator({
     *      rules: {
     *          custom: function(input) {
     *                  //...
     *          }
     *      },
     *      messages: {
     *        // defines message for the 'custom' validation rule
     *        custom: "Please enter valid value for my custom rule",
     *        // overrides the built-in message for required rule
     *        required: "My custom required message",
     *        // overrides the built-in email rule message with a custom function which return the actual message
     *        email: function(input) {
     *          return getMessage(input);
     *        }
     *     }
     *  });
     *  @exampleTitle Use the title and validationMessage attributes to set per input element messages
     *  @example
     *     <input type="tel" pattern="\d{10}" validationMessage="Plase enter a ten digit phone number" />
     *
     *  @section <h4>Triggering validation</h4>
     *  <p>In order to trigger the element(s) validation, <strong>validate</strong> method should be used. It will return either <em>true</em> if validation succeeded or <em>false</em> in case of a failure. </p>
     *  <p>
     *  Note that if a HTML form element is set as validation container, the form submits will be automatically prevented if validation fails.
     *  </p>
     *  @section <h4>Initialize Kendo Validator with specific tooltip position</h4>
     *
     *  <p>
     *      Ideally Kendo Validator places its tooltips besides the validated input. However, if the input is later enhanced to a ComboBox, AutoComplete or other Kendo Widget, placing the
     *      tooltip beside the input may cover important information or break the widget rendering. In this case, you can specify where exactly do you want the tooltip to be placed by
     *      adding a span with data-for attribute set to the validated input ID and a class .k-invalid-msg. Check the example below:
     *  </p>
     *
     *  @exampleTitle <b>Validator</b> initialization with specific tooltip placement (the tooltip will remain outside of the AutoComplete widget after enhancement)
     *  @example
     *  <div id="myform">
     *      <input type="text" id="name" name="name" required />
     *      <span class="k-invalid-msg" data-for="name"></span>
     *  </div>
     *
     *  <script>
     *      $("#name").kendoAutoComplete({
     *                     dataSource: data,
     *                     separator: ", "
     *                 });
     *
     *      $("#myform").kendoValidator();
     *  </script>
     */
    var Validator = Widget.extend(/** @lends kendo.ui.Validator.prototype */{ /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {Object} [rules] Set of validation rules. Those rules will extend the built-in ones.
         * _example
         * $("#myform").kendoValidator({
         *      rules: {
         *          custom: function(input) {
         *              return input.is("[name=firstname]") && input.val() === "Tom"; // Only Tom will be a valid value for FirstName input
         *          }
         *      }
         * });
         * @option {Object} [messages] Set of messages (either strings or functions) which will be shown when given validation rule fails.
         *  By setting already existing key the appropriate built-in message will be overridden.
         * _example
         * $("#myform").kendoValidator({
         *      rules: {
         *          custom: function(input) {
         *             //...
         *          }
         *      },
         *      messages: {
         *          custom: "Please enter valid value for my custom rule",// defines message for the 'custom' validation rule
         *          required: "My custom required message", // overrides the built-in message for required rule
         *          email: function(input) { // overrides the built-in email rule message with a custom function which return the actual message
         *              return getMessage(input);
         *          }
         *      }
         * });
         */
        init: function(element, options) {
            var that = this;

            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._errors = {};
            that._attachEvents();
        },

        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.attr("checked") !== "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]").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 = 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 = 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")) || 0,
                            val = parseFloat(input.val());

                        return (((val-min)*10)%(step*10)) / 100 === 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
        },

        _submit: function(e) {
            if (!this.validate()) {
                e.stopPropagation();
                e.stopImmediatePropagation();
                e.preventDefault();
                return false;
            }
            return true;
        },

        _attachEvents: function() {
            var that = this;

            if (that.element.is(FORM)) {
                that.element.submit(proxy(that._submit, that));
            }

            if (that.options.validateOnBlur) {
                if (!that.element.is(INPUTSELECTOR)) {
                    that.element.delegate(INPUTSELECTOR, BLUR, function() {
                        that.validateInput($(this));
                    });
                } else {
                    that.element.bind(BLUR, function() {
                        that.validateInput(that.element);
                    });
                }
            }
        },

        /**
         * Validates the input element(s) against the declared validation rules.
         * @returns {Boolean} If all rules are passed successfully.
         * @example
         * // get a reference to the validatable form
         * var validatable = $("#myform").kendoValidator().data("kendoValidator");
         * // check validation on save button click
         * $("#save").click(function() {
         *     if (validatable.validate()) {
         *         save();
         *     }
         * });
         */
        validate: function() {
            var that = this,
                inputs,
                idx,
                invalid = false,
                length;

            that._errors = {};

            if (!that.element.is(INPUTSELECTOR)) {
                inputs = that.element.find(INPUTSELECTOR);

                for (idx = 0, length = inputs.length; idx < length; idx++) {
                    if (!that.validateInput(inputs.eq(idx))) {
                        invalid = true;
                    }
                }
                return !invalid;
            }
            return that.validateInput(that.element);
        },

        /**
         * Validates the input element against the declared validation rules.
         * @param {DomElement} input Input element to be validated.
         * @returns {Boolean} If all rules are passed successfully.
         */
        validateInput: function(input) {
            input = $(input);

            var that = this,
                template = that._errorTemplate,
                result = that._checkValidity(input),
                valid = result.valid,
                className = "." + INVALIDMSG,
                fieldName = (input.attr(NAME) || "").replace(nameSpecialCharRegExp, "\\$1"),
                DATAFOR = kendo.attr("for"),
                lbl = that.element.find(className + "[" + DATAFOR +"=" + fieldName + "]").add(input.next(className)).hide(),
                messageText;

            if (!valid) {
                messageText = that._extractMessage(input, result.key);
                that._errors[fieldName] = messageText;

                var messageLabel = $(template({ message: messageText })).addClass(INVALIDMSG).attr(DATAFOR, fieldName || "");
                if (!lbl.replaceWith(messageLabel).length) {
                    messageLabel.insertAfter(input)
                }
                messageLabel.show();
            }

            input.toggleClass(INVALIDINPUT, !valid);

            return valid;
        },

        _extractMessage: function(input, ruleKey) {
            var that = this,
                customMessage = that.options.messages[ruleKey],
                fieldName = input.attr(NAME);

            customMessage = $.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](input)) {
                    return { valid: false, key: rule };
                }
            }

            return { valid: true };
        },

        /**
         * Get the error messages if any.
         * @returns {Array} Messages for the failed validation rules.
         * @example
         * // get a reference to the validatable form
         * var validatable = $("#myform").kendoValidator().data("kendoValidator");
         * $("#save").click(function() {
         *     if (validatable.validate() === false) {
         *         // get the errors and write them out to the "errors" html container
         *         var errors = validatable.errors();
         *         $(errors).each(function() {
         *             $("#errors").html(this);
         *         });
         *     }
         * });
         */
        errors: function() {
            var results = [],
                errors = this._errors,
                error;

            for (error in errors) {
                results.push(errors[error]);
            }
            return results;
        }
    });

    kendo.ui.plugin(Validator);
})(jQuery);
(function ($, undefined) {
    var kendo = window.kendo,
        support = kendo.support,
        document = window.document,
        SURFACE = $(document.documentElement),
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        Observable = kendo.Observable,
        proxy = $.proxy,
        extend = $.extend,
        getOffset = kendo.getOffset,
        draggables = {},
        dropTargets = {},
        lastDropTarget,
        RESIZE_EVENT = "resize",
        START_EVENTS = "mousedown",
        MOVE_EVENTS = "mousemove",
        END_EVENTS = "mouseup mouseleave",
        KEYUP = "keyup",

        // Draggable events
        DRAGSTART = "dragstart",
        DRAG = "drag",
        DRAGEND = "dragend",
        DRAGCANCEL = "dragcancel",

        // DropTarget events
        DRAGENTER = "dragenter",
        DRAGLEAVE = "dragleave",
        DROP = "drop",

        // Drag events
        START = "start",
        MOVE = "move",
        END = "end",
        CANCEL = "cancel",
        TAP = "tap";

    if (support.touch) {
        RESIZE_EVENT = "orientationchange";
        START_EVENTS = "touchstart";
        MOVE_EVENTS = "touchmove";
        END_EVENTS = "touchend touchcancel";
    }

    function contains(parent, child) {
        try {
            return $.contains(parent, child) || parent == child;
        } catch (e) {
            return false;
        }
    }

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

    function addNS(events, ns) {
        return events.replace(/ /g, ns + " ");
    }

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

    /**
     * @name kendo.DragAxis.Description
     *
     * @section <h4>DragAxis</h4>
     * The DragAxis is used internally by the kendo.Drag component to store and calculate event data.
     * The Drag component contains two DragAxis instances: <code>x</code> for the horizontal coordinates, and <code>y</code> for the vertical.
     * The two DragAxis instances are available in each Drag event parameter.
     * @exampleTitle Access DragAxis information in Drag start event
     * @example
     * new kendo.Drag($("#foo"), {
     *  start: function(e) {
     *      console.log(x); // Horizontal axis
     *      console.log(y); // Vertical axis
     *  }
     * });
     *
     * @section Each axis instance contains the following fields:
     * <ul>
     *   <li><b>location</b> - the offset of the mouse/touch relative to the entire document (pageX/Y);</li>
     *   <li><b>startLocation</b> - the offset of the mouse/touch relative to the document when the drag started;</li>
     *   <li><b>client</b> - the offset of the mouse/touch relative to the viewport (clientX/Y);</li>
     *   <li><b>delta</b> - the change from the previous event location</li>
     *   <li><b>velocity</b> - the pixels per millisecond speed of the current move.</li>
     * </ul>
     */
    var DragAxis = Class.extend(/** @lends kendo.DragAxis.prototype */{
        /**
         * @constructs
         */
        init: function(axis) {
            this.axis = axis;
        },

        start: function(location, timeStamp) {
            var that = this,
                offset = location["page" + that.axis];

            that.startLocation = that.location = offset;
            that.client = location["client" + that.axis];
            that.velocity = that.delta = 0;
            that.timeStamp = timeStamp;
        },

        move: function(location, timeStamp) {
            var that = this,
                offset = location["page" + that.axis];

            if (!offset) {
                return;
            }

            that.delta = offset - that.location;
            that.location = offset;
            that.client = location["client" + that.axis];
            that.initialDelta = offset - that.startLocation;
            that.velocity = that.delta / (timeStamp - that.timeStamp);
            that.timeStamp = timeStamp;
        }
    });

    /**
     * @name kendo.Drag.Description
     * @section <h4>Drag</h4> The kendo Drag component provides a cross-browser, touch-friendly way to handle mouse and touch drag events.
     * @exampleTitle <b>Drag</b> initialization
     * @example
     * var drag = new kendo.Drag($("#draggable"));
     */
    var Drag = Observable.extend(/** @lends kendo.Drag.prototype */{
        /**
         * @constructs
         * @extends kendo.Observable
         * @param {DomElement} element the DOM element from which the drag event starts.
         * @param {Object} options Configuration options.
         * @option {Integer} [threshold] <0> The minimum distance the mouse/touch should move before the event is triggered.
         * @option {Boolean} [global] <false> If set to true, the drag event will be tracked beyond the element boundaries.
         * @option {Boolean} [allowSelection] <false> If set to true, the mousedown and selectstart events will not be prevented.
         * If set to false, dragging outside of the element boundaries will trigger the <code>end</code> event.
         * @option {Selector} [filter] If passed, the filter limits the child elements that will trigger the event sequence.
         */
        init: function(element, options) {
            var that = this,
                eventMap = {},
                filter,
                ns = "." + kendo.guid();

            options = options || {};
            filter = that.filter = options.filter;
            that.threshold = options.threshold || 0;

            element = $(element);
            Observable.fn.init.call(that);

            eventMap = {};

            eventMap[addNS(MOVE_EVENTS, ns)] = proxy(that._move, that);
            eventMap[addNS(END_EVENTS, ns)] = proxy(that._end, that);

            extend(that, {
                x: new DragAxis("X"),
                y: new DragAxis("Y"),
                element: element,
                surface: options.global ? SURFACE : element,
                pressed: false,
                eventMap: eventMap,
                ns: ns
            });

            element
                .on(START_EVENTS, filter, proxy(that._start, that))
                .on("dragstart", filter, preventDefault);

            if (!options.allowSelection) {
                element.on("mousedown selectstart", filter, preventDefault);
            }

            if (support.touch) {
                that.surface[0].addEventListener("touchend", function(e) { if (that.moved) { e.preventDefault() } }, true);
            }

            that.bind([
            /**
             * Fires when the user presses and releases the element without any movement or with a movement below the <code>threshold</code> specified.
             * @name kendo.Drag#tap
             * @event
             * @param {Event} e
             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
             * @param {jQueryEvent} e.event Reference to the jQuery event object.
             * @param {Element} e.target Reference to the DOM element from which the Drag started.
             * It is different from the element only if <code>filter</code> option is specified.
             */
            TAP,
            /**
             * Fires when the user starts dragging the element.
             * @name kendo.Drag#start
             * @event
             * @param {Event} e
             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
             * @param {jQueryEvent} e.event Reference to the jQuery event object.
             * @param {Element} e.target Reference to the DOM element from which the Drag started.
             * It is different from the element only if <code>filter</code> option is specified.
             */
            START,
            /**
             * Fires while dragging.
             * @name kendo.Drag#move
             * @event
             * @param {Event} e
             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
             * @param {jQueryEvent} e.event Reference to the jQuery event object.
             * @param {Element} e.target Reference to the DOM element from which the Drag started.
             * It is different from the element only if <code>filter</code> option is specified.
             */
            MOVE,
            /**
             * Fires when the drag ends.
             * @name kendo.Drag#end
             * @event
             * @param {Event} e
             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
             * @param {jQueryEvent} e.event Reference to the jQuery event object.
             * @param {Element} e.target Reference to the DOM element from which the Drag started.
             * It is different from the element only if <code>filter</code> option is specified.
             */
            END,
            /**
             * Fires when the drag is canceled. This  when the <code>cancel</code> method is called.
             * @name kendo.Drag#cancel
             * @event
             * @param {Event} e
             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
             * @param {jQueryEvent} e.event Reference to the jQuery event object.
             * @param {Element} e.target Reference to the DOM element from which the Drag started.
             * It is different from the element only if <code>filter</code> option is specified.
             */
            CANCEL], options);
        },

        /**
         * Capture the current drag, so that Drag listeners bound to parent elements will not trigger.
         * This method will not have any effect if the current drag instance is instantiated with the <code>global</code> option set to true.
         */
        capture: function() {
            Drag.captured = true;
        },

        /**
         * Discard the current drag. Calling the <code>cancel</code> method will trigger the <code>cancel</code> event.
         * The correct moment to call this method would be in the <code>start</code> event handler.
         * @exampleTitle Cancel the drag event sequence
         * @example
         * new kendo.Drag($("#foo"), {
         *  start: function(e) {
         *      e.cancel();
         *  }
         * });
         */
        cancel: function() {
            this._cancel();
            this.trigger(CANCEL);
        },

        _cancel: function() {
            this.pressed = false;
            this.surface.off(this.ns);
        },

        _start: function(e) {
            var that = this,
                filter = that.filter,
                originalEvent = e.originalEvent,
                touches = originalEvent && originalEvent.changedTouches,
                touch = touches && touches[0];

            if (that.pressed) { return; }

            if (filter) {
                that.target = $(e.target).is(filter) ? $(e.target) : $(e.target).closest(filter);
            } else {
                that.target = that.element;
            }

            that.pressed = true;
            that.moved = false;

            if (touch) {
                that.touchID = touch.identifier;
            }

            that._perAxis(START, touch || e, e.timeStamp);
            that.surface.off(that.eventMap).on(that.eventMap);
            Drag.captured = false;
        },

        _move: function(e) {
            var that = this,
                xDelta,
                yDelta,
                delta;

            if (!that.pressed) { return; }

            that._withEvent(e, function(location) {

                that._perAxis(MOVE, location, e.timeStamp);

                if (!that.moved) {
                    xDelta = that.x.initialDelta;
                    yDelta = that.y.initialDelta;

                    delta = Math.sqrt(xDelta * xDelta + yDelta * yDelta);

                    if (delta <= that.threshold) {
                        return;
                    }

                    if (!Drag.captured) {
                        that._trigger(START, e);
                        that.moved = true;
                    } else {
                        return that._cancel();
                    }
                }

                // Event handlers may cancel the swipe in the START event handler, hence the double check for pressed.
                if (that.pressed) {
                    that._trigger(MOVE, e);
                }
            });
        },

        _end: function(e) {
            var that = this;

            if (!that.pressed) { return; }

            that._withEvent(e, function() {
                that._cancel();

                if (that.moved) {
                    that._trigger(END, e);
                    that.moved = false;
                } else {
                    that._trigger(TAP, e);
                }
            });
        },

        _perAxis: function(method, location, timeStamp) {
            this.x[method](location, timeStamp);
            this.y[method](location, timeStamp);
        },

        _trigger: function(name, e) {
            var data = {
                x: this.x,
                y: this.y,
                target: this.target,
                event: e
            };

            if(this.trigger(name, data)) {
                e.preventDefault();
            }
        },

        _withEvent: function(e, callback) {
            var that = this,
                originalEvent = e.originalEvent,
                which = e.which,
                touches = originalEvent && originalEvent.changedTouches,
                idx = touches && touches.length;

            if (!touches) {
                return callback(e);
            }

            while (idx) {
                idx --;
                if (touches[idx].identifier === that.touchID) {
                    return callback(touches[idx]);
                }
            }
        }
    });

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

            that.capture = false;
            element[0].addEventListener(START_EVENTS, proxy(that._press, that), true);
            element.on(END_EVENTS, proxy(that._release, that));
            Observable.fn.init.call(that);

            that.bind(["press", "release"], options || {});
        },

        _press: function(e) {
            var that = this;
            that.trigger("press");
            if (that.capture) {
                e.preventDefault();
                e.originalEvent && e.originalEvent.preventDefault();
            }
        },

        _release: function(e) {
            var that = this;
            that.trigger("release");

            if (that.capture) {
                e.preventDefault();
                e.originalEvent && e.originalEvent.preventDefault();
                that.cancelCapture();
            }
        },

        captureNext: function() {
            this.capture = true;
        },

        cancelCapture: function() {
            this.capture = false;
        }
    });

    var PaneDimension = Observable.extend({
        init: function(options) {
            var that = this;
            Observable.fn.init.call(that);

            $.extend(that, options);

            that.max = 0;

            if (that.horizontal) {
                that.measure = "width";
                that.scrollSize = "scrollWidth";
                that.axis = "x";
            } else {
                that.measure = "height";
                that.scrollSize = "scrollHeight";
                that.axis = "y";
            }
        },

        outOfBounds: function(offset) {
            return  offset > this.max || offset < this.min;
        },

        totalSize: function() {
            return this.element[0][this.scrollSize];
        },

        present: function() {
            return this.max - this.min;
        },

        update: function() {
            var that = this;

            that.size = that.container[that.measure]();
            that.total = that.element[0][that.scrollSize];
            that.min = Math.min(that.max, that.size - that.total);
            that.trigger("change", that);
        }
    });

    var PaneDimensions = Class.extend({
        init: function(options) {
            var that = this,
                movable = options.movable;

            that.x = new PaneDimension(extend({horizontal: true}, options));
            that.y = new PaneDimension(extend({horizontal: false}, options));

            $(window).bind(RESIZE_EVENT, proxy(that.refresh, that));
        },

        refresh: function() {
            this.x.update();
            this.y.update();
        }
    });

    var PaneAxis = Observable.extend({
        init: function(options) {
            var that = this;
            extend(that, options);
            Observable.fn.init.call(that);
        },

        dragMove: function(delta) {
            var that = this,
                dimension = that.dimension,
                axis = that.axis,
                movable = that.movable,
                position = movable[axis] + delta;

            if (!dimension.present()) {
                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;

            extend(that, {elastic: true}, options);

            resistance = that.elastic ? 0.5 : 0;

            that.x = x = new PaneAxis({
                axis: "x",
                dimension: that.dimensions.x,
                resistance: resistance,
                movable: that.movable
            });

            that.y = y = new PaneAxis({
                axis: "y",
                dimension: that.dimensions.y,
                resistance: resistance,
                movable: that.movable
            });

            that.drag.bind(["move", "end"], {
                move: function(e) {
                    if (x.dimension.present() || y.dimension.present()) {
                        x.dragMove(e.x.delta);
                        y.dragMove(e.y.delta);
                        e.preventDefault();
                    }
                },

                end: function(e) {
                    e.preventDefault();
                }
            });
        }
    });

    var TRANSFORM_STYLE = support.transitions.prefix + "Transform",
        round = Math.round,
        translate;

    if (support.hasHW3D) {
        translate = function(x, y) {
            return "translate3d(" + round(x) + "px," + round(y) +"px,0)";
        }
    } else {
        translate = function(x, y) {
            return "translate(" + round(x) + "px," + round(y) +"px)";
        }
    }

    var Movable = Observable.extend({
        init: function(element) {
            var that = this;

            Observable.fn.init.call(that);

            that.element = $(element);
            that.x = 0;
            that.y = 0;
            that._saveCoordinates(translate(that.x, that.y));
        },

        translateAxis: function(axis, by) {
            this[axis] += by;
            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,
                newCoordinates = translate(that.x, that.y);

            if (newCoordinates != that.coordinates) {
                that.element[0].style[TRANSFORM_STYLE] = newCoordinates;
                that._saveCoordinates(newCoordinates);
                that.trigger("change");
            }
        },

        _saveCoordinates: function(coordinates) {
            this.coordinates = coordinates;
        }
    });

    var DropTarget = Widget.extend(/** @lends kendo.ui.DropTarget.prototype */ {
        /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {String} [group] <"default"> Used to group sets of draggable and drop targets. A draggable with the same group value as a drop target will be accepted by the drop target.
         */
        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: [
            /**
             * Fires when draggable moves over the drop target.
             * @name kendo.ui.DropTarget#dragenter
             * @event
             * @param {Event} e
             * @param {jQueryObject} e.draggable Reference to the draggable that enters the drop target.
             */
            DRAGENTER,
            /**
             * Fires when draggable moves out of the drop target.
             * @name kendo.ui.DropTarget#dragleave
             * @event
             * @param {Event} e
             * @param {jQueryObject} e.draggable Reference to the draggable that leaves the drop target.
             */
            DRAGLEAVE,
            /**
             * Fires when draggable is dropped over the drop target.
             * @name kendo.ui.DropTarget#drop
             * @event
             * @param {Event} e
             * @param {jQueryObject} e.draggable Reference to the draggable that is dropped over the drop target.
             * @param {jQueryObject} e.draggable.currentTarget The element that the drag and drop operation started from.
             */
            DROP
        ],

        options: {
            name: "DropTarget",
            group: "default"
        },

        _trigger: function(eventName, e) {
            var that = this,
                draggable = draggables[that.options.group];

            if (draggable) {
                return that.trigger(eventName, extend({}, e.event, {
                           draggable: draggable
                       }));
            }
        },

        _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);
            }
        }
    });

    /**
     * @name kendo.ui.Draggable.Description
     *
     * @section <h4>Draggable</h4>
     * Enable draggable functionality on any DOM element.
     *
     * @exampleTitle <b>Draggable</b> initialization
     * @example
     * var draggable = $("#draggable").kendoDraggable();
     *
     * @name kendo.ui.DropTarget.Description
     *
     * @section <h4>DropTarget</h4>
     * Enable any DOM element to be a target for draggable elements.
     *
     * @exampleTitle <b>DropTarget</b> initialization
     * @example
     * var dropTarget = $("#dropTarget").kendoDropTarget();
     */
    var Draggable = Widget.extend(/** @lends kendo.ui.Draggable.prototype */{
        /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {Integer} [distance] <5> The required distance that the mouse should travel in order to initiate a drag.
         * @option {Selector} [filter] Selects child elements that are draggable if a widget is attached to a container.
         * @option {String} [group] <"default"> Used to group sets of draggable and drop targets. A draggable with the same group value as a drop target will be accepted by the drop target.
         * @option {Object} [cursorOffset] <null> If set, specifies the offset of the hint relative to the mouse cursor/finger.
         * By default, the hint is initially positioned on top of the draggable source offset. The option accepts an object with two keys: <code>top</code> and <code>left</code>.
         * _exampleTitle Initialize Draggable with cursorOffset
         * _example
         * $("#draggable").kendoDraggable({cursorOffset: {top: 10, left: 10});
         * @option {Function | jQueryObject} [hint] Provides a way for customization of the drag indicator. If a function is supplied, it receives one argument - the draggable element's jQuery object.
         * _example
         *  //hint as a function
         *  $("#draggable").kendoDraggable({
         *      hint: function(element) {
         *          return $("#draggable").clone();
         *          // same as
         *          //  return element.clone();
         *      }
         *  });
         *
         * //hint as jQuery object
         *  $("#draggable").kendoDraggable({
         *      hint: $("#draggableHint");
         *  });
         */
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that.drag = new Drag(that.element, {
                global: true,
                filter: that.options.filter,
                threshold: that.options.distance,
                start: proxy(that._start, that),
                move: proxy(that._drag, that),
                end: proxy(that._end, that),
                cancel: proxy(that._cancel, that)
            });

            that.destroy = proxy(that._destroy, that);
            that.captureEscape = function(e) {
                if (e.keyCode === kendo.keys.ESC) {
                    that._trigger(DRAGCANCEL, {event: e});
                    that.drag.cancel();
                }
            }
        },

        events: [
            /**
             * Fires when item drag starts.
             * @name kendo.ui.Draggable#dragstart
             * @event
             * @param {Event} e
             */
            DRAGSTART,
             /**
             * Fires while dragging.
             * @name kendo.ui.Draggable#drag
             * @event
             * @param {Event} e
             */
            DRAG,
             /**
             * Fires when item drag ends.
             * @name kendo.ui.Draggable#dragend
             * @event
             * @param {Event} e
             */
            DRAGEND,
             /**
             * Fires when item drag is canceled by pressing the Escape key.
             * @name kendo.ui.Draggable#dragcancel
             * @event
             * @param {Event} e
             */
            DRAGCANCEL
        ],

        options: {
            name: "Draggable",
            distance: 5,
            group: "default",
            cursorOffset: null,
            dropped: false
        },

        _start: function(e) {
            var that = this,
                options = that.options,
                hint = options.hint;

            that.currentTarget = that.drag.target;
            that.currentTargetOffset = getOffset(that.currentTarget);

            if (hint) {
                that.hint = $.isFunction(hint) ? $(hint(that.currentTarget)) : hint;

                var offset = getOffset(that.currentTarget);
                that.hintOffset = offset;
                that.hint.css( {
                    position: "absolute",
                    zIndex: 10010, //the Window's z-index is 10000
                    left: offset.x,
                    top: offset.y
                })
                .appendTo(document.body);
            }

            draggables[options.group] = that;

            that.dropped = false;

            if (that._trigger(DRAGSTART, e)) {
                that.drag.cancel();
                that.destroy();
            }

            $(document).on(KEYUP, that.captureEscape);
        },

        updateHint: function(e) {
            var that = this,
                coordinates,
                offset = that.initialOffset,
                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 = that.hintOffset;
            }

            that.hint.css(coordinates);
        },

        _drag: function(e) {
            var that = this,
                cursorOffset = that.options.cursorOffset;

            e.preventDefault();

            that._withDropTarget(e, function(target) {
                if (!target) {
                    if (lastDropTarget) {
                        lastDropTarget._trigger(DRAGLEAVE, e);
                        lastDropTarget = null;
                    }
                    return;
                }

                if (lastDropTarget) {
                    if (target.element[0] === lastDropTarget.element[0]) {
                        return;
                    }

                    lastDropTarget._trigger(DRAGLEAVE, e);
                }

                target._trigger(DRAGENTER, e);
                lastDropTarget = target;
            });

            that._trigger(DRAG, e);

            if (that.hint) {
                that.updateHint(e);
            }
        },

        _end: function(e) {
            var that = this;

            that._withDropTarget(e, function(target) {
                if (target) {
                    target._drop(e);
                    lastDropTarget = null;
                }
            });

            that._trigger(DRAGEND, e);
            that._cancel(e.event);
        },

        _cancel: function(e) {
            var that = this;

            if (that.hint && !that.dropped) {
                that.hint.animate(that.currentTargetOffset, "fast", that.destroy);
            } else {
                that.destroy();
            }
        },

        _trigger: function(eventName, e) {
            var that = this;

            return that.trigger(
            eventName, extend(
            {},
            e.event,
            {
                x: e.x,
                y: e.y,
                currentTarget: that.currentTarget
            }));
        },

        _withDropTarget: function(e, callback) {
            var that = this,
                target,
                result,
                options = that.options,
                targets = dropTargets[options.group];

            if (targets && targets.length) {
                target = elementUnderCursor(e);

                if (that.hint && contains(that.hint, target)) {
                    that.hint.hide();
                    target = elementUnderCursor(e);
                    that.hint.show();
                }

                $.each(targets, function() {
                    var that = this,
                        element = that.element[0];

                    if (contains(element, target)) {
                        result = that;
                        return false;
                    }
                });

                callback(result);
            }
        },

        _destroy: 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(Draggable);
    kendo.Drag = Drag;
    kendo.Tap = Tap;

    extend(kendo.ui, {
        Pane: Pane,
        PaneDimensions: PaneDimensions,
        Movable: Movable
    });

 })(jQuery);
(function ($, undefined) {
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        CONTAINER_EMPTY_TEXT = "Drag a column header and drop it here to group by that column",
        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-arrow-${(data.dir || "asc") == "asc" ? "up" : "down"}-small">(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"),
                    height: target.height(),
                    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).children(".k-grid-toolbar").outerHeight() + 3;
    }

    var Groupable = Widget.extend({
        init: function(element, options) {
            var that = this,
                groupContainer,
                group = kendo.guid(),
                intializePositions = proxy(that._intializePositions, that),
                dropCuePositions = that._dropCuePositions = [];

            Widget.fn.init.call(that, element, options);

            groupContainer = that.groupContainer = $(that.options.groupContainer, that.element)
                .kendoDropTarget({
                    group: group,
                    dragenter: function(e) {
                        e.draggable.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
                        dropCue.css({top: dropCueOffsetTop(that.element), left: 0}).appendTo(groupContainer);
                    },

                    dragleave: function(e) {
                        e.draggable.hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
                        dropCue.remove();
                    }
                })
                .kendoDraggable({
                    filter: "div.k-group-indicator",
                    hint: hint,
                    group: group,
                    dragend: function(e) {
                        that._dragEnd(this, e);
                    },
                    dragcancel: proxy(that._dragCancel, that),
                    dragstart: function(e) {
                        var element = e.currentTarget,
                            marginLeft = parseInt(element.css("marginLeft")),
                            left = element.position().left - marginLeft;

                        intializePositions();
                        dropCue.css({top: dropCueOffsetTop(that.element), left: left}).appendTo(groupContainer);
                        this.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
                    },
                    drag: proxy(that._drag, that)
                })
                .delegate(".k-button", "click", function(e) {
                    e.preventDefault();
                    that._removeIndicator($(this).parent());
                })
                .delegate(".k-link", "click", 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();
                });

            that.element.kendoDraggable({
                filter: that.options.filter,
                hint: hint,
                group: group,
                dragend: function(e) {
                    that._dragEnd(this, e);
                },
                dragcancel: proxy(that._dragCancel, that),
                dragstart: function(e) {
                    var element, marginRight, left,
                        field = e.currentTarget.attr(kendo.attr("field"));

                    if(that.indicator(field)) {
                        e.preventDefault();
                        return;
                    }

                    intializePositions();
                    if(dropCuePositions.length) {
                        element = dropCuePositions[dropCuePositions.length - 1].element;
                        marginRight = parseInt(element.css("marginRight"));
                        left = element.position().left + element.outerWidth() + marginRight;
                    } else {
                        left = 0;
                    }

                    dropCue.css({top: dropCueOffsetTop(that.element), left: left}).appendTo(groupContainer);
                    this.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
                },
                drag: proxy(that._drag, that)
            });

            that.dataSource = that.options.dataSource;

            if(that.dataSource) {
                that._refreshHandler = proxy(that.refresh, that);
                that.dataSource.bind("change", that._refreshHandler);
            }
        },

        refresh: function() {
            var that = this,
                dataSource = that.dataSource;

            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;
            if (that.dataSource && that._refreshHandler) {
                that.dataSource.unbind("change", that._refreshHandler);
            }
        },

        options: {
            name: "Groupable",
            filter: "th"
        },

        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, dir: dir, title: title, ns: kendo.ns });
        },
        descriptors: function() {
            var that = this,
                indicators = $(".k-group-indicator", that.groupContainer),
                aggregates,
                names,
                field,
                idx,
                length,
                result;

            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;
            }

            var lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
                right = lastCuePosition.right,
                marginLeft = parseInt(lastCuePosition.element.css("marginLeft")),
                marginRight = parseInt(lastCuePosition.element.css("marginRight"));

            if(position >= right) {
                position = {
                    left: lastCuePosition.element.position().left + lastCuePosition.element.outerWidth() + marginRight,
                    element: lastCuePosition.element,
                    before: false
                };
            } else {
                position = $.grep(dropCuePositions, function(item) {
                    return item.left <= position && position <= item.right;
                })[0];

                if(position) {
                    position = {
                        left: position.element.position().left - marginLeft,
                        element: position.element,
                        before: true
                    };
                }
            }

            return position;
        },
        _drag: function(event) {
            var position = this._dropCuePosition(event.pageX);
            if(position) {
                dropCue.css({ left: position.left });
            }
        },
        _canDrop: function(source, target, position) {
            var next = source.next();
            return source[0] !== target[0] && (!next[0] || target[0] !== next[0] || position > next.position().left);
        },
        _dragEnd: function(draggable, event) {
            var that = this,
                field = event.currentTarget.attr(kendo.attr("field")),
                title = event.currentTarget.attr(kendo.attr("title")),
                sourceIndicator = that.indicator(field),
                dropCuePositions = that._dropCuePositions,
                lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
                position;

            if(draggable.dropped) {
                if(lastCuePosition) {
                    position = that._dropCuePosition(dropCue.offset().left + parseInt(lastCuePosition.element.css("marginLeft")) + parseInt(lastCuePosition.element.css("marginRight")));
                    if(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();
                }
            } else {
                if(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 = item.offset().left;
                return {
                    left: left,
                    right: left + item.outerWidth(),
                    element: item
                };
            });
        },
        _invalidateGroupContainer: function() {
            var groupContainer = this.groupContainer;
            if(groupContainer.is(":empty")) {
                groupContainer.html(CONTAINER_EMPTY_TEXT);
            }
        }
    });

    kendo.ui.plugin(Groupable);

})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        proxy = $.proxy,
        isFunction = $.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 ? "pageX" : "pageY";
            that._position = that.orientation == HORIZONTAL ? "left" : "top";
            that._sizingDom = that.orientation == HORIZONTAL ? "outerWidth" : "outerHeight";

            new ui.Draggable(element, {
                distance: 0,
                filter: options.handle,
                drag: proxy(that._resize, that),
                dragstart: proxy(that._start, that),
                dragend: proxy(that._stop, that)
            });
        },

        events: [
            RESIZE,
            RESIZEEND,
            START
        ],

        options: {
            name: "Resizable",
            orientation: HORIZONTAL
        },
        _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._initialMousePosition = e[that._positionMouse];
            that._initialElementPosition = el.position()[that._position];

            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,
                handle = $(e.currentTarget),
                maxPosition = that._maxPosition,
                minPosition = that._minPosition,
                currentPosition = that._initialElementPosition + (e[that._positionMouse] - 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.trigger(RESIZE, extend(e, { position: position }));
        },
        _stop: function(e) {
            var that = this;

            if(that.hint) {
                that.hint.remove();
            }

            that.trigger(RESIZEEND, extend(e, { position: that.position }));
            $(document.body).css("cursor", "");
        }
    });

    kendo.ui.plugin(Resizable);

})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        proxy = $.proxy,
        DIR = "dir",
        ASC = "asc",
        SINGLE = "single",
        FIELD = "field",
        DESC = "desc",
        TLINK = ".k-link",
        Widget = kendo.ui.Widget;

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

            Widget.fn.init.call(that, element, options);

            that.dataSource = that.options.dataSource.bind("change", proxy(that.refresh, that));
            link = that.element.find(TLINK);

            if (!link[0]) {
                link = that.element.wrapInner('<a class="k-link" href="#"/>').find(TLINK);
            }

            that.link = link;
            that.element.click(proxy(that._click, that));
        },

        options: {
            name: "Sortable",
            mode: SINGLE,
            allowUnsort: true
        },

        refresh: function() {
            var that = this,
                sort = that.dataSource.sort() || [],
                idx,
                length,
                descriptor,
                dir,
                element = that.element,
                field = element.data(FIELD);

            element.removeData(DIR);

            for (idx = 0, length = sort.length; idx < length; idx++) {
               descriptor = sort[idx];

               if (field == descriptor.field) {
                   element.data(DIR, descriptor.dir);
               }
            }

            dir = element.data(DIR);

            element.find(".k-arrow-up,.k-arrow-down").remove();

            if (dir === ASC) {
                $('<span class="k-icon k-arrow-up" />').appendTo(that.link);
            } else if (dir === DESC) {
                $('<span class="k-icon k-arrow-down" />').appendTo(that.link);
            }
        },

        _click: function(e) {
            var that = this,
                element = that.element,
                field = element.data(FIELD),
                dir = element.data(DIR),
                options = that.options,
                sort = that.dataSource.sort() || [],
                idx,
                length;

            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 } ];
            } 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 });
            }

            e.preventDefault();

            that.dataSource.sort(sort);
        }
    });

    kendo.ui.plugin(Sortable);
})(jQuery);
(function ($, undefined) {
    var kendo = window.kendo,
        keys = kendo.keys,
        touch = kendo.support.touch,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        MOUSEUP = touch? "touchend" : "mouseup",
        MOUSEDOWN = touch? "touchstart" : "mousedown",
        MOUSEMOVE = touch? "touchmove" : "mousemove",
        SELECTED = "k-state-selected",
        ACTIVE = "k-state-selecting",
        SELECTABLE = "k-selectable",
        SELECTSTART = "selectstart",
        DOCUMENT = $(document),
        CHANGE = "change",
        UNSELECTING = "k-state-unselecting";

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

            Widget.fn.init.call(that, element, options);

            that._marquee = $("<div class='k-marquee'></div>");
            that._lastActive = null;

            that._moveDelegate = proxy(that._move, that);
            that._upDelegate = proxy(that._up, that);

            that.element.addClass(SELECTABLE);
            that.element.delegate("." + SELECTABLE + " " + that.options.filter, MOUSEDOWN, proxy(that._down, that));
        },

        events: [CHANGE],

        options: {
            name: "Selectable",
            filter: ">*",
            multiple: false
        },
        _collide: function(element, marqueePos) {
            var pos = element.offset(),
                selectee = {
                    left: pos.left,
                    top: pos.top,
                    right: pos.left + element.outerWidth(),
                    bottom: pos.top + element.outerHeight()
                };
            return (!(selectee.left > marqueePos.right
                || selectee.right < marqueePos.left
                || selectee.top > marqueePos.bottom
                || selectee.bottom < marqueePos.top));
        },
        _position: function(event) {
            var pos = this._originalPosition,
                left = pos.x,
                top = pos.y,
                right = event.pageX,
                bottom = event.pageY;
            if (left > right) {
                var tmp = right;
                right = left;
                left = tmp;
            }
            if (top > bottom) {
                var tmp = bottom;
                bottom = top;
                top = tmp;
            }

            return {
                top: top,
                right: right,
                left: left,
                bottom: bottom
            };
        },
        _down: function (event) {
            var that = this,
                selected,
                ctrlKey = event.ctrlKey,
                shiftKey = event.shiftKey,
                single = !that.options.multiple;

            that._downTarget = $(event.currentTarget);
            that._shiftPressed = shiftKey;

            if (that._downTarget.closest("." + SELECTABLE)[0] !== that.element[0]) {
                return;
            }

            DOCUMENT
                .unbind(MOUSEUP, that._upDelegate) // more cancel friendly
                .bind(MOUSEUP, that._upDelegate);

            that._originalPosition = {
                x: event.pageX,
                y: event.pageY
            };

            if(!single && $(event.target).is(":not(:input)")) {
                DOCUMENT
                    .unbind(MOUSEMOVE, that._moveDelegate)
                    .bind(MOUSEMOVE, that._moveDelegate)
                    .unbind(SELECTSTART, false)
                    .bind(SELECTSTART, false);

                if (!kendo.support.touch) {
                    event.preventDefault();
                }
            }

            if (!single) {
                $("body").append(that._marquee);
                that._marquee.css({
                    left: event.clientX + 1,
                    top: event.clientY + 1,
                    width: 0,
                    height: 0
                });
            }

            selected = that._downTarget.hasClass(SELECTED);
            if(single || !(ctrlKey || shiftKey)) {
                that.element
                .find(that.options.filter + "." + SELECTED)
                .not(that._downTarget)
                .removeClass(SELECTED);
            }
            if(ctrlKey) {
                that._lastActive = that._downTarget;
            }

            if(selected && (ctrlKey || shiftKey)) {
                that._downTarget.addClass(SELECTED);
                if(!shiftKey) {
                    that._downTarget.addClass(UNSELECTING);
                }
            }
            else {
                if (!(kendo.support.touch && single)) {
                    that._downTarget.addClass(ACTIVE);
                }
            }
        },
        _move: function (event) {
            var that = this,
                pos = that._position(event),
                ctrlKey = event.ctrlKey,
                selectee, collide;

                that._marquee.css({
                    left: pos.left,
                    top: pos.top,
                    width: pos.right - pos.left,
                    height: pos.bottom - pos.top
                });

            that.element.find(that.options.filter).each(function () {
                selectee = $(this);
                collide = that._collide(selectee, pos);

                if (collide) {
                    if(selectee.hasClass(SELECTED)) {
                        if(that._downTarget[0] !== selectee[0] && ctrlKey) {
                            selectee
                                .removeClass(SELECTED)
                                .addClass(UNSELECTING);
                        }
                    } else if (!selectee.hasClass(ACTIVE) && !selectee.hasClass(UNSELECTING)) {
                        selectee.addClass(ACTIVE);
                    }
                }
                else {
                    if (selectee.hasClass(ACTIVE)) {
                        selectee.removeClass(ACTIVE);
                    }
                    else if(ctrlKey && selectee.hasClass(UNSELECTING)) {
                        selectee
                            .removeClass(UNSELECTING)
                            .addClass(SELECTED);
                    }
                }
            });
        },
        _up: function (event) {
            var that = this,
                options = that.options,
                single = !options.multiple;

            DOCUMENT
                .unbind(SELECTSTART, false)
                .unbind(MOUSEMOVE, that._moveDelegate)
                .unbind(MOUSEUP, that._upDelegate);

            if (!single) {
                that._marquee.remove();
            }

            if (kendo.support.touch && single)
                that._downTarget.addClass(ACTIVE);

            if(!single && that._shiftPressed === true) {
                that.selectRange(that._firstSelectee(), that._downTarget);
            }
            else {
                that.element
                    .find(options.filter + "." + UNSELECTING)
                    .removeClass(UNSELECTING)
                    .removeClass(SELECTED);

                that.value(that.element.find(options.filter + "." + ACTIVE));
            }
            if(!that._shiftPressed) {
                that._lastActive = that._downTarget;
            }
            that._downTarget = null;
            that._shiftPressed = false;
        },
        value: function(val) {
            var that = this,
            selectElement = proxy(that._selectElement, that);
            if(val) {
                val.each(function() {
                    selectElement(this);
                });

                that.trigger(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);
        },
        _selectElement: function(el) {
            var selecee = $(el),
                isPrevented = this.trigger("select", { element: el });

            selecee.removeClass(ACTIVE);
            if(!isPrevented) {
                selecee.addClass(SELECTED);
            }
        },
        clear: function() {
            var that = this;
            that.element
                .find(that.options.filter + "." + SELECTED)
                .removeClass(SELECTED);
        },
        selectRange: function(start, end) {
            var that = this,
                found = false,
                selectElement = proxy(that._selectElement, that),
                selectee;
            start = $(start)[0];
            end = $(end)[0];
            that.element.find(that.options.filter).each(function () {
                selectee = $(this);
                if(found) {
                    selectElement(this);
                    found = !(this === end);
                }
                else if(this === start) {
                    found = !(start === end);
                    selectElement(this);
                }
                else if(this === end) {
                    var tmp = start;
                    start = end;
                    end = tmp;
                    found = true;
                    selectElement(this);
                }
                else {
                    selectee.removeClass(SELECTED);
                }
            });
            that.trigger(CHANGE, {});
        }
    });

    kendo.ui.plugin(Selectable);

})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        proxy = $.proxy;

    function button(template, idx, text, numeric) {
        return template( {
            idx: idx,
            text: text,
            ns: kendo.ns,
            numeric: numeric
        });
    }

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

            Widget.fn.init.call(that, element, options);

            options = that.options;
            that.dataSource = options.dataSource;
            that.linkTemplate = kendo.template(that.options.linkTemplate);
            that.selectTemplate = kendo.template(that.options.selectTemplate);

            that._refreshHandler = proxy(that.refresh, that);

            that.dataSource.bind("change", that._refreshHandler);

            that.list = $('<ul class="k-pager k-reset k-numeric" />').appendTo(that.element);//.html(that.selectTemplate({ text: 1 }));
            that._clickHandler = proxy(that._click, that);

            that.element.delegate("a", "click", that._clickHandler);

            that.refresh();
        },

        destroy: function() {
            var that = this;

            that.element.undelegate("a", "click", that._clickHandler);
            that.dataSource.unbind("change", that._refreshHandler);
        },

        options: {
            name: "Pager",
            selectTemplate: '<li><span class="k-state-active">#=text#</span></li>',
            linkTemplate: '<li><a href="\\#" class="k-link" data-#=ns#page="#=idx#">#=text#</a></li>',
            buttonCount: 10
        },

        refresh: function() {
            var that = this,
                idx,
                end,
                start = 1,
                html = "",
                reminder,
                page = that.page(),
                totalPages = that.totalPages(),
                linkTemplate = that.linkTemplate,
                buttonCount = that.options.buttonCount;

            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);
            }

            for(idx = start; idx <= end; idx++) {
                html += button(idx == page ? that.selectTemplate : linkTemplate, idx, idx, true);
            }

            if(end < totalPages) {
                html += button(linkTemplate, idx, "...", false);
            }

            if (html === "") {
                html = that.selectTemplate({ text: 1 });
            }

            that.list.empty().append(html);
        },

        _click: function(e) {
            var page = $(e.currentTarget).attr(kendo.attr("page"));
            e.preventDefault();

            this.dataSource.page(page);

            this.trigger("change", { index: page });
        },

        totalPages: function() {
            return Math.ceil((this.dataSource.total() || 0) / this.pageSize());
        },

        pageSize: function() {
            return this.dataSource.pageSize() || this.dataSource.total();
        },

        page: function() {
            return this.dataSource.page() || 1;
        }
    });

    ui.plugin(Pager);
})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        support = kendo.support,
        touch = support.touch,
        getOffset = kendo.getOffset,
        appendingToBodyTriggersResize = $.browser.msie && $.browser.version < 9,
        OPEN = "open",
        CLOSE = "close",
        CENTER = "center",
        LEFT = "left",
        RIGHT = "right",
        TOP = "top",
        BOTTOM = "bottom",
        ABSOLUTE = "absolute",
        HIDDEN = "hidden",
        BODY = "body",
        LOCATION = "location",
        POSITION = "position",
        VISIBLE = "visible",
        FITTED = "fitted",
        EFFECTS = "effects",
        ACTIVE = "k-state-active",
        ACTIVEBORDER = "k-state-border",
        ACTIVECHILDREN = ".k-picker-wrap, .k-dropdown-wrap, .k-link",
        MOUSEDOWN = touch ? "touchstart" : "mousedown",
        DOCUMENT= $(document),
        WINDOW = $(window),
        DOCUMENT_ELEMENT = $(document.documentElement),
        RESIZE_SCROLL = "resize scroll",
        cssPrefix = support.transitions.css,
        TRANSFORM = cssPrefix + "transform",
        extend = $.extend,
        styles = ["font-family",
                   "font-size",
                   "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;

            Widget.fn.init.call(that, element, options);

            element = that.element;
            options = that.options;

            that.collisions = that.options.collision.split(" ");

            if (that.collisions.length === 1) {
                that.collisions.push(that.collisions[0]);
            }

            that.element.hide()
                .addClass("k-popup k-group k-reset")
                .css({ position : ABSOLUTE })
                .appendTo($(options.appendTo))
                .bind("mouseenter mouseleave", function(e) {
                    that._hovered = e.type === "mouseenter";
                });

            that.wrapper = $();

            if (options.animation === false) {
                options.animation = { open: { show: true, effects: {} }, close: { hide: true, effects: {} } };
            }

            if (!(EFFECTS in options.animation.close)) {
                options.animation.close = extend(true, { reverse: true }, options.animation.open, options.animation.close);
            }

            extend(options.animation.open, {
                complete: function() {
                    that.wrapper.css({ overflow: VISIBLE }); // Forcing refresh causes flickering in mobile.
                }
            });

            extend(options.animation.close, {
                complete: function() {
                    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.hasClass(ACTIVEBORDER + "-down") ? "down" : "up";
                        dirClass = ACTIVEBORDER + "-" + direction;

                        anchor
                            .removeClass(dirClass)
                            .children(ACTIVECHILDREN)
                            .removeClass(ACTIVE)
                            .removeClass(dirClass);

                        element.removeClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
                    }

                    that._closing = false;
                }
            });

            that._mousedownProxy = function(e) {
                that._mousedown(e);
            };

            that._currentWidth = DOCUMENT.width();

            that._resizeProxy = function(e) {
                that._resize(e);
            };

            if (options.toggleTarget) {
                $(options.toggleTarget).bind(options.toggleEvent, $.proxy(that.toggle, that));
            }
        },

        events: [
            OPEN,
            CLOSE
        ],

        options: {
            name: "Popup",
            toggleEvent: "click",
            origin: BOTTOM + " " + LEFT,
            position: TOP + " " + LEFT,
            anchor: BODY,
            appendTo: BODY,
            collision: "flip fit",
            animation: {
                open: {
                    effects: "slideIn:down",
                    transition: true,
                    duration: 200,
                    show: true
                },
                close: { // if close animation effects are defined, they will be used instead of open.reverse
                    duration: 100,
                    show: false,
                    hide: true
                }
            }
        },

        open: function() {
            var that = this,
                element = that.element,
                options = that.options,
                direction = "down",
                animation, wrapper,
                anchor = $(options.anchor),
                style,
                idx;

            if (!that.visible()) {
                for (idx = 0; idx < styles.length; idx++) {
                    style = styles[idx];
                    element.css(style, anchor.css(style));
                }

                if (element.data("animating") || that.trigger(OPEN)) {
                    return;
                }

                DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy)
                                .bind(MOUSEDOWN, that._mousedownProxy);
                if (!touch) {
                    WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy)
                          .bind(RESIZE_SCROLL, that._resizeProxy)
                }

                that.wrapper = wrapper = kendo.wrap(element)
                                        .css({
                                            overflow: HIDDEN,
                                            display: "block",
                                            position: ABSOLUTE
                                        });

                if (support.mobileOS.android) {
                    wrapper.add(anchor).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 == BODY) {
                    wrapper.css(TOP, "-10000px");
                }

                animation = extend(true, {}, options.animation.open);
                animation.effects = kendo.parseEffects(animation.effects, that._update());
                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() {
            var that = this,
                options = that.options,
                animation,
                effects;

            if (that.visible()) {
                if (that._closing || that.trigger(CLOSE)) {
                    return;
                }

                DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy);
                WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);

                animation = extend(true, {}, options.animation.close);
                effects = that.element.data(EFFECTS);

                that.wrapper = kendo.wrap(that.element).css({ overflow: HIDDEN });

                if (effects) {
                    animation.effects = effects;
                }

                that._closing = true;

                that.element.kendoStop(true).kendoAnimate(animation);
            }
        },

        _resize: function(e) {
            var that = this;

            if (appendingToBodyTriggersResize) {
                var width = DOCUMENT.width();
                if (width == that._currentWidth) {
                    return;
                }
                that._currentWidth = width;
            }

            if (!that._hovered) {
                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")[0];

            if (popup && popup !== that.element[0] ){
                return;
            }

            if (!contains(container, target) && !contains(anchor, target) && !(toggleTarget && contains($(toggleTarget)[0], target))) {
                that.close();
            }
        },

        _update: function() {
            return this._position($(window));
        },

        _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;
        },

        _position: function(viewport) {
            var that = this,
                element = that.element,
                wrapper = that.wrapper,
                options = that.options,
                anchor = $(options.anchor),
                origins = options.origin.toLowerCase().split(" "),
                positions = options.position.toLowerCase().split(" "),
                collisions = that.collisions,
                zoomLevel = support.zoomLevel(),
                zIndex = 10002;

            var siblingContainer = anchor.parents().filter(wrapper.siblings());

            if (siblingContainer[0]) {
                var parentZIndex = Number($(siblingContainer).css("zIndex"));
                if (parentZIndex) {
                    zIndex = parentZIndex + 1;
                }
            }

            wrapper.css("zIndex", zIndex);

            if (options.appendTo === Popup.fn.options.appendTo) {
                wrapper.css(that._align(origins, positions));
            } else {
                wrapper.css(that._align(origins, positions, true));
            }

            var pos = getOffset(wrapper, POSITION),
                offset = getOffset(wrapper),
                anchorParent = anchor.offsetParent().parent(".k-animation-container"); // If the parent is positioned, get the current positions

            if (anchorParent.length && anchorParent.data(FITTED)) {
                pos = getOffset(wrapper, POSITION);
                offset = getOffset(wrapper);
            }

            offset = {
                top: offset.top - (window.pageYOffset || document.documentElement.scrollTop || 0),
                left: offset.left - (window.pageXOffset || document.documentElement.scrollLeft || 0)
            };

            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(), viewport.height() / zoomLevel);
            }

            if (collisions[1] === "fit") {
                location.left += that._fit(offsets.left, wrapper.outerWidth(), viewport.width() / zoomLevel);
            }

            if (location.left != pos.left || location.top != pos.top) {
                wrapper.data(FITTED, true);
            } else {
                wrapper.removeData(FITTED);
            }

            var flipPos = extend({}, location);

            if (collisions[0] === "flip") {
                location.top += that._flip(offsets.top, element.outerHeight(), anchor.outerHeight(), viewport.height() / zoomLevel, origins[0], positions[0], wrapper.outerHeight())
            }

            if (collisions[1] === "flip") {
                location.left += that._flip(offsets.left, element.outerWidth(), anchor.outerWidth(), viewport.width() / zoomLevel, origins[1], positions[1], wrapper.outerWidth());
            }

            wrapper.css(location);

            return (location.left != flipPos.left || location.top != flipPos.top);
        },

        _align: function(origin, position, isPosition) {
            var that = this,
                element = that.wrapper,
                anchor = $(that.options.anchor),
                verticalOrigin = origin[0],
                horizontalOrigin = origin[1],
                verticalPosition = position[0],
                horizontalPosition = position[1],
                anchorOff = getOffset(anchor),
                appendOff = getOffset($(that.options.appendTo)),
                anchorOffset = isPosition ? { top: anchorOff.top - appendOff.top , left: anchorOff.left - appendOff.left } : anchorOff,
                width = element.outerWidth(),
                height = element.outerHeight(),
                anchorWidth = anchor.outerWidth(),
                anchorHeight = anchor.outerHeight(),
                top = anchorOffset.top,
                left = anchorOffset.left,
                round = Math.round,
                verticalFlip = verticalOrigin !== verticalPosition,
                horizontalflip = horizontalOrigin !== horizontalPosition;


            if (verticalOrigin === TOP && isPosition && verticalFlip) {
                top -= height;
            }

            if (verticalOrigin === BOTTOM && !isPosition) {
                top += anchorHeight;
            }

            if (verticalOrigin === CENTER) {
                top += round(anchorHeight / 2);
            }

            if (verticalPosition === TOP && isPosition && verticalFlip) {
                top += anchorHeight;
            }

            if (verticalPosition === BOTTOM && !isPosition) {
                top -= height;
            }

            if (verticalPosition === CENTER) {
                top -= round(height / 2);
            }

            if (horizontalOrigin === LEFT && isPosition && horizontalflip) {
                left -= width;
            }

            if (horizontalOrigin === RIGHT && !isPosition) {
                left += anchorWidth;
            }

            if (horizontalOrigin === CENTER) {
                left += round(anchorWidth / 2);
            }

            if (horizontalPosition === LEFT && isPosition && horizontalflip) {
                left += anchorWidth;
            }

            if (horizontalPosition === RIGHT && !isPosition) {
                left -= width;
            }

            if (horizontalPosition === CENTER) {
                left -= round(width / 2);
            }

            return {
                top: top,
                left: left
            };
        }
    });

    ui.plugin(Popup);
})(jQuery);
(function($, undefined) {
    /**
    * @name kendo.ui.List.Description
    *
    * @section Common class for ComboBox, DropDownList and AutoComplete widgets.
    */
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        keys = kendo.keys,
        touch = kendo.support.touch,
        ID = "id",
        LI = "li",
        CLICK = touch ? "touchend" : "click",
        CHANGE = "change",
        CHARACTER = "character",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        LOADING = "k-loading",
        OPEN = "open",
        CLOSE = "close",
        SELECT = "select",
        extend = $.extend,
        proxy = $.proxy,
        isIE8 = $.browser.msie && parseInt($.browser.version, 10) < 9;

    function contains(container, target) {
        return container === target || $.contains(container, target);
    }

    var List = Widget.extend(/** @lends kendo.ui.List */{
        /**
         * Creates a List instance.
         * @constructs
         * @extends kendo.ui.Widget
         */
        init: function(element, options) {
            var that = this, id, list;

            Widget.fn.init.call(that, element, options);

            that._template();

            that.ul = $('<ul unselectable="on" class="k-list k-reset"/>')
                        .css({ overflow: kendo.support.touch ? "": "auto" })
                        .delegate(LI, "mouseenter", function() { $(this).addClass(HOVER); })
                        .delegate(LI, "mouseleave", function() { $(this).removeClass(HOVER); })
                        .delegate(LI, CLICK, proxy(that._click, that));

            that.list = list = $("<div class='k-list-container'/>")
                        .append(that.ul)
                        .mousedown(function(e) {
                            e.preventDefault();
                        });

            id = that.element.attr(ID);
            if (id) {
                list.attr(ID, id + "-list")
            }

            $(document.documentElement).bind("mousedown", proxy(that._mousedown, that));
        },

        items: function() {
            return this.ul[0].children;
        },

        current: function(candidate) {
            var that = this;

            if (candidate !== undefined) {
                if (that._current) {
                    that._current.removeClass(FOCUSED);
                }

                if (candidate) {
                    candidate.addClass(FOCUSED);
                    that._scroll(candidate);
                }

                that._current = candidate;
            } else {
                return that._current;
            }
        },

        dataItem: function(index) {
            var that = this;

            if (index === undefined) {
                index = that.selectedIndex;
            }

            return that.dataSource.view()[index];
        },

        _accessors: function() {
            var that = this,
                element = that.element,
                options = that.options,
                getter = kendo.getter,
                textField = element.attr(kendo.attr("text-field")),
                valueField = element.attr(kendo.attr("value-field"));

            if (textField) {
                options.dataTextField = textField;
            }

            if (valueField) {
                options.dataValueField = valueField;
            }

            that._text = getter(options.dataTextField);
            that._value = getter(options.dataValueField);
        },

        _blur: function() {
            var that = this;

            that._change();
            that.close();
        },

        _change: function() {
            var that = this,
                index = that.selectedIndex,
                value = that.value(),
                trigger;

            if (value !== that._old) {
                trigger = true;
            } else if (index !== undefined && index !== that._oldIndex) {
                trigger = true;
            }

            if (trigger) {
                that._old = value;
                that._oldIndex = index;

                that.trigger(CHANGE);

                // trigger the DOM change event so any subscriber gets notified
                that.element.trigger(CHANGE);
            }
        },

        _click: function(e) {
            if (!e.isDefaultPrevented()) {
                this._accept($(e.currentTarget));
            }
        },

        _enable: function() {
            var that = this,
                options = that.options;

            if (that.element.prop("disabled")) {
                options.enable = false;
            }

            that.enable(options.enable);
        },

        _focus: function(li) {
            var that = this;

            if (that.popup.visible() && that.trigger(SELECT, {item: li})) {
                that.close();
                return;
            }

            that._select(li);
            that._blur();
        },

        _height: function(length) {
            if (length) {
                var that = this,
                    list = that.list,
                    visible = that.popup.visible(),
                    height = that.options.height;

                list = list.add(list.parent(".k-animation-container")).show()
                           .height(that.ul[0].scrollHeight > height ? height : "auto");

                if (!visible) {
                    list.hide();
                }
            }
        },

        _popup: function() {
            var that = this,
                list = that.list,
                options = that.options,
                wrapper = that.wrapper,
                computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0,
                computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth(),
                width;

            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);
            }

            that.popup = new ui.Popup(list, extend({}, options.popup, {
                anchor: wrapper,
                open: function(e) {
                    if (that.trigger(OPEN)) {
                        e.preventDefault();
                    }
                },
                close: function(e) {
                    if (that.trigger(CLOSE)) {
                        e.preventDefault();
                    }
                },
                animation: options.animation
            }));

            width = computedWidth - (list.outerWidth() - list.width());

            list.css({
                fontFamily: wrapper.css("font-family"),
                width: width
            });

            that._touchScroller = kendo.touchScroller(that.popup.element);
        },

        _makeUnselectable: function(element) {
            if (isIE8) {
                this.list.find("*").attr("unselectable", "on");
            }
        },

        _toggleHover: function(e) {
            if (!touch)
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
        },

        _toggle: function(open) {
            var that = this;
            open = open !== undefined? open : !that.popup.visible();

            if (!touch && that._focused[0] !== document.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;

            ul.scrollTop = ulScrollTop > itemOffsetTop
                        ? itemOffsetTop
                        : bottomDistance > (ulScrollTop + ulOffsetHeight)
                        ? bottomDistance - ulOffsetHeight
                        : ulScrollTop;
        },

        _template: function() {
            var that = this,
                options = that.options,
                template = options.template,
                hasDataSource = options.dataSource;

            if (that.element.is(SELECT) && that.element[0].length) {
                if (!hasDataSource) {
                    options.dataTextField = options.dataTextField || "text";
                    options.dataValueField = options.dataValueField || "value";
                }
            }

            if (!template) {
                that.template = kendo.template("<li class='k-item'>${data" + (options.dataTextField ? "." : "") + options.dataTextField + "}</li>", { useWithBlock: false });
            } else {
                template = kendo.template(template);
                that.template = function(data) {
                    return "<li class='k-item'>" + template(data) + "</li>";
                };
            }
        }
    });

    extend(List, {
        caret: function(element) {
            var caret,
                selection = element.ownerDocument.selection;

            if (selection) {
                caret = Math.abs(selection.createRange().moveStart(CHARACTER, -element.value.length));
            } else {
                caret = element.selectionStart;
            }

            return caret;
        },

        selectText: function (element, selectionStart, selectionEnd) {
            if (element.createTextRange) {
                var textRange = element.createTextRange();
                textRange.collapse(true);
                textRange.moveStart(CHARACTER, selectionStart);
                textRange.moveEnd(CHARACTER, selectionEnd - selectionStart);
                textRange.select();
            } else {
                element.setSelectionRange(selectionStart, selectionEnd);
            }
        },
        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;

    /**
    * @name kendo.ui.Select.Description
    *
    * @section Common class for ComboBox and DropDownList widgets.
    */
    ui.Select = List.extend(/** @lends kendo.ui.Select */{
        /**
         * @extends kendo.ui.List
         * @constructs
         */
        init: function(element, options) {
            List.fn.init.call(this, element, options);
        },

        setDataSource: function(dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            this._selectItem();
        },

        /**
        * Closes the drop-down list.
        * @example
        * dropdownlist.close();
        *
        * @example
        * combobox.close();
        */
        close: function() {
            this.popup.close();
        },

        _accessor: function(value, idx) {
            var element = this.element,
                isSelect = element.is(SELECT),
                option, selectedIndex;

            element = element[0];

            if (value === undefined) {
                if (isSelect) {
                    selectedIndex = element.selectedIndex;

                    if (selectedIndex > -1) {
                        option = element.options[selectedIndex];

                        if (option) {
                            value = option.value;
                        }
                    }
                } else {
                    value = element.value;
                }
                return value;
            } else {
                if (isSelect) {
                    element.selectedIndex = idx;
                } else {
                    element.value = value;
                }
            }
        },

        _hideBusy: function () {
            var that = this;
            clearTimeout(that._busy);
            that._arrow.removeClass(LOADING);
        },

        _showBusy: function () {
            var that = this;

            if (that._busy) {
                return;
            }

            that._busy = setTimeout(function () {
                that._arrow.addClass(LOADING);
            }, 100);
        },

        _data: function() {
            return this.dataSource.view();
        },

        _dataSource: function() {
            var that = this,
                element = that.element,
                options = that.options,
                dataSource = options.dataSource || {},
                idx;

            dataSource = $.isArray(dataSource) ? {data: dataSource} : dataSource;

            if (element.is(SELECT)) {
                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.dataSource.unbind(CHANGE, that._refreshHandler)
                               .unbind("requestStart", that._requestStartHandler);
            } else {
                that._refreshHandler = proxy(that.refresh, that);
                that._requestStartHandler = proxy(that._showBusy, that);
            }

            that.dataSource = kendo.data.DataSource.create(dataSource)
                                   .bind(CHANGE, that._refreshHandler)
                                   .bind("requestStart", that._requestStartHandler);
        },

        _index: function(value) {
            var that = this,
                idx,
                length,
                data = that._data(),
                valueFromData;

            for (idx = 0, length = data.length; idx < length; idx++) {
                valueFromData = that._value(data[idx]);

                if (valueFromData === undefined) {
                    valueFromData = that._text(data[idx]);
                }

                if (valueFromData == value) {
                    return idx;
                }
            }

            return -1;
        },

        _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],
                current = that._current,
                down = key === keys.DOWN,
                pressed;

            if (key === keys.UP || down) {
                if (e.altKey) {
                    that.toggle(down);
                } else if (down) {
                    that._select(current ? current[0].nextSibling : ul.firstChild);
                    e.preventDefault();
                } else {
                    that._select(current ? current[0].previousSibling : ul.lastChild);
                    e.preventDefault();
                }
                pressed = true;
            } else if (key === keys.ENTER || key === keys.TAB) {

                if (that.popup.visible()) {
                    e.preventDefault();
                }

                that._accept(current);
                pressed = true;
            } else if (key === keys.ESC) {
                that.close();
                pressed = true;
            }

            return pressed;
        },

        _valueOnFetch: function(value) {
            var that = this;

            if (!that.ul[0].firstChild && !that._fetch) {
                that.dataSource.one(CHANGE, function() {
                    that._fetch = true;
                    that.value(value);
                }).fetch();

                return true;
            }

            that._fetch = false;
        },

        _options: function(data, optionLabel) {
            var that = this,
                element = that.element,
                selectedIndex = element[0].selectedIndex,
                value = that.value(),
                length = data.length,
                options = "",
                option,
                dataItem,
                dataText,
                dataValue,
                idx = 0;

            if (optionLabel) {
                options = optionLabel;
                idx = 1;
            }

            for (; idx < length; idx++) {
                option = "<option";
                dataItem = data[idx];
                dataText = that._text(dataItem);
                dataValue = that._value(dataItem);

                if (dataValue !== undefined) {
                    option += ' value="' + dataValue + '"';
                }

                option += ">";

                if (dataText !== undefined) {
                    option += dataText;
                }

                option += "</option>";
                options += option;
            }

            element.html(options);
            element[0].selectedIndex = selectedIndex;
        },

        _reset: function() {
            var that = this,
                element = that.element;

            element.closest("form")
                   .bind("reset", function() {
                       setTimeout(function() {
                            that.value(element[0].value);
                       });
                   });
        }
    });

})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.ui.Calendar.Description
     *
     * @section
     * <p>
     *  The <b>Calendar</b> renders a graphical calendar that supports
     *  navigation and selection. It supports custom templates for its
     *  "month" view, configurable options for a minimum and maximum date,
     *  start view and the depth of the navigation.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Create a div element
     * @example
     * <div id="calendar"></div>
     *
     * @exampleTitle Initialize the Calendar via a jQuery ID selector
     * @example
     * $(document).ready(function(){
     *  $("#calendar").kendoCalendar();
     * });
     *
     * @section
     * <p>
     *  When a <b>Calendar</b> is initialized, it will automatically be
     *  displayed near the location of the used HTML element.
     * </p>
     * <h3>Configuring Calendar Behaviors</h3>
     * <p>
     *  The <b>Calendar</b> provides many configuration options that can be
     *  easily set during initialization. Among the properties that can be
     *  controlled:
     * </p>
     * <ul>
     *  <li>Selected date</li>
     *  <li>Minimum and/or maximum date</li>
     *  <li>Start view</li>
     *  <li>
     *   Define the navigation depth (last view to which end user can
     *   navigate)
     *  </li>
     *  <li>Day template</li>
     *  <li>Footer template</li>
     * </ul>
     *
     * @exampleTitle Create Calendar with selected date and a defined minimum
     * and maximum date
     * @example
     * $("#calendar").kendoCalendar({
     *  value: new Date(),
     *  min: new Date(1950, 0, 1),
     *  max: new Date(2049, 11, 31)
     * });
     *
     * @section
     * <p>
     *  The <b>Calendar</b> will not navigate before than the minimum
     *  date specified. It will also not navigate ahead the maximum date
     *  specified.
     * </p>
     * @section
     * <h3>Define start view and navigation depth</h3>
     * <p>
     *  The first rendered view can be defined with "start" option.
     *  Navigation depth can be controlled with "depth" option. Predefined
     *  views are:
     *  <ul>
     *   <li>"month" - shows the days from the month</li>
     *   <li>"year" - shows the months of the year</li>
     *   <li>"decade" - shows the years from the decade</li>
     *   <li>"century" - shows the decades from the century</li>
     *  </ul>
     * </p>
     *
     * @exampleTitle Create a Calendar, which allows a user to select a month
     * @example
     * $("#calendar").kendoCalendar({
     *  start: "year",
     *  depth: "year"
     * });
     *
     * @section
     * <h3>Customize day template</h3>
     * <p>
     *  The <b>Calendar</b> allows to customize content of the rendered day
     *  in the "month" view.
     * </p>
     *
     * @exampleTitle Create a Calendar with custom template
     * @example
     * $("#calendar").kendoCalendar({
     *  month: {
     *   content: '<div class="custom"><#=data.value#></div>'
     *  }
     * });
     *
     * @section
     * <p>
     *  This templates wraps the "value" in a div HTML element. Here is an
     *  example of the object passed to the template function:
     * </p>
     *
     * @exampleTitle Structure of the data object passed to the template
     * @example
     * data = {
     *  date: date, // Date object corresponding to the current cell
     *  title: kendo.toString(date, "D"),
     *  value: date.getDate(),
     *  dateString: "2011/0/1" // formatted date using yyyy/MM/dd format and month is zero-based
     * };
     *
     * @section
     * <h3>Accessing an Existing Calendar</h3>
     * <p>
     *  You can reference an existing <b>Calendar</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Calendar instance
     * @example
     * var calendar = $("#calendar").data("kendoCalendar");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        parse = kendo.parseDate,
        template = kendo.template,
        touch = kendo.support.touch,
        transitions = kendo.support.transitions,
        transitionOrigin = transitions ? transitions.css + "transform-origin" : "",
        cellTemplate = template('<td#=data.cssClass#><a class="k-link" href="\\#" data-#=data.ns#value="#=data.dateString#">#=data.value#</a></td>', { useWithBlock: false }),
        emptyCellTemplate = template("<td>&nbsp;</td>", { useWithBlock: false }),
        isIE8 = $.browser.msie && parseInt($.browser.version, 10) < 9,
        CLICK = touch ? "touchend" : "click",
        MIN = "min",
        LEFT = "left",
        SLIDE = "slide",
        MONTH = "month",
        CENTURY = "century",
        CHANGE = "change",
        NAVIGATE = "navigate",
        VALUE = "value",
        HOVER = "k-state-hover",
        DISABLED = "k-state-disabled",
        OTHERMONTH = "k-other-month",
        OTHERMONTHCLASS = ' class="' + OTHERMONTH + '"',
        TODAY = "k-nav-today",
        CELLSELECTOR = "td:has(.k-link)",
        MOUSEENTER = touch ? "touchstart" : "mouseenter",
        MOUSELEAVE = touch ? "touchend" : "mouseleave",
        MS_PER_MINUTE = 60000,
        MS_PER_DAY = 86400000,
        PREVARROW = "_prevArrow",
        NEXTARROW = "_nextArrow",
        proxy = $.proxy,
        extend = $.extend,
        DATE = Date,
        views = {
            month: 0,
            year: 1,
            decade: 2,
            century: 3
        };

    var Calendar = Widget.extend(/** @lends kendo.ui.Calendar.prototype */{
        /**
          * @constructs
          * @extends kendo.ui.Widget
          * @param {DomElement} element DOM element
          * @param {Object} options Configuration options.
          * @option {Date} [value] <null> Specifies the selected date.
          * _example
          * // set the selected date to Jan 1st. 2012
          * $("#calendar").kendoCalendar({
          *     value: new Date(2012, 0, 1)
          * });
          * _exampleTitle To set after initialization
          * _example
          * // get a reference to the Kendo UI calendar widget
          * var calendar = $("#calendar").data("kendoCalendar");
          * // set the selected date on the calendar to Jan 1st, 2012
          * calendar.value(new Date(2012, 0, 1));
          * @option {Date} [min] <Date(1900, 0, 1)> Specifies the minimum date, which the calendar can show.
          * _example
          * // set the min date to Jan 1st, 2011
          * $("#calendar").kendoCalendar({
          *     min = new Date(2011, 0, 1)
          * });
          * _exampleTitle To set after initialization
          * _example
          * // get a reference to the Kendo UI calendar widget
          * var calendar = $("#calendar").data("kendoCalendar");
          * // set the min date to Jan 1st, 2011
          * calendar.min(new Date(2011, 0, 1));
          * @option {Date} [max] <Date(2099, 11, 31)> Specifies the maximum date, which the calendar can show.
          * _example
          * $("#calendar").kendoCalendar({
          *     max = new Date(2013, 0, 1);
          * });
          * _exampleTitle To set after initialization
          * _example
          * // get a reference to the Kendo UI calendar widget
          * var calendar = $("#calendar").data("kendoCalendar");
          * // set the max date to Jan 1st, 2013
          * calendar.max(new Date(2013, 0, 1));
          * @option {String} [footer] <> Specifies the content of the footer. If false, the footer will not be rendered.
          * _example
          * // change the footer text from the default current date
          * $("#calendar").kendoCalendar({
          *     footer = "My Custom Footer"
          * });
          * _exampleTitle Hide the footer
          * _example
          * $("#calendar").kendoCalendar({
          *     footer = false;
          * });
          * @option {String} [format] <MM/dd/yyyy> Specifies the format, which is used to parse value set with value() method.
          * _example
          * $("#calendar").kendoCalendar({
          *     format: "yyyy/MM/dd"
          * });
          * @option {String} [start] <month> Specifies the start view.
          * _example
          * $("#calendar").kendoCalendar({
          *     start: "year"
          * });
          * @option {String} [depth] Specifies the navigation depth.
          * _example
          * $("#calendar").kendoCalendar({
          *     depth: "year"
          * });
          * @option {Function} [footer] <> Template to be used for rendering the footer. If false, the footer will not be rendered.
          * _example
          *
          *  //calendar intialization
          *  &lt;script&gt;
          *      $("#calendar").kendoCalendar({
          *          footer: kendo.template("Today - #=kendo.toString(data, 'd') #")
          *      });
          *  &lt;/script&gt;
          * @option {Object} [month] <> Templates for the cells rendered in the "month" view.
          * @option {Function} [month.content] <> Template to be used for rendering the cells in the "month" view, which are in range.
          * _example
          *  //template
          *  &lt;script id="cellTemplate" type="text/x-kendo-tmpl"&gt;
          *      &lt;div class="${ data.value < 10 ? exhibition : party }"&gt;
          *      &lt;/div&gt;
          *      ${ data.value }
          *  &lt;/script&gt;
          *
          *  //calendar intialization
          *  &lt;script&gt;
          *      $("#calendar").kendoCalendar({
          *          month: {
          *             content:  kendo.template($("#cellTemplate").html()),
          *          }
          *      });
          *  &lt;/script&gt;
          * @option {Function} [month.empty] <> Template to be used for rendering the cells in the "month" view, which are not in the min/max range.
          */
        init: function(element, options) {
            var that = this, value;

            Widget.fn.init.call(that, element, options);

            element = that.wrapper = that.element;
            options = that.options;

            element.addClass("k-widget k-calendar");

            that._templates();

            that._header();

            that._footer(that.footer);

            element
                .delegate(CELLSELECTOR, MOUSEENTER, mouseenter)
                .delegate(CELLSELECTOR, MOUSELEAVE, mouseleave)
                .delegate(CELLSELECTOR, CLICK, proxy(that._click, that));

            value = options.value;
            validate(options);

            that._index = views[options.start];
            that._current = new DATE(restrictValue(value, options.min, options.max));

            that.value(value);

            kendo.notify(that);
        },

        options: {
            name: "Calendar",
            value: null,
            min: new DATE(1900, 0, 1),
            max: new DATE(2099, 11, 31),
            footer : '#= kendo.toString(data,"D") #',
            format : "",
            month : {},
            start: MONTH,
            depth: MONTH,
            animation: {
                horizontal: {
                    effects: SLIDE,
                    duration: 500,
                    divisor: 2
                },
                vertical: {
                    effects: "zoomIn",
                    duration: 400
                }
            }
        },

        events: [
            /**
            * Fires when the selected date is changed
            * @name kendo.ui.Calendar#change
            * @event
            * @param {Event} e
            * @example
            * $("#calendar").kendoCalendar({
            *     change: function(e) {
            *         // handle event
            *     });
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to the Kendo UI calendar widget
            * var calendar = $("#calendar").data("kendoCalendar");
            * // bind to the change event
            * calendar.bind("change", function(e) {
            *      // handle event
            * });
            */
            /**
            * Fires when navigate
            * @name kendo.ui.Calendar#navigate
            * @event
            * @param {Event} e
            * @example
            * $("#calendar").kendoCalendar({
            *     navigate: function(e) {
            *          // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to the Kendo UI calendar widget
            * var calendar = $("#calendar").data("kendoCalendar");
            * // bind to the change event
            * calendar.bind("navigate", function(e) {
            *      // handle event
            * });
            */
            CHANGE,
            NAVIGATE
        ],

        /**
        * Gets/Sets the min value of the calendar.
        * @param {Date|String} value The min date to set.
        * @returns {Date} The min value of the calendar.
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        *
        * // get the min value of the calendar.
        * var min = calendar.min();
        *
        * // set the min value of the calendar.
        * calendar.min(new Date(1900, 0, 1));
        */
        min: function(value) {
            return this._option(MIN, value);
        },

        /**
        * Gets/Sets the max value of the calendar.
        * @param {Date | String} value The max date to set.
        * @returns {Date} The max value of the calendar.
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        *
        * // get the max value of the calendar.
        * var max = calendar.max();
        *
        * // set the max value of the calendar.
        * calendar.max(new Date(2100, 0, 1));
        */
        max: function(value) {
            return this._option("max", value);
        },

        /**
        * Navigates to the past
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        * // navigate to past
        * calendar.navigateToPast();
        */
        navigateToPast: function() {
            this._navigate(PREVARROW, -1);
        },

        /**
        * Navigates to the future
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        * // navigate to future
        * calendar.navigateToFuture();
        */
        navigateToFuture: function() {
            this._navigate(NEXTARROW, 1);
        },

        /**
        * Navigates to the upper view
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        * // navigate up
        * calendar.navigateUp();
        */
        navigateUp: function() {
            var that = this,
                index = that._index;

            if (that._title.hasClass(DISABLED)) {
                return;
            }

            that.navigate(that._current, ++index);
        },

        /**
        * Navigates to the lower view
        * @param {Date} value Desired date
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        * // navigate down
        * calendar.navigateDown(value);
        */
        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);
        },

        /**
        * Navigates to view
        * @param {Date} value Desired date
        * @param {String} view Desired view
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        * // navigate to the desired date
        * calendar.navigate(value, view);
        */
        navigate: function(value, view) {
            view = isNaN(view) ? views[view] : view;

            var that = this,
                options = that.options,
                min = options.min,
                max = options.max,
                title = that._title,
                from = that._table,
                selectedValue = that._value,
                currentValue = that._current,
                future = value && +value > +currentValue,
                vertical = view !== undefined && view !== that._index,
                to, currentView, compare;

            //do not navigate if the view is still animating
            if (from && from.parent().data("animating")) {
                return;
            }

            if (!value) {
                value = currentValue;
            } else {
                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;

            title.toggleClass(DISABLED, view === views[CENTURY])
            that[PREVARROW].toggleClass(DISABLED, compare(value, min) < 1);
            that[NEXTARROW].toggleClass(DISABLED, compare(value, max) > -1);

            if (!from || that._changeView) {
                title.html(currentView.title(value));

                that._table = to = $(currentView.content(extend({
                    min: min,
                    max: max,
                    date: value
                }, that[currentView.name])));

                makeUnselectable(to);

                that._animate({
                    from: from,
                    to: to,
                    vertical: vertical,
                    future: future
                });

                that.trigger(NAVIGATE);
            }

            if (view === views[options.depth] && selectedValue) {
                that._class("k-state-selected", currentView.toDateString(selectedValue));
            }

            that._changeView = true;
        },

        /**
        * Gets/Sets the value of the calendar.
        * @param {Date|String} value The date to set.
        * @returns {Date} The value of the calendar.
        * @example
        * // get a reference to the calendar widget
        * var calendar = $("#calendar").data("kendoCalendar");
        *
        * // get the value of the calendar.
        * var value = calendar.value();
        *
        * // set the value of the calendar.
        * calendar.value(new Date());
        */
        value: function(value) {
            var that = this,
            view = that._view,
            options = that.options,
            min = options.min,
            max = options.max;

            if (value === undefined) {
                return that._value;
            }

            value = parse(value, options.format);

            if (value !== null) {
                value = new DATE(value);

                if (!isInRange(value, min, max)) {
                    value = null;
                }
            }

            that._value = value;
            that._changeView = !value || view && view.compare(value, that._current) !== 0;

            that.navigate(value);
        },

        _animate: function(options) {
            var that = this,
                from = options.from,
                to = options.to;

            if (!from) {
                to.insertAfter(that.element[0].firstChild);
            } else if (!from.is(":visible") || that.options.animation === false) {
                to.insertAfter(from);
                from.remove();
            } else {
                that[options.vertical ? "_vertical" : "_horizontal"](from, to, options.future);
            }
        },

        _horizontal: function(from, to, future) {
            var that = this,
                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/>");

                    from.parent()
                    .css({
                        position: "relative",
                        width: viewWidth * 2,
                        "float": LEFT,
                        left: future ? 0 : -viewWidth
                    });

                    to[future ? "insertAfter" : "insertBefore"](from);

                    extend(horizontal, {
                        effects: SLIDE + ":" + (future ? LEFT : "right"),
                        complete: function() {
                            from.remove();
                            to.unwrap();
                        }
                    });

                    from.parent().kendoStop(true, true).kendoAnimate(horizontal);
                }
        },

        _vertical: function(from, to) {
            var that = this,
                vertical = that.options.animation.vertical,
                effects = vertical.effects,
                viewWidth = from.outerWidth(),
                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)) + "px" + " " + (position.top + parseInt(cell.height() / 2) + "px");
                    to.css(transitionOrigin, position);
                }

                from.kendoStop(true, true).kendoAnimate({
                    effects: "fadeOut",
                    duration: 600,
                    complete: function() {
                        from.remove();
                        to.css({
                            position: "static",
                            top: 0,
                            left: 0
                        });
                    }
                });

                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) {
            this._table.find("td:not(." + OTHERMONTH + ")")
                .removeClass(className)
                .filter(function() {
                   return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
                })
                .addClass(className);
        },

        _click: function(e) {
            var that = this,
                options = that.options,
                currentValue = that._current,
                link = $(e.currentTarget.firstChild),
                value = link.attr(kendo.attr(VALUE)).split("/");

            //Safari cannot create corretly date from "1/1/2090"
            value = new DATE(value[0], value[1], value[2]);

            e.preventDefault();

            if (link.parent().hasClass(OTHERMONTH)) {
                currentValue = value;
            } else {
                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("k-state-focused", view.toDateString(value));
        },

        _footer: function(template) {
            var that = this,
                element = that.element,
                today = new DATE(),
                footer = element.find(".k-footer"),
                link;

            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._toggle();
        },

        _header: function() {
            var that = this,
            element = that.element,
            links;

            if (!element.find(".k-header")[0]) {
                element.html('<div class="k-header">'
                           + '<a href="#" class="k-link k-nav-prev"><span class="k-icon k-arrow-prev"></span></a>'
                           + '<a href="#" class="k-link k-nav-fast"></a>'
                           + '<a href="#" class="k-link k-nav-next"><span class="k-icon k-arrow-next"></span></a>'
                           + '</div>');
            }

            links = element.find(".k-link")
                           .bind(MOUSEENTER, mouseenter)
                           .bind(MOUSELEAVE, mouseleave)
                           .click(false);

            that._title = links.eq(1).bind(CLICK, proxy(that.navigateUp, that));
            that[PREVARROW] = links.eq(0).bind(CLICK, proxy(that.navigateToPast, that));
            that[NEXTARROW] = links.eq(2).bind(CLICK, proxy(that.navigateToFuture, that));
        },

        _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,
                selectedValue = +that._value,
                bigger, navigate;

            if (value === undefined) {
                return options[option];
            }

            value = parse(value, options.format);

            if (!value) {
                return;
            }

            options[option] = new DATE(value);

            navigate = that._view.compare(value, that._current);

            if (option === MIN) {
                bigger = +value > selectedValue;
                navigate = navigate > -1
            } else {
                bigger = selectedValue > +value;
                navigate = navigate < 1;
            }

            if (bigger) {
                that.value(null);
            } else if (navigate) {
                that.navigate();
            }

            that._toggle();
        },

        _toggle: function(toggle) {
            var that = this,
                options = that.options,
                link = that._today;

            if (toggle === undefined) {
                toggle = isInRange(new DATE(), options.min, options.max);
            }

            if (link) {
                link.unbind(CLICK)

                if (toggle) {
                    link.addClass(TODAY)
                        .removeClass(DISABLED)
                        .bind(CLICK, proxy(that._todayClick, that));
                } else {
                    link.removeClass(TODAY)
                        .addClass(DISABLED)
                        .bind(CLICK, prevent);
                }
            }
        },

        _todayClick: function(e) {
            var that = this,
                depth = views[that.options.depth],
                today = new DATE();

            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#><a class="k-link" href="\\#" ' + kendo.attr("value") + '="#=data.dateString#" title="#=data.title#">' + (content || "#=data.value#") + '</a></td>', { useWithBlock: !!content }),
                empty: template("<td>" + (empty || "&nbsp;") + "</td>", { useWithBlock: !!empty })
            };

            if (footer) {
                that.footer = template(footer, { useWithBlock: false });
            }
        }
    });

    ui.plugin(Calendar);

    var calendar = {
        firstDayOfMonth: function (date) {
            return new DATE(
                date.getFullYear(),
                date.getMonth(),
                1
            );
        },

        firstVisibleDay: function (date) {
            var firstDay = kendo.culture().calendar.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) {
                return kendo.culture().calendar.months.names[date.getMonth()] + " " + date.getFullYear();
            },
            content: function(options) {
                var that = this,
                idx = 0,
                min = options.min,
                max = options.max,
                date = options.date,
                currentCalendar = kendo.culture().calendar,
                firstDayIdx = currentCalendar.firstDay,
                days = currentCalendar.days,
                names = shiftArray(days.names, firstDayIdx),
                abbr = shiftArray(days.namesAbbr, firstDayIdx),
                short = shiftArray(days.namesShort, firstDayIdx),
                start = calendar.firstVisibleDay(date),
                firstDayOfMonth = that.first(date),
                lastDayOfMonth = that.last(date),
                toDateString = that.toDateString,
                today = new DATE(),
                html = '<table class="k-content" cellspacing="0"><thead><tr>';

                for (; idx < 7; idx++) {
                    html += '<th abbr="' + abbr[idx] + '" scope="col" title="' + names[idx] + '">' + short[idx] + '</th>';
                }

                today = +new DATE(today.getFullYear(), today.getMonth(), today.getDate());

                return view({
                    cells: 42,
                    perRow: 7,
                    html: html += "</tr></thead><tbody><tr>",
                    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, idx) {
                        var cssClass = [],
                        day = date.getDay();

                        if (date < firstDayOfMonth || date > lastDayOfMonth) {
                            cssClass.push(OTHERMONTH);
                        }

                        if (+date === today) {
                            cssClass.push("k-today");
                        }

                        if (day === 0 || day === 6) {
                            cssClass.push("k-weekend");
                        }

                        return {
                            date: date,
                            ns: kendo.ns,
                            title: kendo.toString(date, "D"),
                            value: date.getDate(),
                            dateString: toDateString(date),
                            cssClass: cssClass[0] ? ' class="' + cssClass.join(" ") + '"' : ""
                        };
                    }
                });
            },
            first: function(date) {
                return calendar.firstDayOfMonth(date);
            },
            last: function(date) {
                return new DATE(date.getFullYear(), date.getMonth() + 1, 0);
            },
            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) {
                if (value instanceof DATE) {
                    date.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
                } else {
                    calendar.setTime(date, value * MS_PER_DAY);
                }
            },
            toDateString: function(date) {
                return date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate();
            }
        },
        {
            name: "year",
            title: function(date) {
                return date.getFullYear();
            },
            content: function(options) {
                var namesAbbr = kendo.culture().calendar.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) {
                if (value instanceof DATE) {
                    date.setFullYear(value.getFullYear(),
                    value.getMonth(),
                    date.getDate());
                } else {
                    var month = date.getMonth() + value;

                    date.setMonth(month);

                    if (month > 11) {
                        month -= 12;
                    }

                    if (month > 0 && date.getMonth() != month) {
                        date.setDate(0);
                    }
                }
            },
            toDateString: function(date) {
                return date.getFullYear() + "/" + date.getMonth() + "/1";
            }
        },
        {
            name: "decade",
            title: function(date) {
                var start = date.getFullYear();

                start = start - start % 10;

                return start + "-" + (start + 9);
            },
            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) {
                var start = date.getFullYear();

                start = start - start % 100;

                return start + "-" + (start + 99);
            },
            content: function(options) {
                var year = options.date.getFullYear(),
                minYear = options.min.getFullYear(),
                maxYear = options.max.getFullYear(),
                toDateString = this.toDateString;

                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 year = date.getFullYear();
                        return {
                            value: year + " - " + (year + 9),
                            ns: kendo.ns,
                            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 view(options) {
        var idx = 0,
            data,
            view = options.view,
            min = options.min,
            max = options.max,
            start = options.start,
            setter = options.setter,
            build = options.build,
            length = options.cells || 12,
            cellsPerRow = options.perRow || 4,
            toDateString = options.toDateString,
            content = options.content || cellTemplate,
            empty = options.empty || emptyCellTemplate,
            html = options.html || '<table class="k-content k-meta-view" cellspacing="0"><tbody><tr>';

        for(; idx < length; idx++) {
            if (idx > 0 && idx % cellsPerRow == 0) {
                html += "</tr><tr>";
            }

            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 restrictValue (value, min, max) {
        var today = new DATE();

        today = new DATE(today.getFullYear(), today.getMonth(), today.getDate());

        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 mouseenter() {
        $(this).addClass(HOVER);
    }

    function mouseleave() {
        $(this).removeClass(HOVER);
    }

    function prevent (e) {
        e.preventDefault();
    }

    function validate(options) {
        var start = views[options.start],
            depth = views[options.depth],
            format = options.format || kendo.culture().calendar.patterns.d;

        if (isNaN(start)) {
            start = 0;
            options.start = MONTH;
        }

        if (depth === undefined || depth > start) {
            options.depth = MONTH;
        }

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

        options.format = format;
    }

    function makeUnselectable(element) {
        if (isIE8) {
            element.find("*").attr("unselectable", "on");
        }
    }

    calendar.makeUnselectable =  makeUnselectable;
    calendar.restrictValue = restrictValue;
    calendar.isInRange = isInRange;
    calendar.validate = validate;
    calendar.viewsEnum = views;

    kendo.calendar = calendar;
})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.ui.DatePicker.Description
     *
     * @section
     * <p>
     *  The <b>DatePicker</b> allows the end user to select a date from a
     *  calendar or by direct input. It supports custom templates for "month"
     *  view, configurable options for min and max date, start view and the
     *  depth of the navigation.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Creating a DatePicker from existing input element
     * @example
     * <input id="datePicker" />
     *
     * @exampleTitle DatePicker initialization
     * @example
     * $(document).ready(function(){
     *  $("#datePicker").kendoDatePicker();
     * });
     *
     * @section
     * <p>
     *  When a <b>DatePicker</b> is initialized, it will be displayed at the
     *  location of the target HTML element.
     * </p>
     * <h3>Configuring DatePicker Behaviors</h3>
     * <p>
     *  The <b>DatePicker</b> provides configuration options that can be set
     *  during initialization. Among the properties that can be controlled:
     * </p>
     * <ul>
     *  <li>Selected date</li>
     *  <li>Minimum and/or maximum date</li>
     *  <li>Define format</li>
     *  <li>Start view</li>
     *  <li>Navigation depth (last view to which end user can navigate)</li>
     * </ul>
     *
     * @exampleTitle Create DatePicker with a selected date and a defined
     * minimum and maximum date
     * @example
     * $(document).ready(function(){
     *  $("#datePicker").kendoDatePicker({
     *   value: new Date(),
     *   min: new Date(1950, 0, 1),
     *   max: new Date(2049, 11, 31)
     *  })
     * });
     *
     * @section
     * <p>
     *  DatePicker will set the value only if the entered date is valid and
     *  within the defined range.
     * </p>
     * @section
     * <h3>Defining a Start View and Navigation Depth</h3>
     * <p>
     *  The first rendered view can be defined with "start" option.
     *  Navigation depth can be controlled with "depth" option. Predefined
     *  views are:
     * </p>
     * <ul>
     *  <li>"month" - shows the days from the month</li>
     *  <li>"year" - shows the months of the year</li>
     *  <li>"decade" - shows the years from the decade</li>
     *  <li>"century" - shows the decades from the century</li>
     * </ul>
     *
     * @exampleTitle Create a DatePicker for selecting a month
     * @example
     * $("#datePicker").kendoDatePicker({
     *  start: "year",
     *  depth: "year"
     * });
     *
     * @section
     * <h3>Accessing an Existing DatePicker</h3>
     * <p>
     *  You can reference an existing <b>DatePicker</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing DatePicker instance
     * @example
     * var datePicker = $("#datePicker").data("kendoDatePicker");
     *
     */
    var kendo = window.kendo,
    ui = kendo.ui,
    touch = kendo.support.touch,
    Widget = ui.Widget,
    parse = kendo.parseDate,
    keys = kendo.keys,
    template = kendo.template,
    DIV = "<div />",
    SPAN = "<span />",
    CLICK = (touch ? "touchend" : "click"),
    CLICK_DATEPICKER = CLICK + ".datepicker",
    OPEN = "open",
    CLOSE = "close",
    CHANGE = "change",
    NAVIGATE = "navigate",
    DATEVIEW = "dateView",
    DISABLED = "disabled",
    DEFAULT = "k-state-default",
    FOCUSED = "k-state-focused",
    SELECTED = "k-state-selected",
    STATEDISABLED = "k-state-disabled",
    HOVER = "k-state-hover",
    HOVEREVENTS = "mouseenter mouseleave",
    MOUSEDOWN = (touch ? "touchstart" : "mousedown"),
    MIN = "min",
    MAX = "max",
    MONTH = "month",
    FIRST = "first",
    calendar = kendo.calendar,
    views = calendar.viewsEnum,
    isInRange = calendar.isInRange,
    restrictValue = calendar.restrictValue,
    extend = $.extend,
    proxy = $.proxy,
    DATE = Date;

    var DateView = function(options) {
        var that = this,
            body = document.body,
            sharedCalendar = DatePicker.sharedCalendar;

        if (!sharedCalendar) {
            sharedCalendar = DatePicker.sharedCalendar = new ui.Calendar($(DIV).hide().appendTo(body));
            calendar.makeUnselectable(sharedCalendar.element);
        }

        that.calendar = sharedCalendar;
        that.options = options = options || {};
        that.popup = new ui.Popup($(DIV).addClass("k-calendar-container").appendTo(body), extend(options.popup, options));

        that._templates();

        that.value(options.value);
    };

    DateView.prototype = {
        _calendar: function() {
            var that = this,
                popup = that.popup,
                options = that.options,
                calendar = that.calendar,
                element = calendar.element;

            if (element.data(DATEVIEW) !== that) {

                element.appendTo(popup.element)
                       .data(DATEVIEW, that)
                       .undelegate(CLICK_DATEPICKER)
                       .delegate("td:has(.k-link)", CLICK_DATEPICKER, proxy(that._click, that))
                       .unbind(MOUSEDOWN)
                       .bind(MOUSEDOWN, function(e) { e.preventDefault(); })
                       .show();

                calendar.unbind(CHANGE)
                        .bind(CHANGE, options);

                if (!touch) {
                    calendar.unbind(NAVIGATE)
                            .bind(NAVIGATE, proxy(that._navigate, that))
                }

                calendar.month = that.month;
                calendar.options.depth = options.depth;

                calendar._footer(that.footer);

                calendar.min(options.min);
                calendar.max(options.max);

                calendar.navigate(that._value, options.start);
                that.value(that._value);
            }
        },

        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,
                options = that.options,
                min = options.min,
                max = options.max,
                currentValue = new DATE(that._current),
                calendar = that.calendar,
                index = calendar._index,
                view = calendar._view,
                key = e.keyCode,
                dateString, value, prevent, method;

            if (key == keys.ESC) {
                that.close();
                return;
            }

            if (e.altKey) {
                if (key == keys.DOWN) {
                    that.open();
                    prevent = true;
                } else if (key == keys.UP) {
                    that.close();
                    prevent = true;
                }
                return;
            }

            if (!that.popup.visible() || calendar._table.parent().data("animating")) {
                return;
            }

            if (e.ctrlKey) {
                if (key == keys.RIGHT) {
                    calendar.navigateToFuture();
                    prevent = true;
                } else if (key == keys.LEFT) {
                    calendar.navigateToPast();
                    prevent = true;
                } else if (key == keys.UP) {
                    calendar.navigateUp();
                    prevent = true;
                } else if (key == keys.DOWN) {
                    that._navigateDown();
                    prevent = true;
                }
            } else {
                if (key == keys.RIGHT) {
                    value = 1;
                    prevent = true;
                } else if (key == keys.LEFT) {
                    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._navigateDown();
                    prevent = true;
                } else if (key == keys.HOME || key == keys.END) {
                    method = key == keys.HOME ? FIRST : "last";
                    currentValue = view[method](currentValue);
                    prevent = true;
                } else if (key == keys.PAGEUP) {
                    prevent = true;
                    calendar.navigateToPast();
                } else if (key == keys.PAGEDOWN) {
                    prevent = true;
                    calendar.navigateToFuture();
                }

                if (value || method) {
                    if (!method) {
                        view.setDate(currentValue, value);
                    }

                    that._current = currentValue = restrictValue(currentValue, options.min, options.max);
                    calendar._focus(currentValue);
                }
            }

            if (prevent) {
                e.preventDefault();
            }
        },

        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.element.data(DATEVIEW) === that) {
                calendar._focus(that._current);
                calendar.value(value);
            }
        },

        _click: function(e) {
            if (e.currentTarget.className.indexOf(SELECTED) !== -1) {
                this.close();
            }
        },

        _navigate: function() {
            var that = this,
                calendar = that.calendar;

            that._current = new DATE(calendar._current);
            calendar._focus(calendar._current);
        },

        _navigateDown: function() {
            var that = this,
                calendar = that.calendar,
                currentValue = calendar._current,
                cell = calendar._table.find("." + FOCUSED),
                value = cell.children(":" + FIRST).attr(kendo.attr("value")).split("/");

            //Safari cannot create corretly date from "1/1/2090"
            value = new DATE(value[0], value[1], value[2]);

            if (!cell[0] || cell.hasClass(SELECTED)) {
                that.close();
                return;
            }

            calendar._view.setDate(currentValue, value);
            calendar.navigateDown(currentValue);
        },

        _option: function(option, value) {
            var that = this,
                options = that.options,
                calendar = that.calendar;

            options[option] = value;

            if (calendar.element.data(DATEVIEW) === that) {
                calendar[option](value);
            }
        },

        _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#><a class="k-link" href="\\#" ' + kendo.attr("value") + '="#=data.dateString#" title="#=data.title#">' + (content || "#=data.value#") + '</a></td>', { useWithBlock: !!content }),
                empty: template("<td>" + (empty || "&nbsp;") + "</td>", { useWithBlock: !!empty })
            };

            if (footer) {
                that.footer = template(footer, { useWithBlock: false });
            }
        }
    };

    kendo.DateView = DateView;

    var DatePicker = Widget.extend(/** @lends kendo.ui.DatePicker.prototype */{
        /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {Date} [value] <null> Specifies the selected date.
         * _example
         * // set the selected value to January 1st, 2011
         * $("#datePicker").kendoDatePicker({
         *  value: new Date(2011, 0, 1)
         * });
         * _exampleTitle To set after initialization
         * _example
         * // get a reference to the datePicker widget
         * var datePicker = $("#datePicker").data("kendoDatePicker");
         * // set the selected date on the datePicker to January 1st, 2011
         * datePicker.value(new Date(2011, 0, 1));
         * @option {Date} [min] <Date(1900, 0, 1)> Specifies the minimum date that the calendar can show.
         * _example
         * // set the min date to Jan 1st, 2011
         * $("#datePicker").kendoDatePicker({
         *  min: new Date(2011, 0, 1)
         * });
         * _exampleTitle To set after initialization
         * _example
         * // get a reference to the datePicker widget
         * var datePicker = $("#datePicker").data("kendoDatePicker");
         * // set the min date to Jan 1st, 2011
         * datePicker.min(new Date(2011, 0, 1));
         * @option {Date} [max] <Date(2099, 11, 31)> Specifies the maximum date, which the calendar can show.
         * _example
         * $("#datePicker").kendoDatePicker({
         *  max: new Date(2013, 0, 1) // sets max date to Jan 1st, 2013
         * });
         * _exampleTitle To set after initialization
         * _example
         * var datePicker = $("#datePicker").data("kendoDatePicker");
         * // set the max date to Jan 1st, 2013
         * datePicker.max(new Date(2013,0, 1));
         * @option {String} [format] <MM/dd/yyyy> Specifies the format, which is used to parse value set with value() method.
         * _example
         * $("#datePicker").kendoDatePicker({
         *     format: "yyyy/MM/dd"
         * });
         * @option {String} [start] <month> Specifies the start view.
         * The following settings are available for the <b>start</b> value:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <code>"month"</code>
         *         </dt>
         *         <dd>
         *             shows the days of the month
         *         </dd>
         *         <dt>
         *              <code>"year"</code>
         *         </dt>
         *         <dd>
         *              shows the months of the year
         *         </dd>
         *         <dt>
         *              <code>"decade"</code>
         *         </dt>
         *         <dd>
         *              shows the years of the decade
         *         </dd>
         *         <dt>
         *              <code>"century"</code>
         *         </dt>
         *         <dd>
         *              shows the decades from the centery
         *         </dd>
         *    </dl>
         * </div>
         * _example
         * $("#datePicker").kendoDatePicker({
         *     start: "decade" // the datePicker will start with a decade display
         * });
         * @option {String} [depth] Specifies the navigation depth. The following
         * settings are available for the <b>depth</b> value:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <code>"month"</code>
         *         </dt>
         *         <dd>
         *             shows the days of the month
         *         </dd>
         *         <dt>
         *              <code>"year"</code>
         *         </dt>
         *         <dd>
         *              shows the months of the year
         *         </dd>
         *         <dt>
         *              <code>"decade"</code>
         *         </dt>
         *         <dd>
         *              shows the years of the decade
         *         </dd>
         *         <dt>
         *              <code>"century"</code>
         *         </dt>
         *         <dd>
         *              shows the decades from the centery
         *         </dd>
         *    </dl>
         * </div>
         * _example
         * $("#datePicker").kendoDatePicker({
         *     start: "decade",
         *     depth: "year" // the datePicker will only go to the year level
         * });
         * @option {Function} [footer] <> Template to be used for rendering the footer of the calendar.
         * _example
         *  // DatePicker initialization
         *  &lt;script&gt;
         *      $("#datePicker").kendoDatePicker({
         *          footer: kendo.template("Today - #=kendo.toString(data, 'd') #")
         *      });
         *  &lt;/script&gt;
         * @option {Object} [month] <> Templates for the cells rendered in the calendar "month" view.
         * @option {Function} [month.content] <> Template to be used for rendering the cells in the calendar "month" view, which are in range.
         * _example
         *  //template
         *  &lt;script id="cellTemplate" type="text/x-kendo-tmpl"&gt;
         *      &lt;div class="${ data.value < 10 ? exhibition : party }"&gt;
         *      &lt;/div&gt;
         *      ${ data.value }
         *  &lt;/script&gt;
         *
         *  //datePicker initialization
         *  &lt;script&gt;
         *      $("#datePicker").kendoDatePicker({
         *          month: {
         *             content:  kendo.template($("#cellTemplate").html()),
         *          }
         *      });
         *  &lt;/script&gt;
         *
         * @option {Function} [month.empty]
         * The template used for rendering the cells in the calendar "month" view, which are not in the range between
         * the minimum and maximum values.
         *
         * @option {Object} [animation]
         * The animation(s) used for opening and/or closing the pop-up. Setting this value to <strong>false</strong>
         * will disable the animation(s).
         *
         * @option {Object} [animation.open]
         * The animation(s) used for displaying of the pop-up.
         *
         * _exampleTitle Fade-in the pop-up over 300 milliseconds
         * _example
         * $("#datePicker").kendoDatePicker({
         *     animation: {
         *         open: {
         *             effects: "fadeIn",
         *             duration: 300,
         *             show: true
         *         }
         *     }
         * });
         *
         * @option {Object} [animation.close]
         * The animation(s) used for hiding of the pop-up.
         *
         * _exampleTitle Fade-out the pop-up over 300 milliseconds
         * _example
         * $("#datePicker").kendoDatePicker({
         *     animation: {
         *         close: {
         *             effects: "fadeOut",
         *             duration: 300,
         *             show: false,
         *             hide: true
         *         }
         *     }
         * });
         *
         */
        init: function(element, options) {
            var that = this,
                dateView, enable;

            Widget.fn.init.call(that, element, options);
            element = that.element;
            options = that.options;

            calendar.validate(options);

            that._wrapper();

            that.dateView = dateView = new DateView(extend({}, options, {
                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();
                    }
                },
                open: function(e) {
                    if (that.trigger(OPEN)) {
                        e.preventDefault();
                    }
                }
            }));

            that._icon();

            element
                .addClass("k-input")
                .bind({
                    keydown: proxy(that._keydown, that),
                    focus: function(e) {
                        that._inputWrapper.addClass(FOCUSED);
                    },
                    blur: proxy(that._blur, that)
                })
                .closest("form")
                .bind("reset", function() {
                    that.value(element[0].defaultValue);
                });

            that.enable(!element.is('[disabled]'));
            that.value(options.value || that.element.val());

            kendo.notify(that);
        },
        events: [
        /**
        * Fires when the selected date is changed
        * @name kendo.ui.DatePicker#change
        * @event
        * @param {Event} e
        * @example
        * $("#datePicker").kendoDatePicker({
        *     change: function(e) {
        *         // handle event
        *     }
        * });
        * @exampleTitle To set after initialization
        * @example
        * // get a reference to the datePicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        * // bind to the change event
        * datePicker.bind("change", function(e) {
        *     // handle event
        * });
        */
        /**
        * Fires when the calendar is opened
        * @name kendo.ui.DatePicker#open
        * @event
        * @param {Event} e
        * @example
        * $("#datePicker").kendoDatePicker({
        *     open: function(e) {
        *         // handle event
        *     }
        * });
        * @exampleTitle To set after initialization
        * @example
        * // get a reference to the datePicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        * // bind to the open event
        * datePicker.bind("open", function(e) {
        *     // handle event
        * });
        */
        /**
        * Fires when the calendar is closed
        * @name kendo.ui.DatePicker#close
        * @event
        * @param {Event} e
        * @example
        * $("#datePicker").kendoDatePicker({
        *     close: function(e) {
        *         // handle event
        *     }
        * });
        * @exampleTitle To set after initialization
        * @example
        * // get a reference to the datePicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        * // bind to the close event
        * datePicker.bind("close", function(e) {
        *     // handle event
        * });
        */
        OPEN,
        CLOSE,
        CHANGE],
        options: {
            name: "DatePicker",
            value: null,
            format: "",
            min: new Date(1900, 0, 1),
            max: new Date(2099, 11, 31),
            footer: '#= kendo.toString(data,"D") #',
            start: MONTH,
            depth: MONTH,
            animation: {},
            month : {}
        },

        setOptions: function(options) {
            Widget.fn.setOptions.call(this, options);
            extend(this.dateView.options, options);
        },

        /**
        * Enable/Disable the datePicker widget.
        * @param {Boolean} enable The argument, which defines whether to enable/disable the datePicker.
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        *
        * // disables the datePicker
        * datePicker.enable(false);
        *
        * // enables the datePicker
        * datePicker.enable(true);
        */
        enable: function(enable) {
            var that = this,
                icon = that._icon.unbind(CLICK + " " + MOUSEDOWN),
                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
                element = that.element;

            if (enable === false) {
                wrapper
                    .removeClass(DEFAULT)
                    .addClass(STATEDISABLED);

                element.attr(DISABLED, DISABLED);
            } else {
                wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .bind(HOVEREVENTS, that._toggleHover);

                element
                    .removeAttr(DISABLED);

                icon.bind(CLICK, proxy(that._click, that))
                    .bind(MOUSEDOWN, function(e) { e.preventDefault(); })
            }
        },

        /**
        * Opens the calendar.
        * @name kendo.ui.DatePicker#open
        * @function
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        * // open the datepicker
        * datePicker.open();
        */
        open: function() {
            this.dateView.open();
        },

        /**
        * Closes the calendar.
        * @name kendo.ui.DatePicker#close
        * @function
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        * // close the datepicker
        * datePicker.close();
        */
        close: function() {
            this.dateView.close();
        },

        /**
        * Gets/Sets the min value of the datePicker.
        * @param {Date | String} value The min date to set.
        * @returns {Date} The min value of the datePicker.
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        *
        * // get the min value of the datePicker.
        * var min = datePicker.min();
        *
        * // set the min value of the datePicker.
        * datePicker.min(new Date(1900, 0, 1));
        */
        min: function(value) {
            return this._option(MIN, value);
        },

        /**
        * Gets/Sets the max value of the datePicker.
        * @param {Date | String} value The max date to set.
        * @returns {Date} The max value of the datePicker.
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        *
        * // get the max value of the datePicker.
        * var max = datePicker.max();
        *
        * // set the max value of the datePicker.
        * datePicker.max(new Date(1900, 0, 1));
        */
        max: function(value) {
            return this._option(MAX, value);
        },

        /**
        * Gets/Sets the value of the datePicker.
        * @param {Date | String} value The value to set.
        * @returns {Date} The value of the datePicker.
        * @example
        * // get a reference to the datepicker widget
        * var datePicker = $("#datePicker").data("kendoDatePicker");
        *
        * // get the value of the datePicker.
        * var value = datePicker.value();
        *
        * // set the value of the datePicker.
        * datePicker.value("10/10/2000"); //parse "10/10/2000" date and selects it in the calendar.
        */
        value: function(value) {
            var that = this;

            if (value === undefined) {
                return that._value;
            }

            that._old = that._update(value);
        },

        _toggleHover: function(e) {
            if (!touch) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            }
        },

        _blur: function() {
            var that = this;

            that.close();
            that._change(that.element.val());
            that._inputWrapper.removeClass(FOCUSED);
        },

        _click: function() {
            var that = this,
                element = that.element;

            if (!touch && element[0] !== document.activeElement) {
                element.focus();
            }

            that.dateView.toggle();
        },

        _change: function(value) {
            var that = this;

            value = that._update(value);

            if (+that._old != +value) {
                that._old = value;
                that.trigger(CHANGE);

                // trigger the DOM change event so any subscriber gets notified
                that.element.trigger(CHANGE);
            }
        },

        _keydown: function(e) {
            var that = this,
                dateView = that.dateView;

            if (!dateView.popup.visible() && e.keyCode == keys.ENTER) {
                that._change(that.element.val());
            } else {
                dateView.move(e);
            }
        },

        _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-icon-calendar">select</span></span>').insertAfter(element);
            }

            that._icon = icon;
        },

        _option: function(option, value) {
            var that = this,
                options = that.options;

            if (value === undefined) {
                return options[option];
            }

            value = parse(value, options.format);

            if (!value) {
                return;
            }

            options[option] = new DATE(value);
            that.dateView[option](value);
        },

        _update: function(value) {
            var that = this,
                options = that.options,
                format = options.format,
                date = parse(value, format);

            if (!isInRange(date, options.min, options.max)) {
                date = null;
            }

            that._value = date;
            that.dateView.value(date);
            that.element.val(date ? kendo.toString(date, format) : value);

            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");
            that._inputWrapper = $(wrapper[0].firstChild);
        }
    });

    ui.plugin(DatePicker);

})(jQuery);
(function ($, undefined) {
    /**
     * @name kendo.ui.AutoComplete.Description
     *
     * @section
     * <p>
     *  The <b>AutoComplete</b> provides suggestions depending on the typed
     *  text. It also allows multiple value entries. The suggestions shown by
     *  the <b>AutoComplete</b> can come from a local Array or from a remote
     *  data service.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Create a HTML input element
     * @example
     * <input id="autoComplete" />
     *
     * @exampleTitle Initialize the AutoComplete using a jQuery selector
     * @example
     * $(document).ready(function() {
     *  $("#autoComplete").kendoAutoComplete(["Item1", "Item2"]);
     * });
     *
     * @section <h3>AutoComplete Suggestions</h3>
     * <p>
     *  There are two primary ways to provide the <b>AutoComplete</b>
     *  suggestions:
     * </p>
     * <ol>
     *  <li>From a local array</li>
     *  <li>From a remote data service</li>
     * </ol>
     * <p>
     *  Locally defined values are best for small, fixed sets of suggestions.
     *  Remote suggestions should be used for larger data sets. When used
     *  with the <a href="../datasource/index.html">DataSource</a> component,
     *  filtering large remote data services can be pushed to the server as
     *  well, maximizing client-side performance.
     * </p>
     * <h3>Local Suggestions</h3>
     * <p>
     *  To configure and provide <b>AutoComplete</b> suggestions locally, you
     *  can either pass an array directly to its constructor or you can set
     *  the dataSource property to an local array.
     * </p>
     *
     * @exampleTitle Directly initialize suggestions in constructor
     * @example
     * $("#autoComplete").kendoAutoComplete(["Item1", "Item2", "Item3"]);
     *
     * @exampleTitle Using dataSource property to bind to local Array
     * @example
     * var data = ["Item1", "Item2", "Item3"];
     * $("#autoComplete").kendoAutoComplete({
     *    dataSource: data
     * });
     *
     * @section
     * <h3>Remote Suggestions</h3>
     * <p>
     *  The easiest way to bind an <b>AutoComplete</b> to remote
     *  suggestions is to use the
     *  <a href="../datasource/index.html">DataSource</a> component; an
     *  abstraction for local and remote data. The <b>DataSource</b>
     *  component can be used to serve data from a variety of data services,
     *  such as
     *  <a href="http://en.wikipedia.org/wiki/XML">XML</a>,
     *  <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>, and
     *  <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>.
     * </p>
     *
     * @exampleTitle Using the Kendo UI Web DataSource component to bind to
     * remote suggestions with OData
     * @example
     * $(document).ready(function(){
     *  $("#autoComplete").kendoAutoComplete({
     *   minLength: 3,
     *   dataTextField: "Name", // JSON property name to use
     *   dataSource: new kendo.data.DataSource({
     *    type: "odata", // specifies data protocol
     *    pageSize: 10, // limits result set
     *    transport: {
     *     read: "http://odata.netflix.com/Catalog/Titles"
     *    }
     *   })
     *  })
     * });
     *
     * @exampleTitle Using the Kendo UI Web DataSource to bind to JSONP
     * suggestions
     * @example
     * $(document).ready(function(){
     *  $("#autoComplete").kendoAutoComplete({
     *   minLength:6,
     *   dataTextField:"title",
     *   filter: "contains",
     *   dataSource: new kendo.data.DataSource({
     *    transport: {
     *     read: {
     *      url: "http://api.geonames.org/wikipediaSearchJSON",
     *      data: {
     *       q: function(){
     *        return $("#autoComplete").data("kendoAutoComplete").value();
     *       },
     *       maxRows: 10,
     *       username: "demo"
     *      }
     *     }
     *    },
     *    schema: {
     *     data:"geonames"
     *    }
     *   }),
     *   change: function(){
     *    this.dataSource.read();
     *   }
     *  })
     * });
     *
     * @section
     * <h3>Accessing an Existing AutoComplete</h3>
     * <p>
     *  You can reference an existing <b>AutoComplete</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing AutoComplete instance
     * @example
     * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
     *
     */
    var kendo = window.kendo,
        support = kendo.support,
        placeholderSupported = support.placeholder,
        ui = kendo.ui,
        keys = kendo.keys,
        DataSource = kendo.data.DataSource,
        List = ui.List,
        CHANGE = "change",
        DEFAULT = "k-state-default",
        DISABLED = "disabled",
        FOCUSED = "k-state-focused",
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter mouseleave",
        caretPosition = List.caret,
        selectText = List.selectText,
        proxy = $.proxy;

    function indexOfWordAtCaret(caret, text, separator) {
        return separator ? text.substring(0, caret).split(separator).length - 1 : 0;
    }

    function wordAtCaret(caret, text, separator) {
        return text.split(separator)[indexOfWordAtCaret(caret, text, separator)];
    }

    function replaceWordAtCaret(caret, text, word, separator) {
        var words = text.split(separator);

        words.splice(indexOfWordAtCaret(caret, text, separator), 1, word);

        if (separator && words[words.length - 1] !== "") {
            words.push("");
        }

        return words.join(separator);
    }

    function moveCaretAtEnd(element) {
        var length = element.value.length;

        selectText(element, length, length);
    }

    var AutoComplete = List.extend/** @lends kendo.ui.AutoComplete.prototype */({
        /**
        * @constructs
        * @extends kendo.ui.List
        * @param {DomElement} element DOM element
        * @param {Object} options Configuration options.
        * @option {Object | kendo.data.DataSource } [dataSource] The set of data that the AutoComplete will be bound to.
        *  Either a local JavaScript object, or an instance of the Kendo UI DataSource.
        * _exampleTitle Bind to local array
        * _example
        * var items = [ { Name: "Item 1" }, { Name: "Item 2"} ];
        * $("#autoComplete").kendoAutoComplete({ dataSource: items });
        * _exampleTitle Bind to a remote URL
        * _example
        * $("#autocomplete").kendoAutoComplete({
        *     dataSource: new kendo.data.DataSource({
        *         transport: {
        *             read: "Items/GetData" // url to server method which returns data
        *         }
        *     });
        * });
        * @option {Boolean} [enable] <true> Controls whether the AutoComplete should be initially enabled.
        * _example
        * // disable the autocomplete when it is created (enabled by default)
        * $("#autoComplete").kendoAutoComplete({
        *     enable: false
        * });
        * @option {Boolean} [suggest] <false> Controls whether the AutoComplete should automatically auto-type the rest of text.
        * _example
        * // turn on auto-typing (off by default)
        * $("#autoComplete").kendoAutoComplete({
        *     suggest: true
        * });
        * @option {Number} [delay] <200> Specifies the delay in ms after which the AutoComplete will start filtering the dataSource.
        * _example
        * // set the delay to 500 milliseconds
        * $("#autoComplete").kendoAutoComplete({
        *     delay: 500
        * });
        * @option {Number} [minLength] <1> Specifies the minimum number of characters that should be typed before the AutoComplete queries
        * the dataSource.
        * _example
        * // wait until the user types 3 characters before querying the server
        * $("#autoComplete").kendoAutoComplete({
        *     minLength: 3
        * });
        * @option {String} [dataTextField] <null> Sets the field of the data item that provides the text content of the list items.
        * _example
        * var items = [ { ID: 1, Name: "Item 1" }, { ID: 2, Name: "Item 2"} ];
        * $("#autoComplete").kendoAutoComplete({
        *     dataSource: items,
        *     dataTextField: "Name"
        * });
        * @option {String} [filter] <"startswith"> Defines the type of filtration. This value is handled by the remote data source.
        * _example
        * // send a filter value of 'contains' to the server
        * $("#autoComplete").kendoAutoComplete({
        *     filter: 'contains'
        * });
        * @option {Number} [height] <200> Sets the height of the drop-down list in pixels.
        * _example
        * // set the height of the drop-down list that appears when the autocomplete is activated to 500px
        * $("#autoComplete").kendoAutoComplete({
        *     height: 500
        * });
        * @option {String} [separator] <""> Sets the separator for completion. Empty by default, allowing for only one completion.
        * _example
        * // set completion separator to ,
        * $("#autoComplete").kendoAutoComplete({
        *     separator: ", "
        * });
        * @option {Function} [template] Template to be used for rendering the items in the list.
        * _example
        *  //template
        * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
        *       # if (data.BoxArt.SmallUrl) { #
        *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
        *       # } else { #
        *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
        *       # } #
        *  &lt;/script&gt;
        *
        *  //autocomplete initialization
        *  &lt;script&gt;
        *      $("#autocomplete").kendoAutoComplete({
        *          dataSource: dataSource,
        *          dataTextField: "Name",
        *          dataValueField: "Id",
        *          template: kendo.template($("#template").html())
        *      });
        *  &lt;/script&gt;
        * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
        * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
        * _example
        *  //autocomplete initialization
        *  &lt;script&gt;
        *      $("#autocomplete").kendoAutoComplete({
        *          dataSource: dataSource,
        *          animation: {
        *             open: {
        *                 effects: "fadeIn",
        *                 duration: 300,
        *                 show: true
        *             }
        *          }
        *      });
        *  &lt;/script&gt;
        * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
        * _example
        *  //autocomplete initialization
        *  &lt;script&gt;
        *      $("#autocomplete").kendoAutoComplete({
        *          dataSource: dataSource,
        *          animation: {
        *             close: {
        *                 effects: "fadeOut",
        *                 duration: 300,
        *                 hide: true
        *                 show: false
        *             }
        *          }
        *      });
        *  &lt;/script&gt;
        *  @option {String} [placeholder] <""> A string that appears in the textbox when it has no value.
        *  _example
        *  //autocomplete initialization
        *  <script>
        *      $("#autocomplete").kendoAutoComplete({
        *          dataSource: dataSource,
        *          placeholder: "Enter value..."
        *      });
        *  </script>
        *  _example
        *  <input id="autocomplete" placeholder="Enter value..." />
        *
        *  //combobox initialization
        *  <script>
        *      $("#autocomplete").kendoAutoComplete({
        *          dataSource: dataSource
        *      });
        *  </script>
        */
        init: function (element, options) {
            var that = this, wrapper, placeholder;

            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._accessors();

            that._dataSource();

            element[0].type = "text";
            wrapper = that.wrapper;

            element
                .attr("autocomplete", "off")
                .addClass("k-input")
                .bind({
                    keydown: proxy(that._keydown, that),
                    paste: proxy(that._search, that),
                    focus: function () {
                        that._prev = that.value();
                        that._placeholder(false);
                        wrapper.addClass(FOCUSED);
                        clearTimeout(that._bluring);
                    },
                    blur: function () {
                        that._change();
                        that._placeholder();
                        wrapper.removeClass(FOCUSED);
                    }
                });

            that._enable();

            that._popup();

            that._old = that.value();

            that._placeholder();

            kendo.notify(that);
        },

        options: {
            name: "AutoComplete",
            suggest: false,
            template: "",
            dataTextField: "",
            minLength: 1,
            delay: 200,
            height: 200,
            filter: "startswith",
            highlightFirst: false,
            separator: null,
            placeholder: "",
            animation: {}
        },

        _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 = DataSource.create(that.options.dataSource)
                .bind(CHANGE, that._refreshHandler);
        },

        setDataSource: function(dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();
        },

        events: [
        /**
        * Fires when the drop-down list is opened
        * @name kendo.ui.AutoComplete#open
        * @event
        * @param {Event} e
        * @example
        * $("#autoComplete").kendoAutoComplete({
        *     open: function(e) {
        *         // handle event
        *     }
        * });
        * @example
        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
        * autoComplete.bind("open", function(e) {
        *     // handle event
        * });
        */
        "open",
        /**
        * Fires when the drop-down list is closed
        * @name kendo.ui.AutoComplete#close
        * @event
        * @param {Event} e
        * @example
        * $("#autoComplete").kendoAutoComplete({
        *     close: function(e) {
        *         // handle event
        *     }
        * });
        * @exampleTitle To set after initialization
        * @example
        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
        * autoComplete.bind("close", function(e) {
        *     // handle event
        * });
            */
        "close",
            /**
            * Fires when the value has been changed.
            * @name kendo.ui.AutoComplete#change
            * @event
            * @param {Event} e
            * $("#autoComplete").kendoAutoComplete({
            *     change: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
            * $("#autoComplete").data("kendoAutoComplete").bind("change", function(e) {
            *     // handle event
            * });
                */
               CHANGE,
               /**
               *
               * Triggered when a Li element is selected.
               *
               * @name kendo.ui.AutoComplete#select
               * @event
               *
               * @param {Event} e
               *
               * @param {jQuery} e.item
               * The selected item chosen by a user.
               *
               * @exampleTitle Attach select event handler during initialization; detach via unbind()
               * @example
               * // event handler for select
               * var onSelect = function(e) {
               *     // access the selected item via e.item (jQuery object)
               * };
               *
               * // attach select event handler during initialization
               * var autocomplete = $("#autocomplete").kendoAutoComplete({
               *     select: onSelect
               * });
               *
               * // detach select event handler via unbind()
               * autocomplete.data("kendoAutoComplete").unbind("select", onSelect);
               *
               * @exampleTitle Attach select event handler via bind(); detach via unbind()
               * @example
               * // event handler for select
               * var onSelect = function(e) {
               *     // access the selected item via e.item (jQuery object)
               * };
               *
               * // attach select event handler via bind()
               * $("#autocomplete").data("kendoAutoComplete").bind("select", onSelect);
               *
               * // detach select event handler via unbind()
               * $("#autocomplete").data("kendoAutoComplete").unbind("select", onSelect);
               *
               */
               "select",
               "dataBinding",
               "dataBound"
            ],
        setOptions: function(options) {
            List.fn.setOptions.call(this, options);

            this._template();
            this._accessors();
        },

        /**
        * Returns the raw data record at the specified index
        * @name kendo.ui.AutoComplete#dataItem
        * @function
        * @param {Number} index The zero-based index of the data record
        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
        * @example
        * var autocomplete = $("#autocomplete").data("kendoAutoComplete");
        *
        * // get the dataItem corresponding to the passed index.
        * var dataItem = autocomplete.dataItem(1);
        */

        /**
        * Enable/Disable the autocomplete widget.
        * @param {Boolean} enable The argument, which defines whether to enable/disable the autocomplete.
        * @example
        * // get a reference to the autocomplete widget
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        *
        * // disables the autocomplete
        * autocomplete.enable(false);
        *
        * // enables the autocomplete
        * autocomplete.enable(true);
        */
        enable: function(enable) {
            var that = this,
                element = that.element,
                wrapper = that.wrapper;

            if (enable === false) {
                wrapper
                    .removeClass(DEFAULT)
                    .addClass(STATEDISABLED)
                    .unbind(HOVEREVENTS);

                element.attr(DISABLED, DISABLED);
            } else {
                wrapper
                    .removeClass(STATEDISABLED)
                    .addClass(DEFAULT)
                    .bind(HOVEREVENTS, that._toggleHover);

                element
                    .removeAttr(DISABLED);
            }
        },

        /**
        * Closes the drop-down list.
        * @example
        * // get a reference to the autocomplete widget
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        *
        * autocomplete.close();
        */
        close: function () {
            var that = this;
            that._current = null;
            that.popup.close();
        },

        /**
        * Re-render the items in drop-down list.
        * @name kendo.ui.AutoComplete#refresh
        * @function
        * @example
        * // get a referenence to the Kendo UI AutoComplete
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        * // re-render the items in drop-down list.
        * autocomplete.refresh();
        */
        refresh: function () {
            var that = this,
            ul = that.ul[0],
            popup = that.popup,
            options = that.options,
            suggest = options.suggest,
            data = that.dataSource.view(),
            length = data.length;

            that.trigger("dataBinding");

            ul.innerHTML = kendo.render(that.template, data);

            that._height(length);

            if (length) {
                if (suggest || options.highlightFirst) {
                    that.current($(ul.firstChild));
                }

                if (suggest) {
                    that.suggest(that._current);
                }
            }

            if (that._open) {
                that._open = false;
                popup[length ? "open" : "close"]();
            }

            if (that._touchScroller) {
                that._touchScroller.reset();
            }

            that._makeUnselectable();

            that.trigger("dataBound");
        },

        /**
        * Selects drop-down list item and sets the text of the autocomplete.
        * @param {jQuery Object} li The LI element.
        * @example
        * // get a reference to the autocomplete widget
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        *
        * // selects by jQuery object
        * autocomplete.select(autocomplete.ul.children().eq(0));
        */
        select: function (li) {
            this._select(li);
        },

        /**
        * Filters dataSource using the provided parameter and rebinds drop-down list.
        * @param {string} word The filter value.
        * @example
        * // get a reference to the autocomplete widget
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        *
        * // Searches for item which has "Inception" in the name.
        * autocomplete.search("Inception");
        */
        search: function (word) {
            var that = this,
            word = word || that.value(),
            options = that.options,
            separator = options.separator,
            length,
            caret,
            index;

            that._current = null;

            clearTimeout(that._typing);

            if (separator) {
                word = wordAtCaret(caretPosition(that.element[0]), word, separator);
            }

            length = word.length;

            if (!length) {
                that.popup.close();
            } else if (length >= that.options.minLength) {
                that._open = true;
                that.dataSource.filter({ field: options.dataTextField, operator: options.filter, value: word });
            }
        },

        /**
        * Forces a suggestion onto the text of the AutoComplete.
        * @param {string} value Characters to force a suggestion.
        * @example
        * // note that this suggest is not the same as the configuration method
        * // suggest which enables/disables auto suggesting for the AutoComplete
        * //
        * // get a referenence to the Kendo UI AutoComplete
        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
        *
        * // force a suggestion to the item with the name "Inception"
        * autoComplete.suggest("Inception");
        */
        suggest: function (word) {
            var that = this,
                key = that._last,
                value = that.value(),
                element = that.element[0],
                caret = caretPosition(element),
                separator = that.options.separator,
                words = value.split(separator),
                wordIndex = indexOfWordAtCaret(caret, value, separator),
                selectionEnd = caret,
                idx;

            if (key == keys.BACKSPACE || key == keys.DELETE) {
                that._last = undefined;
                return;
            }

            word = word || "";

            if (typeof word !== "string") {
                idx = word.index();

                if (idx > -1) {
                    word = that._text(that.dataSource.view()[idx]);
                } else {
                    word = "";
                }
            }

            if (caret <= 0) {
                caret = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
            }

            idx = value.substring(0, caret).lastIndexOf(separator);
            idx = idx > -1 ? caret - (idx + separator.length) : caret;
            value = words[wordIndex].substring(0, idx);

            if (word) {
                idx = word.toLowerCase().indexOf(value.toLowerCase());
                if (idx > -1) {
                    word = word.substring(idx + value.length);

                    selectionEnd = caret + word.length;

                    value += word;
                }

                if (separator && words[words.length - 1] !== "") {
                    words.push("");
                }

            }

            words[wordIndex] = value;

            that.value(words.join(separator || ""));

            selectText(element, caret, selectionEnd);
        },

        /**
        * Gets/Sets the value of the autocomplete.
        * @param {String} value The value to set.
        * @returns {String} The value of the autocomplete.
        * @example
        * // get a reference to the autocomplete widget
        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
        *
        * // get the text of the autocomplete.
        * var value = autocomplete.value();
        */
        value: function (value) {
            var that = this,
                element = that.element[0];

            if (value !== undefined) {
                element.value = value;
            } else {
                return element.value;
            }
        },

        _accept: function (li) {
            var that = this;

            that._focus(li);
            moveCaretAtEnd(that.element[0]);
        },

        _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 (that.popup.visible()) {
                    e.preventDefault();
                }

                that._accept(current);
            } else if (key === keys.ESC) {
                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);
            }
        },

        _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 = "";
                    }
                }

                element.toggleClass("k-readonly", show)
                       .val(placeholder);
            }
        },

        _search: function () {
            var that = this;
            clearTimeout(that._typing);

            that._typing = setTimeout(function () {
                if (that._prev !== that.value()) {
                    that._prev = that.value();
                    that.search();
                }
            }, that.options.delay);
        },

        _select: function (li) {
            var that = this,
                separator = that.options.separator,
                data = that.dataSource.view(),
                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(caretPosition(that.element[0]), that.value(), text, separator);
                    }

                    that.value(text);
                    that.current(li.addClass(SELECTED));
                }
            }
        },

        _toggleHover: function(e) {
            if (!support.touch) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            }
        },

        _wrapper: function () {
            var that = this,
                element = that.element,
                DOMelement = element[0],
                TABINDEX = "tabIndex",
                wrapper;

            wrapper = element.parent();

            if (!wrapper.is("span.k-widget")) {
                wrapper = element.wrap("<span />").parent();
            }

            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);
})(jQuery);
/**
 * @fileOverview Provides a DropDownList implementation which can be used to display a list of values and allows the
 * selection of a single value from the list.
 */

(function($, undefined) {
    /**
     * @name kendo.ui.DropDownList.Description
     *
     * @section
     * <p>
     *  A <strong>DropDownList</strong> displays a list of values and allows the selection of a single value from the
     *  list. It is a richer version of the standard HTML select, providing support for local and remote data binding,
     *  item templates, and configurable options for controlling the list behavior.
     * </p>
     * <p>
     *  If you want to allow user input, use the
     *  <a href="../dropdownlist/index.html" title="Kendo UI dropdownlist">Kendo UI dropdownlist</a>.
     * </p>
     * <h3>Getting Started</h3>
     * <p>There are two basic ways to create a <strong>DropDownList</strong>:</p>
     * <ol>
     *  <li>From a input element, using data binding to define the list items</li>
     *  <li>From a select element, using HTML to define the list items</li>
     * </ol>
     * <p>
     *  Regardless of the initialization technique, the resulting <strong>DropDownList</strong> will look and function
     *  identically.
     * </p>
     *
     * @exampleTitle Creating a dropdownlist from existing input HTML element
     * @example
     * <input id="dropDownList" />
     *
     * @section
     * <p></p>
     * <p>
     *  Initialization of a <strong>DropDownList</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>DropDownList</strong> occur within a handler is provided to
     *  $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a DropDownList using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#dropDownList").kendoDropDownList({
     *         dataTextField: "text",
     *         dataValueField: "value",
     *         dataSource: [
     *             { text: "Item1", value: "1" },
     *             { text: "Item2", value: "2" }
     *         ]
     *     });
     * });
     *
     * @exampleTitle Create a DropDownList from existing select HTML element with a defined structure
     * @example
     * <select id="dropDownList">
     *     <option>Item 1</option>
     *     <option>Item 2</option>
     *     <option>Item 3</option>
     * </select>
     *
     * <script>
     *    $(document).ready(function(){
     *       $("#dropDownList").kendoDropDownList();
     *    });
     * </script>
     * @section
     * <h3>Binding to Data</h3>
     * <p>
     *  A <strong>DropDownList</strong> can be bound to both local arrays and remote data via the DataSource component.
     *  Arrays are appropriate for limited value options while remote data binding is better suited for larger data
     *  sets. With remote binding, options will be loaded on-demand, similar to an <strong>AutoComplete</strong>.
     * </p>
     *
     * @exampleTitle Binding to a remote OData service
     * @example
     * $(document).ready(function() {
     *     $("#titles").kendoDropDownList({
     *         index: 0,
     *         dataTextField: "Name",
     *         dataValueField: "Id",
     *         filter: "contains",
     *         dataSource: {
     *             type: "odata",
     *             serverFiltering: true,
     *             serverPaging: true,
     *             pageSize: 20,
     *             transport: {
     *                 read: "http://odata.netflix.com/Catalog/Titles"
     *             }
     *         }
     *     });
     * });
     *
     * @section
     * <h3>Customizing Item Templates</h3>
     * <p>
     *  A DropDownList leverages templates, providing an ability to control item rendering. For a complete overview of
     *  the template capabilities and syntax of Kendo UI Web, please review the
     *  <a href="../templates/index.html" title="Kendo UI Template">Kendo UI Template</a> demos and documentation.
     * </p>
     *
     * @exampleTitle Basic item template customization
     * @example
     * <!-- HTML -->
     * <input id="titles" />
     *
     * <!-- Template -->
     * <script id="scriptTemplate" type="text/x-kendo-template">
     *     # if (data.BoxArt.SmallUrl) { #
     *         <img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" />Title:${ data.Name }, Year: ${ data.Name }
     *     # } else { #
     *         <img alt="${ data.Name }" />Title:${ data.Name }, Year: ${ data.Name }
     *     # } #
     * </script>
     *
     * <!-- DropDownList initialization -->
     * <script type="text/javascript">
     *     $(document).ready(function() {
     *         $("#titles").kendoDropDownList({
     *             autoBind: false,
     *             dataTextField: "Name",
     *             dataValueField: "Id",
     *             template: $("#scriptTemplate").html(),
     *             dataSource: {
     *                 type: "odata",
     *                 serverFiltering: true,
     *                 serverPaging: true,
     *                 pageSize: 20,
     *                 transport: {
     *                     read: "http://odata.netflix.com/Catalog/Titles"
     *                 }
     *             }
     *         });
     *     });
     * </script>
     *
     * @section
     * <h3>Accessing an Existing DropDownList</h3>
     * <p>
     *  You can reference an existing <b>DropDownList</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing DropDownList instance
     * @example
     * var dropDownList = $("#dropDownList").data("kendoDropDownList");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        Select = ui.Select,
        os = kendo.support.mobileOS,
        CLICK = kendo.support.touch ? "touchend" : "click",
        ATTRIBUTE = "disabled",
        CHANGE = "change",
        SELECT = "select",
        FOCUSED = "k-state-focused",
        DEFAULT = "k-state-default",
        DISABLED = "k-state-disabled",
        SELECTED = "k-state-selected",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter mouseleave",
        INPUTWRAPPER = ".k-dropdown-wrap",
        trimRegExp = /^\s/,
        proxy = $.proxy;

    var DropDownList = Select.extend( /** @lends kendo.ui.DropDownList.prototype */ {
        /**
         * @constructs
         * @extends kendo.ui.Select
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {kendo.data.DataSource | Object} [dataSource] Instance of DataSource or the data that the DropDownList will be bound to.
         * _exampleTitle Bind to a local array
         * _example
         * // bind to local data
         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
         * $("#dropdownlist").kendoDropDownList({
         *     dataSource: items,
         *     dataTextField: "Title",
         *     dataValueField: "Id"
         * });
         * _exampleTitle Bind to a remote URL
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     dataSource: {
         *         transport: {
         *             read: "titles.json"
         *         }
         *     },
         *     dataTextField: "Title",
         *     dataValueField: "Id"
         * });
         * @option {Boolean} [enable] <true> Controls whether the DropDownList should be initially enabled.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     enabled: false // dropdown list will not be enabled
         * });
         * _exampleTitle To set after initialization
         * _example
         * // get a reference to the dropdown list
         * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
         * // disable the dropdown
         * dropdownlist.enable(false);
         * @option {Number} [index] <0> Defines the initial selected item.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     index: 1 // selects the second item in the dropdown list
         * });
         * @option {Boolean} [autoBind] <true> Controls whether to bind the widget on initialization.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     autoBind: false
         * });
         * @option {Number} [delay] <500> Specifies the delay in ms before the search text typed by the end user is cleared.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     delay: 1000 // wait 1 second before clearing the user input
         * });
         * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text content of the list items.
         * _example
         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
         * $("#dropdownlist").kendoDropDownList({
         *     dataSource: items,
         *     dataTextField: "Title",
         *     dataValueField: "Id"
         * });
         * @option {String} [dataValueField] <""> Sets the field of the data item that provides the value content of the list items.
         * _example
         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
         * $("#dropdownlist").kendoDropDownList({
         *     dataSource: items,
         *     dataTextField: "Title",
         *     dataValueField: "Id"
         * });
         * @option {Number} [height] <200> Define the height of the drop-down list in pixels.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     height: 400
         * });
         * @option {String} [optionLabel] Define the text of the default empty item.
         * _example
         * $("#dropdownlist").kendoDropDownList({
         *     optionLabel: "Select An Option"
         * });
         * @option {Function} [template] Template to be used for rendering the items in the list.
         * _example
         *  //template
         * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
         *       # if (data.BoxArt.SmallUrl) { #
         *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
         *       # } else { #
         *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
         *       # } #
         *  &lt;/script&gt;
         *
         *  //dropdownlist initialization
         *  &lt;script&gt;
         *      $("#dropdownlist").kendoDropDownList({
         *          dataSource: dataSource,
         *          dataTextField: "Name",
         *          dataValueField: "Id",
         *          template: kendo.template($("#template").html())
         *      });
         *  &lt;/script&gt;
         * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
         * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
         * _example
         *  //dropdownlist initialization
         *  &lt;script&gt;
         *      $("#dropdownlist").kendoDropDownList({
         *          dataSource: dataSource,
         *          animation: {
         *             open: {
         *                 effects: "fadeIn",
         *                 duration: 300,
         *                 show: true
         *             }
         *          }
         *      });
         *  &lt;/script&gt;
         * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
         * _example
         *  //dropdownlist initialization
         *  &lt;script&gt;
         *      $("#dropdownlist").kendoDropDownList({
         *          dataSource: dataSource,
         *          animation: {
         *             close: {
         *                 effects: "fadeOut",
         *                 duration: 300,
         *                 hide: true
         *                 show: false
         *             }
         *          }
         *      });
         *  &lt;/script&gt;
         */
        init: function(element, options) {
            var that = this,

            options = $.isArray(options) ? { dataSource: options } : options;

            Select.fn.init.call(that, element, options);

            options = that.options;
            element = that.element.focus(function() {
                that.wrapper.focus();
            });

            that._reset();

            that._word = "";

            that._wrapper();

            that._span();

            that._popup();

            that._mobile();

            that._accessors();

            that._dataSource();

            that._enable();

            that.selectedIndex = -1;

            if (options.autoBind) {
                that._selectItem();
            } else {
                if (element.is(SELECT)) {
                    that.text(element.children(":selected").text());
                }

                if (!that.text().replace(trimRegExp, "") && options.optionLabel) {
                    that.text(options.optionLabel);
                }
            }

            kendo.notify(that);
        },

        options: {
            name: "DropDownList",
            enable: true,
            index: 0,
            autoBind: true,
            template: "",
            delay: 500,
            dataTextField: "",
            dataValueField: "",
            optionLabel: "",
            height: 200,
            animation: {}
        },
        events: [
            /**
            * Fires when the drop-down list is opened
            * @name kendo.ui.DropDownList#open
            * @event
            * @param {Event} e
            * @example
            * $("#dropdownlist").kendoDropDownList({
            *     open: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to the dropdown list
            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
            * // bind to the open event
            * dropdownlist.bind("open", function(e) {
            *     // handle event
            * });
            */
            "open",

            /**
            * Fires when the drop-down list is closed
            * @name kendo.ui.DropDownList#close
            * @event
            * @param {Event} e
            * @example
            * $("#dropdownlist").kendoDropDownList({
            *     close: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to the dropdown list
            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
            * // bind to the close event
            * dropdownlist.bind("close", function(e) {
            *     // handle event
            * });
            */
            "close",

            /**
            * Fires when the value has been changed.
            * @name kendo.ui.DropDownList#change
            * @event
            * @param {Event} e
            * @example
            * $("#dropdownlist").kendoDropDownList({
            *     change: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to the dropdown list
            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
            * // bind to the change event
            * dropdownlist.bind("change", function(e) {
            *     // handle event
            * });
            */
            CHANGE,
            /**
            *
            * Triggered when a Li element is selected.
            *
            * @name kendo.ui.DropDownList#select
            * @event
            *
            * @param {Event} e
            *
            * @param {jQuery} e.item
            * The selected item chosen by a user.
            *
            * @exampleTitle Attach select event handler during initialization; detach via unbind()
            * @example
            * // event handler for select
            * var onSelect = function(e) {
            *     // access the selected item via e.item (jQuery object)
            * };
            *
            * // attach select event handler during initialization
            * var dropdownlist = $("#dropdownlist").kendoDropDownList({
            *     select: onSelect
            * });
            *
            * // detach select event handler via unbind()
            * dropdownlist.data("kendoDropDownList").unbind("select", onSelect);
            *
            * @exampleTitle Attach select event handler via bind(); detach via unbind()
            * @example
            * // event handler for select
            * var onSelect = function(e) {
            *     // access the selected item via e.item (jQuery object)
            * };
            *
            * // attach select event handler via bind()
            * $("#dropdownlist").data("kendoDropDownList").bind("select", onSelect);
            *
            * // detach select event handler via unbind()
            * $("#dropdownlist").data("kendoDropDownList").unbind("select", onSelect);
            *
            */
            "select",
            "dataBinding",
            "dataBound"
        ],

        setOptions: function(options) {
            Select.fn.setOptions.call(this, options);

            this._template();
            this._accessors();
        },

        setDataSource: function(dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            this._selectItem();
        },

        /**
        * Closes the drop-down list.
        * @name kendo.ui.DropDownList#close
        * @function
        * @example
        * // get a reference to the dropdown widget
        * var dropdownList = $("#dropdownList").data("kendoDropDownList");
        * // close the dropdown
        * dropdownlist.close();
        */

        /**
        * Returns the raw data record at the specified index. If the index is not specified, the selected index will be used.
        * @name kendo.ui.DropDownList#dataItem
        * @function
        * @param {Number} index The zero-based index of the data record
        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
        * @example
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // get the dataItem corresponding to the selectedIndex.
        * var dataItem = dropdownlist.dataItem();
        *
        * // get the dataItem corresponding to the passed index.
        * var dataItem = dropdownlist.dataItem(1);
        */

        /**
        * Enables/disables the dropdownlist widget
        * @param {Boolean} enable Desired state
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        * // disable the dropdown list
        * dropdownlist.enable(false);
        */
        enable: function(enable) {
            var that = this,
                element = that.element,
                wrapper = that.wrapper.unbind(),
                dropDownWrapper = that._inputWrapper.unbind(HOVEREVENTS);

            if (enable === false) {
                element.attr(ATTRIBUTE, ATTRIBUTE);

                dropDownWrapper
                    .removeClass(DEFAULT)
                    .addClass(DISABLED);
            } else {
                element.removeAttr(ATTRIBUTE, ATTRIBUTE);

                dropDownWrapper
                    .addClass(DEFAULT)
                    .removeClass(DISABLED)
                    .bind(HOVEREVENTS, that._toggleHover);

                wrapper
                    .bind({
                        click: function(e) {
                            e.preventDefault();
                            that.toggle();
                        },
                        keydown: proxy(that._keydown, that),
                        keypress: proxy(that._keypress, that),
                        focusin: function() {
                            dropDownWrapper.addClass(FOCUSED);
                        },
                        focusout: function(e) {
                            that._blur();
                            dropDownWrapper.removeClass(FOCUSED);
                        }
                    });
            }
        },

        /**
        * Opens the drop-down list.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        * // open the drop down
        * dropdownlist.open();
        */
        open: function() {
            var that = this;

            if (!that.ul[0].firstChild) {
                that._open = true;
                that._selectItem();
            } else {
                that.popup.open();
                that._scroll(that._current);
            }
        },

        /**
        * Toggles the drop-down list between opened and closed state.
        * @param {Boolean} toggle Defines the whether to open/close the drop-down list.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // toggles the open state of the drop-down list.
        * dropdownlist.toggle();
        */
        toggle: function(toggle) {
            this._toggle(toggle);
        },

        /**
        * Re-render the items in drop-down list.
        * @name kendo.ui.DropDownList#refresh
        * @function
        * @example
        * // get a referenence to the Kendo UI DropDownList
        * var dropdownlist = $("dropdownlist").data("kendoDropDownList");
        * // re-render the items in drop-down list.
        * dropdownlist.refresh();
        */
        refresh: function() {
            var that = this,
                data = that._data(),
                length = data.length,
                optionLabel = that.options.optionLabel;

            that.trigger("dataBinding");

            that.ul[0].innerHTML = kendo.render(that.template, data);
            that._height(length);

            if (that.element.is(SELECT)) {
                if (optionLabel && length) {
                    optionLabel = '<option value="">' + optionLabel + "</option>";
                }

                that._options(data, optionLabel);
            }

            if (that._open) {
                that.toggle(length);
            }

            that._hideBusy();
            that._makeUnselectable();

            that.trigger("dataBound");
        },



        /**
        * Selects item, which starts with the provided parameter.
        * @param {string} word The search value.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // Selects item which starts with "In".
        * dropdownlist.search("In");
        */
        search: function(word) {
            if(word){
                var that = this;
                word = word.toLowerCase();

                that._select(function(dataItem) {
                    var text = that._text(dataItem);
                    if (text !== undefined) {
                        return (text + "").toLowerCase().indexOf(word) === 0;
                    }
                });
            }
        },

        /**
        * Selects drop-down list item and sets the value and the text of the dropdownlist.
        * @param {jQueryObject | Number | Function} li LI element or index of the item or predicate function, which defines the item that should be selected.
        * @returns {Integer} The index of the selected LI element.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // selects by jQuery object
        * dropdownlist.select(dropdownlist.ul.children().eq(0));
        *
        * // selects by index
        * dropdownlist.select(1);
        *
        * // selects item if its text is equal to "test" using predicate function
        * dropdownlist.select(function(dataItem) {
        *     return dataItem.text === "test";
        * });
        */
        select: function(li) {
            var that = this;

            if (li === undefined) {
                return that.selectedIndex;
            } else {
                that._select(li);
                that._old = that._accessor();
                that._oldIndex = that.selectedIndex;
            }
        },

        /**
        * Gets/Sets the text of the dropdownlist.
        * @param {String} text The text to set.
        * @returns {String} The text of the dropdownlist.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // get the text of the dropdownlist.
        * var text = dropdownlist.text();
        */
        text: function (text) {
            var span = this.span;

            if (text !== undefined) {
                span.text(text);
            } else {
                return span.text();
            }
        },

        /**
        * Gets/Sets the value of the dropdownlist. The value will not be set if there is no item with such value. If value is undefined, text of the data item is used.
        * @param {String} value The value to set.
        * @returns {String} The value of the dropdownlist.
        * @example
        * // get a reference to the dropdown list
        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
        *
        * // get the value of the dropdownlist.
        * var value = dropdownlist.value();
        *
        * // set the value of the dropdownlist.
        * dropdownlist.value("1"); //looks for item which has value "1"
        */
        value: function(value) {
            var that = this,
                element = that.element,
                idx;

            if (value !== undefined) {
                if (value && that._valueOnFetch(value)) {
                    return;
                }

                idx = that._index(value);

                that.select(idx > -1 ? idx : 0);
            } else {
                return that._accessor();
            }
        },

        _selectItem: function() {
            var that = this;

            that.dataSource.one(CHANGE, function() {
                var value = that.value();
                if (value) {
                    that.value(value);
                } else {
                    that.select(that.options.index);
                }
            }).fetch();
        },

        _accept: function(li) {
            this._focus(li);
        },

        _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 (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;
        },

        _keydown: function(e) {
            var that = this,
                key = e.keyCode,
                keys = kendo.keys,
                ul = that.ul[0];

            that._move(e);

            if (key === keys.HOME) {
                e.preventDefault();
                that._select(ul.firstChild);
            } else if (key === keys.END) {
                e.preventDefault();
                that._select(ul.lastChild);
            }
        },

        _keypress: function(e) {
            var that = this;

            setTimeout(function() {
                that._word += String.fromCharCode(e.keyCode || e.charCode);
                that._search();
            });
        },

        _popup: function() {
            Select.fn._popup.call(this);
            this.popup.one("open", function() {
                this.wrapper = kendo.wrap(this.element)
                                    .addClass("km-popup");
            });
        },

        _search: function() {
            var that = this;
            clearTimeout(that._typing);

            that._typing = setTimeout(function() {
                that._word = "";
            }, that.options.delay);

            that.search(that._word);
        },

        _select: function(li) {
            var that = this,
                element = that.element[0],
                current = that._current,
                data = that._data(),
                value,
                text,
                idx;

            li = that._get(li);

            if (li && li[0] && !li.hasClass(SELECTED)) {
                if (current) {
                    current.removeClass(SELECTED);
                }

                idx = ui.List.inArray(li[0], that.ul[0]);
                if (idx > -1) {
                    data = data[idx];
                    text = that._text(data);
                    value = that._value(data);
                    that.selectedIndex = idx;

                    that.text(text);
                    that._accessor(value != undefined ? value : text, idx);
                    that.current(li.addClass(SELECTED));
                }
            }
        },

        _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 ? "slideIn:up" : popup.options.animation.open.effects;
            }
        },

        _span: function() {
            var that = this,
                wrapper = that.wrapper,
                SELECTOR = ".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 class="k-select"><span class="k-icon k-arrow-down">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").mousedown(function(e) { e.preventDefault(); });
        },

        _wrapper: function() {
            var that = this,
                element = that.element,
                DOMelement = element[0],
                TABINDEX = "tabIndex",
                wrapper;

            wrapper = element.parent();

            if (!wrapper.is("span.k-widget")) {
                wrapper = element.wrap("<span />").parent();
            }

            if (!wrapper.attr(TABINDEX)) {
                wrapper.attr(TABINDEX, 0);
            }

            wrapper[0].style.cssText = DOMelement.style.cssText;
            element.hide();

            that._focused = that.wrapper = wrapper
                              .attr("unselectable", "on")
                              .addClass("k-widget k-dropdown k-header")
                              .addClass(DOMelement.className);
        }
    });

    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);
})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.ui.ComboBox.Description
     *
     * @section
     * <p>
     * The <b>ComboBox</b> widget allows the selection from pre-defined
     * values or entering a new value. It is a richer version of the standard
     * HTML select, providing support for local and remote data binding, item
     * templates, and configurable options for controlling the list behavior.
     * </p>
     * <p>
     *  To prevent user input, use the
     *  <a href="../combobox/index.html">combobox</a>.
     * </p>
     * <h3>Getting Started</h3>
     * <p>There are two basic ways to create a <b>ComboBox</b>:</p>
     * <ol>
     *  <li>
     *   From a HTML input element, using data binding to define the list
     *   items
     *  </li>
     *  <li>
     *   From a HTML select element, using HTML to define the list items
     *  </li>
     * </ol>
     * <p>
     *  Regardless of the initialization technique, the resulting
     *  <b>ComboBox</b> will look and function identically.
     * </p>
     *
     * @exampleTitle Creating a combobox from existing input HTML element
     * @example
     * <input id="comboBox" />
     *
     * @exampleTitle ComboBox initialization
     * @example
     * $(document).ready(function(){
     *     $("#comboBox").kendoComboBox({
     *         dataTextField: "text",
     *         dataValueField: "value",
     *         dataSource: [
     *             { text: "Item1", value: "1" },
     *             { text: "Item2", value: "2" }
     *         ]
     *     });
     * });
     *
     * @exampleTitle Creating a ComboBox from existing select HTML element
     * @example
     * <select id="comboBox">
     *  <option>Item 1</option>
     *  <option>Item 2</option>
     *  <option>Item 3</option>
     * </select>
     *
     * <script>
     *    $(document).ready(function(){
     *      $("#comboBox").kendoComboBox();
     *    });
     * </script>
     *
     * @section
     * <h3>Binding to Data</h3>
     * <p>
     *  The <b>ComboBox</b> can be bound to both local arrays and remote data
     *  via the <a href="../datasource/index.html">DataSource</a> component;
     *  an abstraction for local and remote data. Local arrays are
     *  appropriate for limited value options, while remote data binding is
     *  better for larger data sets. With remote binding, options will be
     *  loaded on-demand, similar to an
     *  <a href="../combobox/index.html">combobox</a>.
     * </p>
     * @exampleTitle Binding to a remote OData service
     * @example
     * $(document).ready(function() {
     *  $("#comboBox").kendoComboBox({
     *   index: 0,
     *   dataTextField: "Name",
     *   dataValueField: "Id",
     *   filter: "contains",
     *   dataSource: {
     *    type: "odata",
     *    serverFiltering: true,
     *    serverPaging: true,
     *    pageSize: 20,
     *    transport: {
     *     read: "http://odata.netflix.com/Catalog/Titles"
     *    }
     *   }
     *  });
     * });
     *
     * @section
     * <h3>Customizing Item Templates</h3>
     * <p>
     *       ComboBox leverages Kendo UI high-performance Templates to give you complete control
     *       over item rendering. For a complete overview of Kendo UI Template capabilities and syntax,
     *       please review the <a href="../templates/index.html" title="Kendo UI Template">Kendo UI Template</a> demos and documentation.
     *   </p>
     * @exampleTitle Basic item template customization
     * @example
     * <input id="comboBox" />
     * <!-- Template -->
     * <script id="scriptTemplate" type="text/x-kendo-template">
     *  # if (data.BoxArt.SmallUrl) { #
     *  <img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" />Title:${ data.Name }, Year: ${ data.Name }
     *  # } else { #
     *  <img alt="${ data.Name }" />Title:${ data.Name }, Year: ${ data.Name }
     *  # } #
     * </script>
     * <!-- ComboBox initialization -->
     * <script>
     *  $(document).ready(function() {
     *   $("#comboBox").kendoComboBox({
     *    autoBind: false,
     *    dataTextField: "Name",
     *    dataValueField: "Id",
     *    template: $("#scriptTemplate").html(),
     *    dataSource: {
     *     type: "odata",
     *     serverFiltering: true,
     *     serverPaging: true,
     *     pageSize: 20,
     *     transport: {
     *      read: "http://odata.netflix.com/Catalog/Titles"
     *     }
     *    }
     *   });
     *  });
     * </script>
     *
     * @section
     * <h3>Accessing an Existing ComboBox</h3>
     * <p>
     *  You can reference an existing <b>ComboBox</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing ComboBox instance
     * @example
     * var comboBox = $("#comboBox").data("kendoComboBox");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        List = ui.List,
        Select = ui.Select,
        support = kendo.support,
        placeholderSupported = support.placeholder,
        keys = kendo.keys,
        CLICK = support.touch ? "touchend" : "click",
        ATTRIBUTE = "disabled",
        CHANGE = "change",
        DEFAULT = "k-state-default",
        DISABLED = "k-state-disabled",
        FOCUSED = "k-state-focused",
        MOUSEDOWN = "mousedown",
        SELECT = "select",
        STATE_SELECTED = "k-state-selected",
        STATE_FILTER = "filter",
        STATE_ACCEPT = "accept",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter mouseleave",
        NULL = null,
        proxy = $.proxy;

    var ComboBox = Select.extend(/** @lends kendo.ui.ComboBox.prototype */{
        /**
        * @constructs
        * @extends kendo.ui.Select
        * @param {DomElement} element DOM element
        * @param {Object} options Configuration options.
        * @option {Object | kendo.data.DataSource} [dataSource] A local JavaScript object or instance of DataSource or the data that the ComboBox will be bound to.
        * _example
        * var items = [{ text: "Item 1", value: "1" }, { text: "Item 2", value: "2" }];
        * $("#comboBox").kendoComboBox({
        *     dataTextField: "text",
        *     dataValueField: "value",
        *     dataSource: items
        * });
        * _exampleTitle To set after initialization
        * _example
        * $("#comboBox").kendoComboBox({
        *     dataSource: new kendo.data.DataSource({
        *         transport: {
        *             read: {
        *                 url: "Get/Items" // url to remote data source (simple list of strings)
        *             }
        *         }
        *     });
        * });
        * @option {Boolean} [enable] <true> Controls whether the ComboBox should be initially enabled.
        * _example
        * $("#comboBox").kendoComboBox({
        *     enable: false
        * });
        * _exampleTitle To set after initialization
        * _example
        * // get a reference to the ComboBox widget
        * var comboBox = $("#comboBox").data("kendoComboBox");
        * comboBox.enable(false);
        * @option {Number} [index] <-1> Defines the initial selected item.
        * _example
        * var items = [{ text: "Item 1", value: "1" }, { text: "Item 2", value: "2" }];
        * $("#comboBox").kendoComboBox({
        *     dataSource: items,
        *     index: 1 // 0 based from the start of the collection of objects. this selects "Item 2".
        * });
        * @option {Boolean} [autoBind] <true> Controls whether to bind the widget to the DataSource on initialization.
        * _example
        * $("#comboBox").kendoComboBox({
        *     autoBind: false
        * });
        * @option {Boolean} [highlightFirst] <true> Controls whether the first item will be automatically highlighted.
        * _example
        * $("#comboBox").kendoComboBox({
        *     highLightFirst: true
        * });
        * @option {Boolean} [suggest] <false> Controls whether the ComboBox should automatically auto-type the rest of text.
        * _example
        * $("#comboBox").kendoComboBox({
        *     suggest: false
        * });
        * @option {Number} [delay] <200> Specifies the delay in ms after which the ComboBox will start filtering dataSource.
        * _example
        * $("#comboBox").kendoComboBox({
        *     delay: 500
        * });
        * @option {Number} [minLength] <1> Specifies the minimum characters that should be typed before the ComboBox activates
        * _example
        * $("#comboBox").kendoComboBox({
        *     minLength: 3
        * });
        * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text content of the list items.
        * _example
        * $("#comboBox").kendoComboBox({
        *     dataTextField: "Name",
        *     dataValueField: "ID"
        * });
        * @option {String} [dataValueField] <""> Sets the field of the data item that provides the value content of the list items.
        * _example
        * $("#comboBox").kendoComboBox({
        *     dataTextField: "Name",
        *     dataValueField: "ID"
        * });
        * @option {String} [filter] <"none"> Defines the type of filtration. If "none" the ComboBox will not filter the items.
        * _example
        * $("#comboBox").kendoComboBox({
        *     filter: "startswith"
        * });
        * @option {Number} [height] <200> Define the height of the drop-down list in pixels.
        * _example
        * $("#comboBox").kendoComboBox({
        *     height: 500
        * });
        * @option {Function} [template] Template to be used for rendering the items in the list.
        * _example
        *  //template
        * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
        *       # if (data.BoxArt.SmallUrl) { #
        *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
        *       # } else { #
        *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
        *       # } #
        *  &lt;/script&gt;
        *
        *  //combobox initialization
        *  &lt;script&gt;
        *      $("#combobox").kendoComboBox({
        *          dataSource: dataSource,
        *          dataTextField: "Name",
        *          dataValueField: "Id",
        *          template: kendo.template($("#template").html())
        *      });
        *  &lt;/script&gt;
        * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn off the animation.
        * _exampleTitle Turn of animation
        * _example
        * $("#comboBox").kendoComboBox({
        *     animation: false
        * });
        * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
        * _example
        *  //combobox initialization
        *  &lt;script&gt;
        *      $("#combobox").kendoComboBox({
        *          dataSource: dataSource,
        *          animation: {
        *             open: {
        *                 effects: "fadeIn",
        *                 duration: 300,
        *                 show: true
        *             }
        *          }
        *      });
        *  &lt;/script&gt;
        *
        * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
        * _example
        *  //combobox initialization
        *  &lt;script&gt;
        *      $("#combobox").kendoComboBox({
        *          dataSource: dataSource,
        *          animation: {
        *             close: {
        *                 effects: "fadeOut",
        *                 duration: 300,
        *                 hide: true
        *                 show: false
        *             }
        *          }
        *      });
        *  &lt;/script&gt;
        *  @option {String} [placeholder] <""> A string that appears in the textbox when the combobox has no value.
        *  _example
        *  //combobox initialization
        *  <script>
        *      $("#combobox").kendoComboBox({
        *          dataSource: dataSource,
        *          placeholder: "Select..."
        *      });
        *  </script>
        *  _example
        *  <input id="combobox" placeholder="Select..." />
        *
        *  //combobox initialization
        *  <script>
        *      $("#combobox").kendoComboBox({
        *          dataSource: dataSource
        *      });
        *  </script>
        */
        init: function(element, options) {
            var that = this, wrapper;

            options = $.isArray(options) ? { dataSource: options } : options;

            Select.fn.init.call(that, element, options);

            options = that.options;
            element = that.element.focus(function() {
                        that.input.focus();
                      });

            options.placeholder = options.placeholder || element.attr("placeholder");

            that._reset();

            that._wrapper();

            that._input();

            that._popup();

            that._accessors();

            that._dataSource();

            that._enable();

            wrapper = that._inputWrapper;

            that.input.bind({
                keydown: proxy(that._keydown, that),
                focus: function() {
                    wrapper.addClass(FOCUSED);
                    that._placeholder(false);
                },
                blur: function() {
                    wrapper.removeClass(FOCUSED);
                    clearTimeout(that._typing);
                    that.text(that.text());
                    that._placeholder();
                    that._blur();
                }
            });

            that._oldIndex = that.selectedIndex = -1;
            that._old = that.value();

            if (options.autoBind) {
                that._selectItem();
            } else if (element.is(SELECT)) {
                that.input.val(element.children(":selected").text());
            }

            that._placeholder();

            kendo.notify(that);
        },

        options: {
            name: "ComboBox",
            enable: true,
            index: -1,
            autoBind: true,
            delay: 200,
            dataTextField: "",
            dataValueField: "",
            minLength: 0,
            height: 200,
            highlightFirst: true,
            template: "",
            filter: "none",
            placeholder: "",
            suggest: false,
            animation: {}
        },

        events:[
            /**
            * Fires when the drop-down list is opened
            * @name kendo.ui.ComboBox#open
            * @event
            * @param {Event} e
            * @example
            * $("#comboBox").kendoComboBox({
            *     open: function(e) {
            *             // handle event
            *         }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to instance of the Kendo UI ComboBox
            * var combobox = $("#comboBox").data("kendoComboBox");
            * // bind to the open event
            * combobox.bind("open", function(e) {
            *     // handle event
            * });
            */
            "open",

            /**
            * Fires when the drop-down list is closed
            * @name kendo.ui.ComboBox#close
            * @event
            * @param {Event} e
            * @example
            * $("#comboBox").kendoComboBox({
            *     close: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to instance of the Kendo UI ComboBox
            * var combobox = $("#comboBox").data("kendoComboBox");
            * // bind to the close event
            * combobox.bind("close", function(e) {
            *     // handle event
            * });
            */
            "close",

            /**
            * Fires when the value has been changed.
            * @name kendo.ui.ComboBox#change
            * @event
            * @param {Event} e
            * @example
            * $("#comboBox").kendoComboBox({
            *     change: function(e) {
            *         // handle event
            *     }
            * });
            * @exampleTitle To set after initialization
            * @example
            * // get a reference to instance of the Kendo UI ComboBox
            * var combobox = $("#comboBox").data("kendoComboBox");
            * // bind to the change event
            * combobox.bind("change", function(e) {
            *     // handle event
            * });
            */
            CHANGE,
            /**
            *
            * Triggered when a Li element is selected.
            *
            * @name kendo.ui.ComboBox#select
            * @event
            *
            * @param {Event} e
            *
            * @param {jQuery} e.item
            * The selected item chosen by a user.
            *
            * @exampleTitle Attach select event handler during initialization; detach via unbind()
            * @example
            * // event handler for select
            * var onSelect = function(e) {
            *     // access the selected item via e.item (jQuery object)
            * };
            *
            * // attach select event handler during initialization
            * var combobox = $("#combobox").kendoComboBox({
            *     select: onSelect
            * });
            *
            * // detach select event handler via unbind()
            * combobox.data("kendoComboBox").unbind("select", onSelect);
            *
            * @exampleTitle Attach select event handler via bind(); detach via unbind()
            * @example
            * // event handler for select
            * var onSelect = function(e) {
            *     // access the selected item via e.item (jQuery object)
            * };
            *
            * // attach select event handler via bind()
            * $("#combobox").data("kendoComboBox").bind("select", onSelect);
            *
            * // detach select event handler via unbind()
            * $("#combobox").data("kendoComboBox").unbind("select", onSelect);
            *
            */
            "select",
            "dataBinding",
            "dataBound"
        ],
        setOptions: function(options) {
            Select.fn.setOptions.call(this, options);

            this._template();
            this._accessors();
        },

        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);
        },

        /**
        * Returns the raw data record at the specified index. If the index is not specified, the selected index will be used.
        * @name kendo.ui.ComboBox#dataItem
        * @function
        * @param {Number} index The zero-based index of the data record
        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // get the dataItem corresponding to the selectedIndex.
        * var dataItem = combobox.dataItem();
        *
        * // get the dataItem corresponding to the passed index.
        * var dataItem = combobox.dataItem(1);
        */

        /**
        * Closes the drop-down list.
        * @name kendo.ui.ComboBox#close
        * @function
        * @example
        * // get a reference to instance of the Kendo UI ComboBox
        * var combobox = $("#comboBox").data("kendoComboBox");
        * combobox.close();
        */
        /**
        * Enables/disables the combobox widget
        * @param {Boolean} enable Desired state
        * @example
        * // get a reference to instance of the Kendo UI ComboBox
        * var combobox = $("#comboBox").data("kendoComboBox");
        * // disables the combobox
        * combobox.enable(false);
        */
        enable: function(enable) {
            var that = this,
                input = that.input.add(that.element),
                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
                arrow = that._arrow.parent().unbind(CLICK + " " + MOUSEDOWN);

            if (enable === false) {
                wrapper
                    .removeClass(DEFAULT)
                    .addClass(DISABLED);

                input.attr(ATTRIBUTE, ATTRIBUTE);
            } else {
                wrapper
                    .removeClass(DISABLED)
                    .addClass(DEFAULT)
                    .bind(HOVEREVENTS, that._toggleHover);

                input.removeAttr(ATTRIBUTE);
                arrow.bind(CLICK, function() { that.toggle() })
                     .bind(MOUSEDOWN, function(e) { e.preventDefault(); });
            }
        },

        /**
        * Opens the drop-down list.
        * @example
        * // get a reference to instance of the Kendo UI ComboBox
        * var combobox = $("#comboBox").data("kendoComboBox");
        * combobox.open();
        */
        open: function() {
            var that = this,
                serverFiltering = that.dataSource.options.serverFiltering;

            if (that.popup.visible()) {
                return;
            }

            if (!that.ul[0].firstChild || (that._state === STATE_ACCEPT && !serverFiltering)) {
                that._open = true;
                that._state = "";
                that._selectItem();
            } else {
                that.popup.open();
                that._scroll(that._current);
            }
        },

        /**
        * Re-render the items of the drop-down list.
        * @name kendo.ui.ComboBox#refresh
        * @function
        * @example
        * // get a referenence to the Kendo UI ComboBox
        * var combobox = $("#combobox").data("kendoComboBox");
        * // re-render the items of the drop-down list.
        * combobox.refresh();
        */
        refresh: function() {
            var that = this,
                ul = that.ul[0],
                popup = that.popup,
                options = that.options,
                suggest = options.suggest,
                height = options.height,
                data = that._data(),
                length = data.length;

            that.trigger("dataBinding");

            ul.innerHTML = kendo.render(that.template, data);
            that._height(length);

            if (that.element.is(SELECT)) {
                that._options(data);
            }

            if (length) {
                if (suggest || options.highlightFirst) {
                    that.current($(ul.firstChild));
                }

                if (suggest && that.input.val()) {
                    that.suggest(that._current);
                }
            }

            if (that._open) {
                that._open = false;
                that.toggle(!!length);
            }

            if (that._touchScroller) {
                that._touchScroller.reset();
            }

            that._makeUnselectable();

            that._hideBusy();
            that.trigger("dataBound");
        },

        /**
        * Selects drop-down list item and sets the value and the text of the combobox.
        * @param {jQueryObject | Number | Function} li LI element or index of the item or predicate function, which defines the item that should be selected.
        * @returns {Integer} The index of the selected LI element.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // selects by jQuery object
        * combobox.select(combobox.ul.children().eq(0));
        *
        * // selects by index
        * combobox.select(1);
        *
        * // selects item if its text is equal to "test" using predicate function
        * combobox.select(function(dataItem) {
        *     return dataItem.text === "test";
        * });
        */
        select: function(li) {
            var that = this;

            if (li === undefined) {
                return that.selectedIndex;
            } else {
                that._select(li);
                that._old = that._accessor();
                that._oldIndex = that.selectedIndex;
            }
        },

        /**
        * Filters dataSource using the provided parameter and rebinds drop-down list.
        * @param {string} word The filter value.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // Searches for item which has "In" in the name.
        * combobox.search("In");
        */
        search: function(word) {
            word = typeof word === "string" ? word : this.text();
            var that = this,
                length = word.length,
                options = that.options,
                filter = options.filter,
                field = options.dataTextField,
                expression;

            clearTimeout(that._typing);

            if (length >= options.minLength) {
                if (filter === "none") {
                    that._filter(word);
                } else {
                    that._open = true;
                    that._state = STATE_FILTER;

                    expression = that.dataSource.filter() || {};
                    removeFiltersForField(expression, field);

                    filters = expression.filters || [];
                    filters.push({ field: field, operator: filter, value: word });

                    that.dataSource.filter(filters);
                }
            }
        },

        /**
        * Forces a suggestion onto the text of the ComboBox.
        * @param {string} value Characters to force a suggestion.
        * @example
        * // note that this suggest is not the same as the configuration method
        * // suggest which enables/disables auto suggesting for the ComboBox
        * //
        * // get a referenence to the Kendo UI ComboBox
        * var combobox = $("#combobox").data("kendoComboBox");
        * // force a suggestion to the item with the name "Inception"
        * combobox.suggest("Inception");
        */
        suggest: function(word) {
            var that = this,
                element = that.input[0],
                value = that.text(),
                caret = List.caret(element),
                key = that._last,
                idx;

            if (key == keys.BACKSPACE || key == keys.DELETE) {
                that._last = undefined;
                return;
            }

            word = word || "";

            if (typeof word !== "string") {
                idx = word.index();

                if (idx > -1) {
                    word = that._text(that.dataSource.view()[idx]);
                } else {
                    word = "";
                }
            }

            if (caret <= 0) {
                caret = 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, caret);
            }

            if (value.length !== caret || !word) {

                if (value.toLowerCase() === word.toLowerCase()) {
                    value = word;
                }

                that.text(value);
                List.selectText(element, caret, value.length);
            }
        },

        /**
        * Gets/Sets the text of the ComboBox.
        * @param {String} text The text to set.
        * @returns {String} The text of the combobox.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // get the text of the combobox.
        * var text = combobox.text();
        */
        text: function (text) {
            var that = this,
                textAccessor = that._text,
                input = that.input[0],
                dataItem;

            if (text !== undefined) {
                dataItem = that.dataItem();

                if (dataItem && textAccessor(dataItem) === text) {
                    return;
                }

                that._select(function(dataElement) {
                    return textAccessor(dataElement) === text;
                });

                if (that.selectedIndex < 0) {
                    that._custom(text);
                }

                input.value = text;
            } else {
                return input.value;
            }
        },

        /**
        * Toggles the drop-down list between opened and closed state.
        * @param {Boolean} toggle Defines the whether to open/close the drop-down list.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // toggles the open state of the drop-down list.
        * combobox.toggle();
        */
        toggle: function(toggle) {
            var that = this;

            that._toggle(toggle);
        },

        /**
        * Gets/Sets the value of the combobox. If the value is undefined, text of the data item will be used.
        * @param {String} value The value to set.
        * @returns {String} The value of the combobox.
        * @example
        * var combobox = $("#combobox").data("kendoComboBox");
        *
        * // get the value of the combobox.
        * var value = combobox.value();
        *
        * // set the value of the combobox.
        * combobox.value("1"); //looks for item which has value "1"
        */
        value: function(value) {
            var that = this,
                element = that.element,
                idx;

            if (value !== undefined) {
                if (value && that._valueOnFetch(value)) {
                    return;
                }

                idx = that._index(value);

                if (idx > -1) {
                    that.select(idx);
                } else {
                    that.current(NULL);
                    that._custom(value);
                    that.text(value);
                }

                that._old = that._accessor();
                that._oldIndex = that.selectedIndex;
            } else {
                return that._accessor();
            }
        },

        _accept: function(li) {
            var that = this;

            if (li && that.popup.visible()) {

                if (that._state === STATE_FILTER) {
                    that._state = STATE_ACCEPT;
                }

                that._focus(li);
            } else {
                that.text(that.text());
                that._change();
            }
        },

        _custom: function(value) {
            var that = this,
                element = that.element,
                custom = that._option;

            if (element.is(SELECT)) {
                if (!custom) {
                    custom = that._option = $("<option/>");
                    element.append(custom);
                }
                custom.text(value);
                custom[0].selected = true;
            } else {
                element.val(value);
            }
        },

        _filter: function(word) {
            word = word.toLowerCase();
            var that = this,
                options = that.options,
                dataSource = that.dataSource,
                predicate = function (dataItem) {
                    var text = that._text(dataItem);
                    if (text !== undefined) {
                        text = text + "";
                        if (text !== "" && word === "") {
                            return false;
                        }

                        return text.toLowerCase().indexOf(word) === 0;
                    }
                };

            if (!that.ul[0].firstChild) {
                dataSource.one(CHANGE, function () { 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) {
                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);
                } else {
                    li = NULL;
                }
            }

            that.current(li);

            return idx;
        },

        _input: function() {
            var that = this,
                element = that.element[0],
                wrapper = that.wrapper,
                SELECTOR = ".k-input",
                input;

            input = wrapper.find(SELECTOR);

            if (!input[0]) {
                wrapper.append('<span unselectable="on" class="k-dropdown-wrap k-state-default"><input class="k-input" type="text" autocomplete="off"/><span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-arrow-down">select</span></span></span>')
                       .append(that.element);

                input = wrapper.find(SELECTOR);
            }

            input[0].style.cssText = element.style.cssText;
            input.addClass(element.className)
                 .val(element.value)
                 .css({
                    width: "100%",
                    height: element.style.height
                 })
                 .show();

            if (placeholderSupported) {
                input.attr("placeholder", that.options.placeholder);
            }

            that._focused = that.input = input;
            that._arrow = wrapper.find(".k-icon");
            that._inputWrapper = $(wrapper[0].firstChild)
        },

        _keydown: function(e) {
            var that = this,
                key = e.keyCode,
                input = that.input;

            that._last = key;

            clearTimeout(that._typing);

            if (key == keys.TAB) {
                that.text(input.val());

                if (that._state === STATE_FILTER && that.selectedIndex > -1) {
                    that._state = STATE_ACCEPT;
                }
            } else if (!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;
                }

                if (!show) {
                    if (value) {
                        placeholder = input.val();
                    } else {
                        placeholder = "";
                    }
                }

                input.toggleClass("k-readonly", show)
                     .val(placeholder);
            }
        },

        _search: function() {
            var that = this;

            that._typing = setTimeout(function() {
                var value = that.text();
                if (that._prev !== value) {
                    that._prev = value;
                    that.search(value);
                }
            }, that.options.delay);
        },

        _select: function(li) {
            var that = this,
                text,
                value,
                idx = that._highlight(li),
                data = that._data();

            that.selectedIndex = idx;

            if (idx !== -1) {
                that._current.addClass(STATE_SELECTED);

                data = data[idx];
                text = that._text(data);
                value = that._value(data);

                that._prev = that.input[0].value = text;
                that._accessor(value != undefined ? value : text, idx);
                that._placeholder();
            }
        },

        _selectItem: function() {
            var that = this,
                dataSource = that.dataSource,
                expression = dataSource.filter() || {};

            removeFiltersForField(expression, that.options.dataTextField);

            that.dataSource.one(CHANGE, function() {
                var value = that.value();
                if (value) {
                    that.value(value);
                } else {
                    that.select(that.options.index);
                }
            }).filter(expression);
        },

        _wrapper: function() {
            var that = this,
                element = that.element,
                wrapper;

            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").show();
        }
    });

    function removeFiltersForField(expression, field) {
        if (!field) {
            return;
        }

        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;
                }
            });
        }
    }

    ui.plugin(ComboBox);
})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        DataSource = kendo.data.DataSource,
        Groupable = ui.Groupable,
        tbodySupportsInnerHtml = kendo.support.tbodyInnerHtml,
        Widget = ui.Widget,
        keys = kendo.keys,
        isPlainObject = $.isPlainObject,
        extend = $.extend,
        map = $.map,
        isArray = $.isArray,
        proxy = $.proxy,
        isFunction = $.isFunction,
        isEmptyObject = $.isEmptyObject,
        math = Math,
        REQUESTSTART = "requestStart",
        ERROR = "error",
        ROW_SELECTOR = "tbody>tr:not(.k-grouping-row,.k-detail-row):visible",
        DATA_CELL = ":not(.k-group-cell,.k-hierarchy-cell):visible",
        CELL_SELECTOR =  ROW_SELECTOR + ">td" + DATA_CELL,
        FIRST_CELL_SELECTOR = CELL_SELECTOR + ":first",
        EDIT = "edit",
        SAVE = "save",
        REMOVE = "remove",
        DETAILINIT = "detailInit",
        CHANGE = "change",
        SAVECHANGES = "saveChanges",
        MODELCHANGE = "modelChange",
        DATABOUND = "dataBound",
        DETAILEXPAND = "detailExpand",
        DETAILCOLLAPSE = "detailCollapse",
        FOCUSED = "k-state-focused",
        FOCUSABLE = "k-focusable",
        SELECTED = "k-state-selected",
        CLICK = "click",
        HEIGHT = "height",
        TABINDEX = "tabIndex",
        FUNCTION = "function",
        STRING = "string",
        DELETECONFIRM = "Are you sure you want to delete this record?",
        formatRegExp = /\}/ig,
        templateHashRegExp = /#/ig,
        COMMANDBUTTONTEMP = '<a class="k-button k-button-icontext #=className#" #=attr# href="\\#"><span class="#=iconClass# #=imageClass#"></span>#=text#</a>';

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

            Widget.fn.init.call(that, element, options);
            that.dataSource = options.dataSource;
            that.dataSource.bind(CHANGE, proxy(that.refresh, that));
            that.wrap();
        },

        options: {
            name: "VirtualScrollable",
            itemHeight: $.noop
        },

        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;

            element.css( {
                width: "auto",
                paddingRight: scrollbar,
                overflow: "hidden"
            });
            that.content = element.children().first();
            that.wrapper = that.content.wrap('<div class="k-virtual-scrollable-wrap"/>')
                                .parent()
                                .bind("DOMMouseScroll", proxy(that._wheelScroll, that))
                                .bind("mousewheel", proxy(that._wheelScroll, that));

            if (kendo.support.touch) {
                that.drag = new kendo.Drag(that.wrapper, {
                    global: true,
                    move: function(e) {
                        that.verticalScrollbar.scrollTop(that.verticalScrollbar.scrollTop() - e.y.delta);
                        e.preventDefault();
                    }
                });
            }

            that.verticalScrollbar = $('<div class="k-scrollbar k-scrollbar-vertical" />')
                                        .css({
                                            width: scrollbar
                                        }).appendTo(element)
                                        .bind("scroll", proxy(that._scroll, that));
        },

        _wheelScroll: function(e) {
            var that = this,
                scrollTop = that.verticalScrollbar.scrollTop(),
                originalEvent = e.originalEvent,
                delta;

            e.preventDefault();

            if (originalEvent.wheelDelta) {
                delta = originalEvent.wheelDelta;
            } else if (originalEvent.detail) {
                delta = (-originalEvent.detail) * 10;
            } else if ($.browser.opera) {
                delta = -originalEvent.wheelDelta;
            }
            that.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.scrollbar(),
                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();
            }
            that._fetching = false;
        }
    });

    function groupCells(count) {
        return new Array(count + 1).join('<td class="k-group-cell"></td>');
    }

    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-grid-update",
            iconClass: "k-icon"
        },
        canceledit: {
            text: "Cancel",
            imageClass: "k-cancel",
            className: "k-grid-cancel",
            iconClass: "k-icon"
        }
    }

    /**
     *  @name kendo.ui.Grid.Description
     *
     *  @section
     *  <p>
     *      The Grid widget displays tabular data and offers rich support interacting with data,
     *      including paging, sorting, grouping, and selection. Grid is a powerful widget with
     *      many configuration options. It can be bound to local JSON data or to remote data
     *      using the Kendo DataSource component.
     *  </p>
     *  <h3>Getting Started</h3>
     *  There are two primary ways to create a Kendo Grid:
     *
     *  <ol>
     *      <li>From an existing HTML table element, defining columns, rows, and data in HTML</li>
     *      <li>From an HTML div element, defining columns and rows with configuration, and binding to data</li>
     *  </ol>
     *
     *  @exampleTitle Creating a <b>Grid</b> from existing HTML Table element
     *  @example
     *  <!-- Define the HTML table, with rows, columns, and data -->
     *  <table id="grid">
     *   <thead>
     *       <tr>
     *           <th data-field="title">Title</th>
     *           <th data-field="year">Year</th>
     *       </tr>
     *   </thead>
     *   <tbody>
     *       <tr>
     *           <td>Star Wars: A New Hope</td>
     *           <td>1977</td>
     *       </tr>
     *       <tr>
     *           <td>Star Wars: The Empire Strikes Back</td>
     *           <td>1980</td>
     *       </tr>
     *   </tbody>
     *  </table>
     *
     *  @exampleTitle Initialize the Kendo Grid
     *  @example
     *   $(document).ready(function(){
     *       $("#grid").kendoGrid();
     *   });
     *
     *  @exampleTitle Creating a <b>Grid</b> from existing HTML Div element
     *  @example
     *  <!-- Define the HTML div that will hold the Grid -->
     *  <div id="grid">
     *  </div>
     *
     *  @exampleTitle Initialize the Kendo Grid and configure columns & data binding
     *  @example
     *    $(document).ready(function(){
     *       $("#grid").kendoGrid({
     *           columns:[
     *               {
     *                   field: "FirstName",
     *                   title: "First Name"
     *               },
     *               {
     *                   field: "LastName",
     *                   title: "Last Name"
     *           }],
     *           dataSource: {
     *               data: [
     *                   {
     *                       FirstName: "Joe",
     *                       LastName: "Smith"
     *                   },
     *                   {
     *                       FirstName: "Jane",
     *                       LastName: "Smith"
     *               }]
     *           }
     *       });
     *   });
     *
     *  @section <h3>Configuring Grid Behavior</h3>
     *  Kendo Grid supports paging, sorting, grouping, and scrolling. Configuring any of
     *  these Grid behaviors is done using simple boolean configuration options. For
     *  example, the follow snippet shows how to enable all of these behaviors.
     *
     *  @exampleTitle Enabling Grid paging, sorting, grouping, and scrolling
     *  @example
     *    $(document).ready(function(){
     *       $("#grid").kendoGrid({
     *          groupable: true,
     *          scrollable: true,
     *          sortable: true,
     *          pageable: true
     *       });
     *   });
     *  @section
     *  By default, paging, grouping, and sorting are <strong>disabled</strong>. Scrolling is enabled by default.
     *
     *  <h3>Performance with Virtual Scrolling</h3>
     *  When binding to large data sets or when using large page sizes, reducing active in-memory
     *  DOM objects is important for performance. Kendo Grid provides built-in UI virtualization
     *  for highly optimized binding to large data sets. Enabling UI virtualization is done via simple configuration.
     *
     *  @exampleTitle Enabling Grid UI virtualization
     *  @example
     *    $(document).ready(function(){
     *       $("#grid").kendoGrid({
     *          scrollable: {
     *              virtual: true
     *          }
     *       });
     *   });
     *
     * @section
     * <h3>Accessing an Existing Grid</h3>
     * <p>
     *  You can reference an existing <b>Grid</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Grid instance
     * @example
     * var grid = $("#grid").data("kendoGrid");
     *
     */
    var Grid = Widget.extend(/** @lends kendo.ui.Grid.prototype */ {
        /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         * @option {kendo.data.DataSource | Object} [dataSource] Instance of DataSource or Object with DataSource configuration.
         * _exampleTitle Bind to a DataSource instance
         * _example
         * var sharedDataSource = new kendo.data.DataSource({
         *      data: [{title: "Star Wars: A New Hope", year: 1977}, {title: "Star Wars: The Empire Strikes Back", year: 1980}],
         *      pageSize: 1
         * });
         *
         * $("#grid").kendoGrid({
         *      dataSource: sharedDataSource
         *  });
         * _exampleTitle Bind to a local array
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: [{title: "Star Wars: A New Hope", year: 1977}, {title: "Star Wars: The Empire Strikes Back", year: 1980}],
         *          pageSize: 1
         *      }
         *  });
         * @option {Function} [detailTemplate] Template to be used for rendering the detail rows in the grid.
         * See the <a href="http://demos.kendoui.com/web/grid/detailtemplate.html"><b>Detail Template</b></a> example.
         * @option {Object} [sortable] Defines whether grid columns are sortable.
         * _example
         * $("#grid").kendoGrid({
         *     sortable: true
         * });
         * //
         * // or
         * //
         * $("#grid").kendoGrid({
         *     sortable: {
         *         mode: "multiple", // enables multi-column sorting
         *         allowUnsort: true
         * });
         * @option {String} [sortable.mode] <"single"> Defines sorting mode. Possible values:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <b>"single"</b>
         *         </dt>
         *         <dd>
         *             Defines that only once column can be sorted at a time.
         *         </dd>
         *         <dt>
         *              <b>"multiple"</b>
         *         </dt>
         *         <dd>
         *              Defines that multiple columns can be sorted at a time.
         *         </dd>
         *    </dl>
         * </div>
         * @option {Boolean} [sortable.allowUnsort] <false>  Defines whether column can have unsorted state.
         * @option {Array} [columns] A collection of column objects or collection of strings that represents the name of the fields.
         * _example
         * var sharedDataSource = new kendo.data.DataSource({
         *      data: [{title: "Star Wars: A New Hope", year: 1977}, {title: "Star Wars: The Empire Strikes Back", year: 1980}],
         *      pageSize: 1
         * });
         * $("#grid").kendoGrid({
         *     dataSource: sharedDataSource,
         *     columns: [ { title: "Action", command: "destroy" }, // creates a column with delete buttons
         *                { title: "Title", field: "title", width: 200, template: "&lt;h1 id='title'&gt;${ title }&lt;/div&gt;" },
         *                { title: "Year", field: "year", filterable: false, sortable: true, format: "{0:dd/MMMM/yyyy}" } ];
         * });
         * @option {String} [columns.field] The field from the datasource that will be displayed in the column.
         * @option {String} [columns.title] The text that will be displayed in the column header.
         * @option {String} [columns.format] The format that will be applied on the column cells.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              format: "{0:dd/MMMM/yyyy}"
         *         }
         *      ]
         *   });
         * @option {Boolean} [columns.filterable] <true> Specifies whether given column is filterable.
         * @option {Boolean} [columns.sortable] <true> Specifies whether given column is sortable.
         * @option {Function} [columns.editor] Provides a way to specify custom editor for this column.
         * _example
         * $(".k-grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50)
         *      },
         *      editable: true,
         *      columns: [
         *          {
         *              field: "Name",
         *              editor: function(container, options) {
         *                  // create a KendoUI AutoComplete widget as column editor
         *                   $('&lt;input name="' + options.field + '"/&gt;')
         *                       .appendTo(container)
         *                       .kendoAutoComplete({
         *                           dataTextField: "ProductName",
         *                           dataSource: {
         *                               transport: {
         *                                 //...
         *                               }
         *                           }
         *                       });
         *              }
         *          }
         *      ]
         *   });
         * @option {Object} [columns.editor.container] The container in which the editor must be added.
         * @option {Object} [columns.editor.options] Additional options.
         * @option {String} [columns.editor.options.field] The field for the editor.
         * @option {Object} [columns.editor.options.model] The model for the editor.
         * @option {String} [columns.width] The width of the column.
         * @option {Boolean} [columns.encoded] <true> Specified whether the column content is escaped. Disable encoding if the data contains HTML markup.
         * @option {String} [columns.command] Definition of command column. The supported built-in commands are: "create", "cancel", "save", "destroy".
         * @option {String} [columns.template] The template for column's cells.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ]
         *   });
         * @option {Array} [toolbar] This is a list of commands for which the corresponding buttons will be rendered.
         * The supported built-in commands are: "create", "cancel", "save", "destroy".
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ]
         *      toolbar: [
         *          "create",
         *          { name: "save", text: "Save This Record" },
         *          { name: "cancel", template: '&lt;img src="icons/cancel.png' rel='cancel' /&gt;" }
         *      ],
         *      editable: true
         *   });
         * @option {String} [toolbar.name] The name of the command. One of the predefined or a custom.
         * @option {String} [toolbar.text] The text of the command that will be set on the button.
         * @option {String} [toolbar.template] The template for the command button.
         *
         * @option {Object} [editable] Indicates whether editing is enabled/disabled.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ]
         *      toolbar: [
         *          "create",
         *          { name: "save", text: "Save This Record" },
         *          { name: "cancel", template: "&lt;img src="icons/cancel.png' rel='cancel' /&gt;" }
         *      ],
         *      editable: {
         *          update: "true", // puts the row in edit mode when it is clicked
         *          destroy: "false", // does not remove the row when it is deleted, but marks it for deletion
         *          confirmation: "Are you sure you want to remove this item?"
         *      }
         *  });
         * @option {String} [editable.mode] Indicates which of the available edit modes(incell(default)/inline/popup) will be used
         * @option {Boolean} [editable.update] Indicates whether item should be switched to edit mode on click.
         * @option {Boolean} [editable.destroy] Indicates whether item should be deleted when click on delete button.
         * @option {Boolean} [editable.confirmation] Defines the text that will be used in confirmation box when delete an item.
         * @option {Boolean} [editable.template] Template which will be use during popup editing
         * @option {Boolean} [pageable] <false> Indicates whether paging is enabled/disabled.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ],
         *      pageable: true
         *  });
         * @option {Boolean} [groupable] <false> Indicates whether grouping is enabled/disabled.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ],
         *      groupable: true
         *  });
         * @option {Boolean} [navigatable] <false> Indicates whether keyboard navigation is enabled/disabled.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: {
         *          data: createRandomData(50),
         *          pageSize: 10
         *      },
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ],
         *      navigatable: true
         *  });
         * @option {String} [selectable] <undefined> Indicates whether selection is enabled/disabled. Possible values:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <b>"row"</b>
         *         </dt>
         *         <dd>
         *              Single row selection.
         *         </dd>
         *         <dt>
         *              <b>"cell"</b>
         *         </dt>
         *         <dd>
         *              Single cell selection.
         *         </dd>
         *         <dt>
         *              <b>"multiple, row"</b>
         *         </dt>
         *         <dd>
         *              Multiple row selection.
         *         </dd>
         *         <dt>
         *              <b>"multiple, cell"</b>
         *         </dt>
         *         <dd>
         *              Multiple cell selection.
         *         </dd>
         *    </dl>
         * </div>
         * @option {Boolean} [autoBind] <true> Indicates whether the grid will call read on the DataSource initially.
         * _example
         *  $("#grid").kendoGrid({
         *      dataSource: sharedDataSource,
         *      columns: [
         *          {
         *              field: "Name"
         *          },
         *          {
         *              field: "BirthDate",
         *              title: "Birth Date",
         *              template: '#= kendo.toString(BirthDate,"dd MMMM yyyy") #'
         *         }
         *      ],
         *      autoBind: false // the grid will not be populated with data until read() is called on the sharedDataSource
         *  });
         * @option {Boolean | Object} [scrollable] <true> Enable/disable grid scrolling. Possible values:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <b>true</b>
         *         </dt>
         *         <dd>
         *              Enables grid vertical scrolling
         *         </dd>
         *         <dt>
         *              <b>false</b>
         *         </dt>
         *         <dd>
         *              Disables grid vertical scrolling
         *         </dd>
         *         <dt>
         *              <b>{ virtual: false }</b>
         *         </dt>
         *         <dd>
         *              Enables grid vertical scrolling without data virtualization. Same as first option.
         *         </dd>
         *         <dt>
         *              <b>{ virtual: true }</b>
         *         </dt>
         *         <dd>
         *              Enables grid vertical scrolling with data virtualization.
         *         </dd>
         *    </dl>
         * </div>
         * _example
         *  $("#grid").kendoGrid({
         *      scrollable: {
         *          virtual: true //false
         *      }
         *  });
         * @option {Function} [rowTemplate] Template to be used for rendering the rows in the grid.
         * _example
         *  //template
         *  &lt;script id="rowTemplate" type="text/x-kendo-tmpl"&gt;
         *      &lt;tr&gt;
         *          &lt;td&gt;
         *              &lt;img src="${ BoxArt.SmallUrl }" alt="${ Name }" /&gt;
         *          &lt;/td&gt;
         *          &lt;td&gt;
         *              ${ Name }
         *          &lt;/td&gt;
         *          &lt;td&gt;
         *              ${ AverageRating }
         *          &lt;/td&gt;
         *      &lt;/tr&gt;
         *  &lt;/script&gt;
         *
         *  //grid intialization
         *  &lt;script&gt;PO details informaiton
         *      $("#grid").kendoGrid({
         *          dataSource: dataSource,
         *          rowTemplate: kendo.template($("#rowTemplate").html()),
         *          height: 200
         *      });
         *  &lt;/script&gt;
         */
        init: function(element, options) {
            var that = this;

            options = isArray(options) ? { dataSource: options } : options;

            Widget.fn.init.call(that, element, options);

            that._element();

            that._columns(that.options.columns);

            that._dataSource();

            that._tbody();

            that._pageable();

            that._groupable();

            that._toolbar();

            that._thead();

            that._templates();

            that._navigatable();

            that._selectable();

            that._details();

            that._editable();

            if (that.options.autoBind) {
                that.dataSource.fetch();
            }

            kendo.notify(that);
        },

        events: [
            /**
             * Fires when the grid selection has changed.
             * @name kendo.ui.Grid#change
             * @event
             * @param {Event} e
             * @example
             *  $("#grid").kendoGrid({
             *      change: function(e) {
             *          // handle event
             *      }
             *  });
             *  @exampleTitle To set after initialization
             *  @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the change event
             *  grid.bind("change", function(e) {
             *      // handle event
             *  }
             */
            CHANGE,
            "dataBinding",
            /**
             * Fires when the grid has received data from the data source.
             * @name kendo.ui.Grid#dataBound
             * @event
             * @param {Event} e
             * @example
             *  $("#grid").kendoGrid({
             *      dataBound: function(e) {
             *          // handle event
             *      }
             *  });
             *  @exampleTitle To set after initialization
             *  @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the dataBound event
             *  grid.bind("dataBound", function(e) {
             *      // handle event
             *  }
             */
            DATABOUND,
            /**
             * Fires when the grid detail row is expanded.
             * @name kendo.ui.Grid#detailExpand
             * @event
             * @param {Event} e
             * @param {Object} e.masterRow The jQuery element representing master row.
             * @param {Object} e.detailRow The jQuery element representing detail row.
             * @example
             *  $("#grid").kendoGrid({
             *      detailExpand: function(e) {
             *          // handle event
             *      }
             *  });
             *  @exampleTitle To set after initialization
             *  @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the detailExpand event
             *  grid.bind("detailExpand", function(e) {
             *      // handle event
             *  }
             */
            DETAILEXPAND,
            /**
             * Fires when the grid detail row is collapsed.
             * @name kendo.ui.Grid#detailCollapse
             * @event
             * @param {Event} e
             * @param {Object} e.masterRow The jQuery element representing master row.
             * @param {Object} e.detailRow The jQuery element representing detail row.
             * @example
             *  $("#grid").kendoGrid({
             *      detailCollapse: function(e) {
             *          // handle event
             *      }
             *  });
             * @exampleTitle To set after initialization
             * @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the detailCollapse event
             *  grid.bind("detailCollapse", function(e) {
             *      // handle event
             *  }
             */
            DETAILCOLLAPSE,
            /**
             * Fires when the grid detail is initialized.
             * @name kendo.ui.Grid#detailInit
             * @event
             * @param {Event} e
             * @param {Object} e.masterRow The jQuery element representing master row.
             * @param {Object} e.detailRow The jQuery element representing detail row.
             * @param {Object} e.detailCell The jQuery element representing detail cell.
             * @param {Object} e.data The data for the master row.
             * @example
             *  $("#grid").kendoGrid({
             *      detailInit: function(e) {
             *          // handle event
             *  });
             * @exampleTitle To set after initialization
             * @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the detailInit event
             *  grid.bind("detailInit", function(e) {
             *      // handle event
             *  }
             */
            DETAILINIT,
            /**
             * Fires when the grid enters edit mode.
             * @name kendo.ui.Grid#edit
             * @event
             * @param {Event} e
             * @param {Object} e.container The jQuery element to be edited.
             * @param {Object} e.model The model to be edited.
             * @example
             *  $("#grid").kendoGrid({
             *      edit: function(e) {
             *          // handle event
             *  });
             * @exampleTitle To set after initialization
             * @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the edit event
             *  grid.bind("edit", function(e) {
             *      // handle event
             *  }
             */
            EDIT,
            /**
             * Fires before the grid item is changed.
             * @name kendo.ui.Grid#save
             * @event
             * @param {Event} e
             * @param {Object} e.values The values entered by the user.
             * @param {Object} e.container The jQuery element which is in edit mode.
             * @param {Object} e.model The edited model.
             * @example
             *  $("#grid").kendoGrid({
             *      save: function(e) {
             *          // handle event
             *  });
             * @exampleTitle To set after initialization
             * @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the save event
             *  grid.bind("save", function(e) {
             *      // handle event
             *  }
             */
            SAVE,
            /**
             * Fires before the grid item is removed.
             * @name kendo.ui.Grid#remove
             * @event
             * @param {Event} e
             * @param {Object} e.row The row element to be deleted.
             * @param {Object} e.model The model which to be deleted.
             * @example
             *  $("#grid").kendoGrid({
             *      remove: function(e) {
             *          // handle event
             *  });
             *  @exampleTitle To set after initialization
             *  @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the remove event
             *  grid.bind("remove", function(e) {
             *      // handle event
             *  }
             */
            REMOVE,
            /**
             * Fires before the grid calls DataSource sync.
             * @name kendo.ui.Grid#saveChanges
             * @event
             * @param {Event} e
             * @example
             *  $("#grid").kendoGrid({
             *      saveChanges: function(e) {
             *          // handle event
             *  });
             *  @exampleTitle To set after initialization
             *  @example
             *  // get a reference to the grid
             *  var grid = $("#grid").data("kendoGrid");
             *  // bind to the saveChanges event
             *  grid.bind("saveChanges", function(e) {
             *      // handle event
             *  }
             */
            SAVECHANGES
        ],

        setDataSource: function(dataSource) {
            var that = this;

            that.options.dataSource = dataSource;

            that._dataSource();

            that._pageable();

            that._groupable();

            that._thead();

            dataSource.fetch();
        },

        options: {
            name: "Grid",
            columns: [],
            toolbar: null,
            autoBind: true,
            scrollable: true,
            sortable: false,
            selectable: false,
            navigatable: false,
            pageable: false,
            editable: false,
            rowTemplate: "",
            groupable: false,
            dataSource: {}
        },

        setOptions: function(options) {
            var that = this;

            Widget.fn.setOptions.call(this, options);

            that._templates();
        },

        items: function() {
            return this.tbody.children(':not(.k-grouping-row,.k-detail-row)');
        },

        _element: function() {
            var that = this,
                table = that.element;

            if (!table.is("table")) {
                table = $("<table />").appendTo(that.element);
            }

            that.table = table.attr("cellspacing", 0);

            that._wrapper();
        },

        /**
         * Returns the index of the cell in the grid item skipping group and hierarchy cells.
         * @param {Selector | DOM Element} cell Target cell.
         * @example
         *  // get a reference to the grid widget
         *  var grid = $("#grid").data("kendoGrid");
         *  // get the index of the row
         *  // TODO: add specific function call here
         */
        cellIndex: function(td) {
            return $(td).parent().find('td:not(.k-group-cell,.k-hierarchy-cell)').index(td);
        },

        _modelForContainer: function(container) {
            var id = (container.is("tr") ? container : container.closest("tr")).attr(kendo.attr("uid"));

            return this.dataSource.getByUid(id);
        },

        _editable: function() {
            var that = this,
                cell,
                model,
                column,
                editable = that.options.editable,
                handler = function () {
                    var target = document.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.delegate("tr:not(.k-grouping-row) > td:not(.k-hierarchy-cell,.k-detail-cell,.k-group-cell,.k-edit-cell,:has(a.k-grid-delete))", CLICK, function(e) {
                            var td = $(this)

                            if (td.closest("tbody")[0] !== that.tbody[0] || $(e.target).is(":input")) {
                                return;
                            }

                            if (that.editable) {
                                if (that.editable.end()) {
                                    that.closeCell();
                                    that.editCell(td);
                                }
                            } else {
                                that.editCell(td);
                            }

                        });

                        that.wrapper.bind("focusin", function(e) {
                            clearTimeout(that.timer);
                            that.timer = null;
                        });
                        that.wrapper.bind("focusout", function(e) {
                            that.timer = setTimeout(handler, 1);
                        });
                    }
                } else {
                    if (editable.update !== false) {
                        that.wrapper.delegate("tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-edit", CLICK, function(e) {
                            e.preventDefault();
                            that.editRow($(this).closest("tr"));
                        });
                    }
                }

                if (editable.destroy !== false) {
                    that.wrapper.delegate("tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-delete", CLICK, function(e) {
                        e.preventDefault();
                        that.removeRow($(this).closest("tr"));
                    });
               }
            }
        },

        /**
         * Puts the specified table cell in edit mode. It requires a jQuery object representing the cell. The editCell method triggers edit event.
         * @param {Selector} cell Cell to be edited.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // edit first table cell
         * grid.editCell(grid.tbody.find(">tr>td:first"));
         */
        editCell: function(cell) {
            var that = this,
                column = that.columns[that.cellIndex(cell)],
                model = that._modelForContainer(cell);


            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 },
                        model: model,
                        change: function(e) {
                            if (that.trigger(SAVE, { values: e.values, container: cell, model: model } )) {
                                e.preventDefault();
                            }
                        }
                    }).data("kendoEditable");

                cell.parent().addClass("k-grid-edit-row");

                that.trigger(EDIT, { container: cell, model: model });
            }
        },

        _distroyEditable: function() {
            var that = this;

            if (that.editable) {
                that._detachModelChange();

                that.editable.distroy();
                delete that.editable;

                if (that._editMode() === "popup") {
                    that._editContainer.data("kendoWindow").close();
                }

                that._editContainer = null;
            }
        },

        _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);
            }
        },

        /**
         * Closes current edited cell.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // close the cell being edited
         * grid.closeCell();
         */
        closeCell: function() {
            var that = this,
                cell = that._editContainer.removeClass("k-edit-cell"),
                id = cell.closest("tr").attr(kendo.attr("uid")),
                column = that.columns[that.cellIndex(cell)],
                model = that.dataSource.getByUid(id);

            cell.parent().removeClass("k-grid-edit-row");

            that._distroyEditable(); // editable should be destoryed before content of the container is changed

            that._displayCell(cell, column, model);

            if (cell.hasClass("k-dirty-cell")) {
                $('<span class="k-dirty"/>').prependTo(cell);
            }
        },

        _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));
        },

        /**
         * Removes the specified row from the grid. The removeRow method triggers remove event.
         * (Note: In inline or popup edit modes the changes will be automatically synced)
         * @param {Selector | DOM Element} row Row to be removed.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // remove first table row
         * grid.removeRow(grid.tbody.find(">tr:first"));
         */
        removeRow: function(row) {
            var that = this,
                model,
                mode;

            if (!that._confirmation()) {
                return;
            }

            row = $(row).hide();
            model = that._modelForContainer(row);

            if (model && !that.trigger(REMOVE, { row: row, model: model })) {
                that.dataSource.remove(model);

                mode = that._editMode();

                if (mode === "inline" || mode === "popup") {
                    that.dataSource.sync();
                }
            }
        },

        _editMode: function() {
            var mode = "incell",
                editable = this.options.editable;

            if (editable !== true) {
                mode = editable.mode || editable;
            }

            return mode;
        },

        /**
         * Switches the specified row from the grid into edit mode. The editRow method triggers edit event.
         * @param {Selector | DOM Element} row Row to be edited.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // edit first table row
         * grid.editRow(grid.tbody.find(">tr:first"));
         */
        editRow: function(row) {
            var that = this,
                model = that._modelForContainer(row),
                container;

            that.cancelRow();

            if (model) {

                that._attachModelChange(model);

                if (that._editMode() === "popup") {
                    that._createPopUpEditor(model);
                } else {
                    that._createInlineEditor(row, model);
                }
                container = that._editContainer;

                container.delegate("a.k-grid-cancel", CLICK, function(e) {
                    e.preventDefault();
                    that.cancelRow();
                });

                container.delegate("a.k-grid-update", CLICK, function(e) {
                    e.preventDefault();
                    that.saveRow();
                });
            }
        },

        _createPopUpEditor: function(model) {
            var that = this,
                html = '<div><div class="k-edit-form-container">',
                column,
                fields = [],
                idx,
                length,
                tmpl,
                editable = that.options.editable,
                options = isPlainObject(editable) ? editable.window : {},
                settings = extend({}, kendo.Template, that.options.templateSettings);

            if (editable.template) {
                html += (kendo.template(editable.template, settings))(model);
            } else {
                for (idx = 0, length = that.columns.length; idx < length; idx++) {
                    column = that.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 });
                            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>';
                        }
                    }
                }
            }

            html += that._createButton("update") + that._createButton("canceledit");
            html += '</div></div>';

            var container = that._editContainer = $(html)
                .appendTo(that.wrapper)
                .kendoWindow(extend({
                    modal: true,
                    resizable: false,
                    draggable: true,
                    title: "Edit",
                    visible: false
                }, options));

            var wnd = container.data("kendoWindow");

            wnd.wrapper.delegate(".k-close", "click", function() {
                    that.cancelRow();
                });

            that.editable = that._editContainer
                .kendoEditable({
                    fields: fields,
                    model: model,
                    clearContainer: false
                }).data("kendoEditable");

            wnd.center().open();

            that.trigger(EDIT, { container: container, model: model });
        },

        _createInlineEditor: function(row, model) {
            var that = this,
                column,
                cell,
                fields = [],
                idx,
                length;

            row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
                cell = $(this);
                column = 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 });
                    cell.attr("data-container-for", column.field);
                    cell.empty();
                } else if (column.command && hasCommand(column.command, "edit")) {
                    cell.empty();
                    $(that._createButton("update") + that._createButton("canceledit")).appendTo(cell);
                }
            });

            that._editContainer = row;

            that.editable = row
            .addClass("k-grid-edit-row")
            .kendoEditable({
                fields: fields,
                model: model,
                clearContainer: false
            }).data("kendoEditable");

            that.trigger(EDIT, { container: row, model: model });
        },

        /**
         * Switch the current edited row into dislay mode and revert changes made to the data
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * grid.cancelRow();
         */
        cancelRow: function() {
            var that = this,
                container = that._editContainer,
                newRow,
                model

            if (container) {
                model = that._modelForContainer(container);

                that.dataSource.cancelChanges(model);

                if (that._editMode() !== "popup") {
                    that._displayRow(container);
                }

                that._distroyEditable();
            }
        },

        /**
         * Switch the current edited row into dislay mode and save changes made to the data
         * (Note: the changes will be automatically synced)
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * grid.saveRow();
         */
        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 } )) {

                if (that._editMode() !== "popup") {
                    that._displayRow(container);
                }

                that._distroyEditable();
                that.dataSource.sync();
            }
        },

        _displayRow: function(row) {
            var that = this,
                model = that._modelForContainer(row);

            if (model) {
                row.replaceWith($((row.hasClass("k-alt") ? that.altRowTemplate : that.rowTemplate)(model)));
            }
        },

        _showMessage: function(text) {
            return confirm(text);
        },

        _confirmation: function() {
            var that = this,
                editable = that.options.editable,
                confirmation = editable === true || typeof editable === STRING ? DELETECONFIRM : editable.confirmation;

            return confirmation !== false && confirmation != undefined ? that._showMessage(confirmation) : true;
        },

        /**
         * Cancels any pending changes during. Deleted rows are restored. Inserted rows are removed. Updated rows are restored to their original values.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * grid.cancelChanges();
         */
        cancelChanges: function() {
            this.dataSource.cancelChanges();
        },

        /**
         * Calls DataSource sync to submit any pending changes if state is valid. The saveChanges method triggers saveChanges event.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * grid.saveChanges();
         */
        saveChanges: function() {
            var that = this;

            if (((that.editable && that.editable.end()) || !that.editable) && !that.trigger(SAVECHANGES)) {
                that.dataSource.sync();
            }
        },

        /**
         * Adds a new empty table row in edit mode. The addRow method triggers edit event.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * grid.addRow();
         */
        addRow: function() {
            var that = this,
                options = that.options,
                index,
                dataSource = that.dataSource;

            if ((that.editable && that.editable.end()) || !that.editable) {
                index = dataSource.indexOf((dataSource.view() || [])[0]);
                if (index < 0) {
                    index = 0;
                }

                var model = dataSource.insert(index, {}),
                    id = model.uid,
                    mode = that._editMode(),
                    row = that.table.find("tr[" + kendo.attr("uid") + "=" + id + "]"),
                    cell = row.children("td:not(.k-group-cell,.k-hierarchy-cell)").first();

                if ((mode === "inline" || mode === "popup") && row.length) {
                    that.editRow(row);
                } else if (cell.length) {
                    that.editCell(cell);
                }
            }
        },

        _toolbar: function() {
            var that = this,
                wrapper = that.wrapper,
                toolbar = that.options.toolbar,
                template;

            if (toolbar) {
                toolbar = isFunction(toolbar) ? toolbar({}) : (typeof toolbar === STRING ? toolbar : that._toolbarTmpl(toolbar).replace(templateHashRegExp, "\\#"));

                template = proxy(kendo.template(toolbar), that)

                $('<div class="k-toolbar k-grid-toolbar" />')
                    .html(template({}))
                    .prependTo(wrapper)
                    .delegate(".k-grid-add", CLICK, function(e) { e.preventDefault(); that.addRow(); })
                    .delegate(".k-grid-cancel-changes", CLICK, function(e) { e.preventDefault(); that.cancelChanges(); })
                    .delegate(".k-grid-save-changes", CLICK, function(e) { e.preventDefault(); that.saveChanges(); });
            }
        },

        _toolbarTmpl: function(commands) {
            var that = this,
                idx,
                length,
                html = "",
                options,
                commandName,
                template,
                command;

            if (isArray(commands)) {
                for (idx = 0, length = commands.length; idx < length; idx++) {
                    html += that._createButton(commands[idx]);
                }
            }
            return html;
        },

        _createButton: function(command) {
            var that = this,
                template = command.template || COMMANDBUTTONTEMP,
                commandName = typeof command === STRING ? command : command.name,
                options = { className: "", text: commandName, imageClass: "", attr: "", iconClass: "" };

            if (isPlainObject(command)) {
                options = extend(true, options, defaultCommands[commandName], command);
            } else {
                options = extend(true, options, defaultCommands[commandName]);
            }

            return kendo.template(template)(options);
        },

        _groupable: function() {
            var that = this,
                wrapper = that.wrapper,
                groupable = that.options.groupable;

            if (!that.groupable) {
                that.table.delegate(".k-grouping-row .k-collapse, .k-grouping-row .k-expand", CLICK, function(e) {
                    var element = $(this),
                    group = element.closest("tr");

                    if(element.hasClass('k-collapse')) {
                        that.collapseGroup(group);
                    } else {
                        that.expandGroup(group);
                    }
                    e.preventDefault();
                    e.stopPropagation();
                });
            }

            if (groupable) {
                if(!wrapper.has("div.k-grouping-header")[0]) {
                    $("<div />").addClass("k-grouping-header").html("&nbsp;").prependTo(wrapper);
                }

                if (that.groupable) {
                    that.groupable.destroy();
                }

                that.groupable = new Groupable(wrapper, {
                    filter: "th:not(.k-group-cell)[" + kendo.attr("field") + "][" + kendo.attr("groupable") + "!=false]",
                    groupContainer: "div.k-grouping-header",
                    dataSource: that.dataSource
                });
            }
        },

        _selectable: function() {
            var that = this,
                multi,
                cell,
                selectable = that.options.selectable;

            if (selectable) {
                multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;
                cell = typeof selectable === STRING && selectable.toLowerCase().indexOf("cell") > -1;

                that.selectable = new kendo.ui.Selectable(that.table, {
                    filter: ">" + (cell ? CELL_SELECTOR : ROW_SELECTOR),
                    multiple: multi,
                    change: function() {
                        that.trigger(CHANGE);
                    }
                });

                if (that.options.navigatable) {
                    that.wrapper.keydown(function(e) {
                        var current = that.current();
                        if (e.keyCode === keys.SPACEBAR && e.target == that.wrapper[0] && !current.hasClass("k-edit-cell")) {
                            e.preventDefault();
                            e.stopPropagation();
                            current = cell ? current : current.parent();

                            if(multi) {
                                if(!e.ctrlKey) {
                                    that.selectable.clear();
                                } else {
                                    if(current.hasClass(SELECTED)) {
                                        current.removeClass(SELECTED);
                                        current = null;
                                    }
                                }
                            } else {
                                that.selectable.clear();
                            }

                            that.selectable.value(current);
                        }
                    });
                }
            }
        },

        /**
         * Clears currently selected items.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // clear the selection of items in the grid
         * grid.clearSelection();
         */
        clearSelection: function() {
            var that = this;
            that.selectable.clear();
            that.trigger(CHANGE);
        },

        /**
         * Selects the specified Grid rows/cells. If called without arguments - returns the selected rows/cells.
         * @param {Selector | Array} items Items to select.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // selects first grid item
         * grid.select(grid.tbody.find(">tr:first"));
         */
        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();
        },

        current: function(element) {
            var that = this,
                current = that._current;

            if (element !== undefined && element.length) {
                if (!current || current[0] !== element[0]) {
                    element.addClass(FOCUSED);
                    if (current) {
                        current.removeClass(FOCUSED);
                    }
                    that._current = element;
                    that._scrollTo(element.parent()[0]);
                }
            }

            return that._current;
        },

        _scrollTo: function(element) {
            if(!element || !this.options.scrollable) {
                return;
            }

            var elementOffsetTop = element.offsetTop,
                container = this.content[0],
                elementOffsetHeight = element.offsetHeight,
                containerScrollTop = container.scrollTop,
                containerOffsetHeight = container.clientHeight,
                bottomDistance = elementOffsetTop + elementOffsetHeight;

            container.scrollTop = containerScrollTop > elementOffsetTop
                                    ? elementOffsetTop
                                    : bottomDistance > (containerScrollTop + containerOffsetHeight)
                                    ? bottomDistance - containerOffsetHeight
                                    : containerScrollTop;
        },

        _navigatable: function() {
            var that = this,
                wrapper = that.wrapper,
                table = that.table.addClass(FOCUSABLE),
                currentProxy = proxy(that.current, that),
                selector = "." + FOCUSABLE + " " + CELL_SELECTOR,
                browser = $.browser,
                clickCallback = function(e) {
                    var currentTarget = $(e.currentTarget);
                    if (currentTarget.closest("tbody")[0] !== that.tbody[0]) {
                        return;
                    }

                    currentProxy(currentTarget);
                    if(!$(e.target).is(":button,a,:input,a>.k-icon,textarea,span.k-icon,.k-input")) {
                        setTimeout(function() { wrapper.focus(); } );
                    }

                    e.stopPropagation();
                };

            if (that.options.navigatable) {
                wrapper.bind({
                    focus: function() {
                        var current = that._current;
                        if(current && current.is(":visible")) {
                            current.addClass(FOCUSED);
                        } else {
                            currentProxy(that.table.find(FIRST_CELL_SELECTOR));
                        }
                    },
                    focusout: function(e) {
                        if (that._current) {
                            that._current.removeClass(FOCUSED);
                        }
                        e.stopPropagation();
                    },
                    keydown: function(e) {
                        var key = e.keyCode,
                            current = that.current(),
                            shiftKey = e.shiftKey,
                            dataSource = that.dataSource,
                            pageable = that.options.pageable,
                            canHandle = !$(e.target).is(":button,a,:input,a>.t-icon"),
                            isInCell = that._editMode() == "incell",
                            currentIndex,
                            cell,
                            handled = false;

                        if (canHandle && keys.UP === key) {
                            currentProxy(current ? current.parent().prevAll(ROW_SELECTOR).last().children(":eq(" + current.index() + "),:eq(0)").last() : table.find(FIRST_CELL_SELECTOR));
                            handled = true;
                        } else if (canHandle && keys.DOWN === key) {
                            currentProxy(current ? current.parent().nextAll(ROW_SELECTOR).first().children(":eq(" + current.index() + "),:eq(0)").last() : table.find(FIRST_CELL_SELECTOR));
                            handled = true;
                        } else if (canHandle && keys.LEFT === key) {
                            currentProxy(current ? current.prevAll(DATA_CELL + ":first") : table.find(FIRST_CELL_SELECTOR));
                            handled = true;
                        } else if (canHandle && keys.RIGHT === key) {
                            currentProxy(current ? current.nextAll(":visible:first") : table.find(FIRST_CELL_SELECTOR));
                            handled = true;
                        } else if (canHandle && pageable && keys.PAGEDOWN == key) {
                            that._current = null;
                            dataSource.page(dataSource.page() + 1);
                            handled = true;
                        } else if (canHandle && pageable && keys.PAGEUP == key) {
                            that._current = null;
                            dataSource.page(dataSource.page() - 1);
                            handled = true;
                        } else if (that.options.editable) {
                            current = current ? current : table.find(FIRST_CELL_SELECTOR);
                            if (keys.ENTER == key || keys.F12 == key) {
                                that._handleEditing(current);
                                handled = true;
                            } else if (keys.TAB == key && isInCell) {
                                cell = shiftKey ? current.prevAll(DATA_CELL + ":first") : current.nextAll(":visible:first");
                                if (!cell.length) {
                                    cell = current.parent()[shiftKey ? "prevAll" : "nextAll"]("tr:not(.k-grouping-row,.k-detail-row):visible")
                                        .children(DATA_CELL + (shiftKey ? ":last" : ":first"));
                                }

                                if (cell.length) {
                                    that._handleEditing(current, cell);
                                    handled = true;
                                }
                            } else if (keys.ESC == key && that._editContainer) {
                                if (that._editContainer.has(current[0]) || current[0] === that._editContainer[0]) {
                                    if (isInCell) {
                                        that.closeCell();
                                    } else {
                                        currentIndex = that.items().index(current.parent());
                                        document.activeElement.blur();
                                        that.cancelRow();
                                        if (currentIndex >= 0) {
                                            that.current(that.items().eq(currentIndex).children().filter(DATA_CELL).first());
                                        }
                                    }

                                    if (browser.msie && parseInt(browser.version) < 9) {
                                        document.body.focus();
                                    }
                                    wrapper.focus();
                                    handled = true;
                                }
                            }
                        }

                        if(handled) {
                            e.preventDefault();
                            e.stopPropagation();
                        }
                    }
                });

                wrapper.delegate(selector, "mousedown", clickCallback); }
        },

        _handleEditing: function(current, next) {
            var that = this,
                mode = that._editMode(),
                editContainer = that._editContainer,
                idx,
                isEdited;

            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], document.activeElement)) {
                    document.activeElement.blur();
                }

                if (that.editable.end()) {
                    if (mode == "incell") {
                        that.closeCell();
                    } else {
                        if (current.parent()[0] === editContainer[0]) {
                            idx = that.items().index(current.parent());
                        } else {
                            idx = that.items().index(editContainer);
                        }
                        that.saveRow();
                        that.current(that.items().eq(idx).children().filter(DATA_CELL).first());
                        isEdited = true;
                    }
                } else {
                    if (mode == "incell") {
                        that.current(editContainer);
                    } else {
                        that.current(editContainer.children().filter(DATA_CELL).first());
                    }
                    editContainer.find(":input:visible:first").focus();
                    return;
                }
            }

            if (next) {
                that.current(next);
            }

            that.wrapper.focus();
            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")
                                  .attr(TABINDEX, math.max(table.attr(TABINDEX) || 0, 0));

            table.removeAttr(TABINDEX);

            if (height) {
                that.wrapper.css(HEIGHT, height);
                table.css(HEIGHT, "auto");
            }
        },

        _tbody: function() {
            var that = this,
                table = that.table,
                tbody;

            tbody = table.find(">tbody");

            if (!tbody.length) {
                tbody = $("<tbody/>").appendTo(table);
            }

            that.tbody = tbody;
        },

        _scrollable: function() {
            var that = this,
                header,
                table,
                options = that.options,
                height = that.wrapper.innerHeight(),
                scrollable = options.scrollable,
                scrollbar = kendo.support.scrollbar();

            if (scrollable) {
                header = that.wrapper.children().filter(".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("padding-right", scrollable.virtual ? scrollbar + 1 : scrollbar);
                table = $('<table 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")) {
                    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 (scrollable !== true && scrollable.virtual) {
                        new VirtualScrollable(that.content, {
                            dataSource: that.dataSource,
                            itemHeight: proxy(that._averageRowHeight, that)
                        });
                    }
                }

                var scrollables = header.find(">.k-grid-header-wrap, > .k-footer-template"); // add footer when implemented

                if (scrollable.virtual) {
                    that.content.find(">.k-virtual-scrollable-wrap").bind('scroll', function () {
                        scrollables.scrollLeft(this.scrollLeft);
                    });
                } else {
                    that.content.bind('scroll', function () {
                        scrollables.scrollLeft(this.scrollLeft);
                    });

                    kendo.touchScroller(that.content);
                }
            }
        },

        _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) {

                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 (height > scrollbar * 2) { // do not set height if proper scrollbar cannot be displayed
                    that.content.height(height);
                }
            }
        },

        _averageRowHeight: function() {
            var that = this,
                rowHeight = that._rowHeight;

            if (!that._rowHeight) {
                that._rowHeight = rowHeight = that.table.outerHeight() / that.table[0].rows.length;
                that._sum = rowHeight;
                that._measures = 1;

                totalHeight = math.round(that.dataSource.total() * rowHeight);
            }

            var currentRowHeight = that.table.outerHeight() / that.table[0].rows.length;

            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(REQUESTSTART, that._requestStartHandler)
                                .unbind(ERROR, that._errorHandler);
            } else {
                that._refreshHandler = proxy(that.refresh, that);
                that._requestStartHandler = proxy(that._requestStart, that);
                that._errorHandler = proxy(that._error, that);
            }

            that.dataSource = DataSource.create(dataSource)
                                .bind(CHANGE, that._refreshHandler)
                                .bind(REQUESTSTART, that._requestStartHandler)
                                .bind(ERROR, that._errorHandler);
        },

        _error: function() {
            this._progress(false);
        },

        _requestStart: function() {
            this._progress(true);
        },

        _modelChange: function(e) {
            var that = this,
                model = e.model,
                row = that.tbody.find("tr[" + kendo.attr("uid") + "=" + model.uid +"]"),
                cell,
                column,
                isAlt = row.hasClass("k-alt"),
                tmp,
                idx,
                length;

            if (row.children(".k-edit-cell").length) {
                row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
                    cell = $(this);
                    column = 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")) {
                tmp = $((isAlt ? that.altRowTemplate : that.rowTemplate)(model));

                row.replaceWith(tmp);

                for (idx = 0, length = that.columns.length; idx < length; idx++) {
                    column = that.columns[idx];

                    if (column.field === e.field) {
                        cell = tmp.children(":not(.k-group-cell,.k-hierarchy-cell)").eq(idx)
                        $('<span class="k-dirty"/>').prependTo(cell);
                    }
                }
            }
        },

        _pageable: function() {
            var that = this,
                wrapper,
                pageable = that.options.pageable;

            if (pageable) {
                wrapper = that.wrapper.children("div.k-grid-pager").empty();

                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;

            if (footerTemplate) {
                html = $(that._wrapFooter(footerTemplate(aggregates || {})));

                if (that.footer) {
                    var tmp = html;

                    that.footer.replaceWith(tmp);
                    that.footer = tmp;
                } else {
                    if (options.scrollable) {
                        that.footer = options.pageable ? html.insertBefore(that.wrapper.children("div.k-grid-pager")) : html.appendTo(that.wrapper);
                    } else {
                        that.footer = html.insertBefore(that.tbody);
                    }
                }
            }
        },

        _wrapFooter: function(footerRow) {
            var that = this,
                html = "",
                columns = that.columns,
                idx,
                length,
                groups = that.dataSource.group().length,
                column;

            if (that.options.scrollable) {
                html = $('<div class="k-grid-footer"><table cellspacing="0"><tbody>' + footerRow + '</tbody></table></div>');
                that._appendCols(html.find("table"));
                return html;
            }

            return '<tfoot>' + footerRow + '</tfoot>';
        },

        _filterable: function() {
            var that = this,
                columns = that.columns,
                filterable = that.options.filterable;

            if (filterable) {
                that.thead
                    .find("th:not(.k-hierarchy-cell)")
                    .each(function(index) {
                        if (columns[index].filterable !== false) {
                            $(this).kendoFilterMenu(extend(true, {}, filterable, columns[index].filterable, { dataSource: that.dataSource }));
                        }
                    })
            }
        },

        _sortable: function() {
            var that = this,
                columns = that.columns,
                column,
                sortable = that.options.sortable;

            if (sortable) {
                that.thead
                    .find("th:not(.k-hierarchy-cell)")
                    .each(function(index) {
                        column = columns[index];
                        if (column.sortable !== false && !column.command) {
                            $(this).kendoSortable(extend({}, sortable, { dataSource: that.dataSource }));
                        }
                    })
            }
        },

        _columns: function(columns) {
            var that = this,
                table = that.table,
                encoded,
                cols = table.find("col"),
                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) {
                var th = $(th),
                    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"));

                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",
                    template: th.attr(kendo.attr("template")),
                    width: cols.eq(idx).css("width")
                };
            });

            encoded = !(that.table.find("tbody tr").length > 0 && (!dataSource || !dataSource.transport));

            that.columns = map(columns, function(column) {
                column = typeof column === STRING ? { field: column } : column;
                return extend({ encoded: encoded }, column);
            });
        },

        _tmpl: function(rowTemplate, alt) {
            var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                paramName = settings.paramName,
                idx,
                length = that.columns.length,
                template,
                state = { storage: {}, count: 0 },
                id,
                column,
                type,
                hasDetails = that._hasDetails(),
                className = [],
                groups = that.dataSource.group().length;

            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") + '="#=uid#"';
                }

                rowTemplate += ">";

                if (groups > 0) {
                    rowTemplate += groupCells(groups);
                }

                if (hasDetails) {
                    rowTemplate += '<td class="k-hierarchy-cell"><a class="k-icon k-plus" href="\\#"></a></td>';
                }

                for (idx = 0; idx < length; idx++) {
                    column = that.columns[idx];
                    template = column.template;
                    type = typeof template;

                    rowTemplate += "<td>";
                    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;
        },

        _cellTmpl: function(column, state) {
            var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                template = column.template,
                paramName = settings.paramName,
                html = "",
                idx,
                length,
                format = column.format,
                type = typeof template;

            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 {
                html += column.encoded ? "${" : "#=";

                if (format) {
                    html += 'kendo.format(\"' + format.replace(formatRegExp,"\\}") + '\",';
                }

                if (!settings.useWithBlock) {
                    html += paramName + ".";
                }

                html += column.field;

                if (format) {
                    html += ")";
                }

                html += column.encoded ? "}" : "#";
            }
            return html;
        },

        _templates: function() {
            var that = this,
                options = that.options,
                dataSource = that.dataSource,
                groups = dataSource.group(),
                aggregates = dataSource.aggregate();

            that.rowTemplate = that._tmpl(options.rowTemplate);
            that.altRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, true);

            if (that._hasDetails()) {
                that.detailTemplate = that._detailTmpl(options.detailTemplate || "");
            }

            if (!isEmptyObject(aggregates) ||
                $.grep(that.columns, function(column) { return column.footerTemplate }).length) {

                that.footerTemplate = that._footerTmpl(aggregates, "footerTemplate", "k-footer-template");
            }
            if (groups.length && $.grep(that.columns, function(column) { return column.groupFooterTemplate }).length) {
                aggregates = $.map(groups, function(g) { return g.aggregates });
                that.groupFooterTemplate = that._footerTmpl(aggregates, "groupFooterTemplate", "k-group-footer");
            }
        },

        _footerTmpl: function(aggregates, templateName, rowClass) {
            var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                paramName = settings.paramName,
                html = "",
                idx,
                length,
                columns = that.columns,
                template,
                type,
                storage = {},
                count = 0,
                scope = {},
                dataSource = that.dataSource,
                groups = dataSource.group().length,
                fieldsMap = {},
                column;

            if (!isEmptyObject(aggregates)) {
                if (isArray(aggregates)) {
                    for (idx = 0, length = aggregates.length; idx < length; idx++) {
                        fieldsMap[aggregates[idx].field] = true;
                    }
                } else {
                    fieldsMap[aggregates.field] = true;
                }
            }

            html += '<tr class="' + rowClass + '">';

            if (groups > 0) {
                html += groupCells(groups);
            }

            if (that._hasDetails()) {
                html += '<td class="k-hierarchy-cell">&nbsp;</td>';
            }

            for (idx = 0, length = that.columns.length; idx < length; idx++) {
                column = columns[idx];
                template = column[templateName];
                type = typeof template;

                html += "<td>";

                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.dataSource.group().length,
                columns = 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"' + (columns ? ' colspan="' + columns + '"' : '') + ">";

            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 !== undefined  || (that._events[DETAILINIT] || []).length;
        },

        _details: function() {
            var that = this;

            that.table.delegate(".k-hierarchy-cell .k-plus, .k-hierarchy-cell .k-minus", CLICK, 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);

                if(hasDetails && !masterRow.next().hasClass("k-detail-row")) {
                    data = that.dataItem(masterRow),
                    $(detailTemplate(data)).insertAfter(masterRow);

                    that.trigger(DETAILINIT, { masterRow: masterRow, detailRow: masterRow.next(), data: data, detailCell: masterRow.next().find(".k-detail-cell") });
                }

                detailRow = masterRow.next();

                that.trigger(expanding ? DETAILEXPAND : DETAILCOLLAPSE, { masterRow: masterRow, detailRow: detailRow});
                detailRow.toggle(expanding);

                e.preventDefault();
                return false;
            });
        },

        /**
         * Returns the data item to which a given table row (tr DOM element) is bound.
         * @param {Selector | DOM Element} tr Target row.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // returns the data item for first row
         * grid.dataItem(grid.tbody.find(">tr:first"));
         */
        dataItem: function(tr) {
            return this._data[this.tbody.find('> tr:not(.k-grouping-row,.k-detail-row)').index($(tr))]
        },

        /**
         * Expands specified master row.
         * @param {Selector | DOM Element} row Target master row to expand.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // expands first master row
         * grid.expandRow(grid.tbody.find(">tr.k-master-row:first"));
         */
        expandRow: function(tr) {
            $(tr).find('> td .k-plus, > td .k-expand').click();
        },

        /**
         * Collapses specified master row.
         * @param {Selector | DOM Element} row Target master row to collapse.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // collapses first master row
         * grid.collapseRow(grid.tbody.find(">tr.k-master-row:first"));
         */
        collapseRow: function(tr) {
            $(tr).find('> td .k-minus, > td .k-collapse').click();
        },

        _thead: function() {
            var that = this,
                columns = that.columns,
                hasDetails = that._hasDetails() && columns.length,
                idx,
                length,
                html = "",
                thead = that.table.find("thead"),
                tr,
                th;

            if (!thead.length) {
                thead = $("<thead/>").insertBefore(that.tbody);
            }

            tr = that.table.find("tr").filter(":has(th)");

            if (!tr.length) {
                tr = thead.children().first();
                if(!tr.length) {
                    tr = $("<tr/>");
                }
            }

            if (!tr.children().length) {
                if (hasDetails) {
                    html += '<th class="k-hierarchy-cell">&nbsp;</th>';
                }

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    th = columns[idx];

                    if (!th.command) {
                        html += "<th " + kendo.attr("field") + "='" + th.field + "' ";
                        if (th.title) {
                            html += kendo.attr("title") + '="' + th.title.replace(/'/g, "\'") + '" ';
                        }

                        if (th.groupable !== undefined) {
                            html += kendo.attr("groupable") + "='" + th.groupable + "' ";
                        }

                        if (th.aggregates) {
                            html += kendo.attr("aggregates") + "='" + th.aggregates + "'";
                        }

                        html += ">" + (th.title || th.field || "") + "</th>";
                    } else {
                        html += "<th>" + (th.title || "") + "</th>";
                    }
                }

                tr.html(html);
            } else if (hasDetails) {
                tr.prepend('<th class="k-hierarchy-cell">&nbsp;</th>');
            }

            tr.find("th").addClass("k-header");

            if(!that.options.scrollable) {
                thead.addClass("k-grid-header");
            }

            tr.appendTo(thead);

            that.thead = thead;

            that._sortable();

            that._filterable();

            that._scrollable();

            that._updateCols();

            that._setContentHeight();
        },

        _updateCols: function() {
            var that = this;

            that._appendCols(that.thead.parent().add(that.table));
        },

        _appendCols: function(table) {
            var that = this,
                colgroup = table.find("colgroup"),
                width,
                cols = map(that.columns, function(column) {
                    width = column.width;
                    if (width && parseInt(width) != 0) {
                        return kendo.format('<col style="width:{0}"/>', typeof width === STRING? width : width + "px");
                    }

                    return "<col />";
                }),
                groups = that.dataSource.group().length;

            if (that._hasDetails()) {
                cols.splice(0, 0, '<col class="k-hierarchy-col" />');
            }

            if (colgroup.length) {
                colgroup.remove();
            }

            colgroup = $("<colgroup/>").append($(new Array(groups + 1).join('<col class="k-group-col">') + cols.join("")));

            table.prepend(colgroup);
        },

        _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) {
            var that = this,
                html = "",
                idx,
                length,
                rowTemplate = that.rowTemplate,
                altRowTemplate = that.altRowTemplate;

            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) {
            var that = this,
                html = "",
                idx,
                length,
                field = group.field,
                column = $.grep(that.columns, function(column) { return column.field == field; })[0] || { },
                value = column.format ? kendo.format(column.format, group.value) : group.value,
                template = column.groupHeaderTemplate,
                text =  (column.title || field) + ': ' + value,
                data = extend({}, { field: group.field, value: group.value }, group.aggregates[group.field]),
                groupItems = group.items;

            if (template) {
                text  = typeof template === FUNCTION ? template(data) : kendo.template(template)(data);
            }

            html +=  '<tr class="k-grouping-row">' + groupCells(level) +
                      '<td colspan="' + colspan + '">' +
                        '<p class="k-reset">' +
                         '<a class="k-icon k-collapse" href="#"></a>' + text
                          +'</p></td></tr>';

            if(group.hasSubgroups) {
                for(idx = 0, length = groupItems.length; idx < length; idx++) {
                    html += that._groupRowHtml(groupItems[idx], colspan - 1, level + 1);
                }
            } else {
                html += that._rowsHtml(groupItems);
            }

            if (that.groupFooterTemplate) {
                html += that.groupFooterTemplate(group.aggregates);
            }
            return html;
        },

        /**
         * Collapses specified group.
         * @param {Selector | DOM Element} group Target group item to collapse.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // collapses first group item
         * grid.collapseGroup(grid.tbody.find(">tr.k-grouping-row:first"));
         */
        collapseGroup: function(group) {
            group = $(group).find(".k-icon").addClass("k-expand").removeClass("k-collapse").end();
            var level = group.find(".k-group-cell").length;

            group.nextUntil(function() {
                return $(".k-group-cell", this).length <= level;
            }).hide();
        },

        /**
         * Expands specified group.
         * @param {Selector | DOM Element} group Target group item to expand.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // expands first group item
         * grid.expandGroup(grid.tbody.find(">tr.k-grouping-row:first"));
         */
        expandGroup: function(group) {
            group = $(group).find(".k-icon").addClass("k-collapse").removeClass("k-expand").end();
            var that = this,
                level = group.find(".k-group-cell").length;

            group.nextAll("tr").each(function () {
                var tr = $(this);
                var offset = tr.find(".k-group-cell").length;
                if (offset <= level)
                    return false;

                if (offset == level + 1) {
                    tr.show();

                    if (tr.hasClass("k-grouping-row") && tr.find(".k-icon").hasClass("k-collapse"))
                        that.expandGroup(tr);
                }
            });
        },

        _updateHeader: function(groups) {
            var that = this,
                cells = that.thead.find("th.k-group-cell"),
                length = cells.length;

            if(groups > length) {
                $(new Array(groups - length + 1).join('<th class="k-group-cell k-header">&nbsp;</th>')).prependTo(that.thead.find("tr"));
            } else if(groups < length) {
                length = length - groups;
                $($.grep(cells, function(item, index) { return length > index } )).remove();
            }
        },

        _firstDataItem: function(data, grouped) {
            if(data && grouped) {
                if(data.hasSubgroups) {
                    data = this._firstDataItem(data.items[0], grouped);
                } else {
                    data = data.items[0];
                }
            }
            return data;
        },

        _progress: function(toggle) {
            var that = this,
                element = that.element.is("table") ? that.element.parent() : (that.content && that.content.length ? that.content : that.element);

            kendo.ui.progress(element, toggle);
        },

        /**
         * Reloads the data and repaints the grid.
         * @example
         * // get a reference to the grid widget
         * var grid = $("#grid").data("kendoGrid");
         * // refreshes the grid
         * grid.refresh();
         */
        refresh: function(e) {
            var that = this,
                length,
                idx,
                html = "",
                data = that.dataSource.view(),
                tbody,
                placeholder,
                currentIndex,
                current = that.current(),
                groups = (that.dataSource.group() || []).length,
                colspan = groups + that.columns.length;

            if (e && e.action === "itemchange" && that.editable) { // skip rebinding if editing is in progress
                return;
            }

            that.trigger("dataBinding");

            if (current && current.hasClass("k-state-focused")) {
                currentIndex = that.items().index(current.parent());
            }

            that._distroyEditable();

            that._progress(false);

            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._updateHeader(groups);
                that._group = groups > 0;
            }

            if(groups > 0) {

                if (that.detailTemplate) {
                    colspan++;
                }

                for (idx = 0, length = data.length; idx < length; idx++) {
                    html += that._groupRowHtml(data[idx], colspan, 0);
                }
            } else {
                html += that._rowsHtml(data);
            }

            if (tbodySupportsInnerHtml) {
                that.tbody[0].innerHTML = html;
            } else {
                placeholder = document.createElement("div");
                placeholder.innerHTML = "<table><tbody>" + html + "</tbody></table>";
                tbody = placeholder.firstChild.firstChild;
                that.table[0].replaceChild(tbody, that.tbody[0]);
                that.tbody = $(tbody);
            }

            that._footer();

            if (currentIndex >= 0) {
                that.current(that.items().eq(currentIndex).children().filter(DATA_CELL).first());
            }

            that.trigger(DATABOUND);
       }
   });

   function hasCommand(commands, name) {
       var idx, length, command;

       if (typeof commands === STRING) {
           return commands === name;
       }

       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 true;
               }
           }
       }
       return false;
   };

   ui.plugin(Grid);
   ui.plugin(VirtualScrollable);
})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        CHANGE = "change",
        DATABOUND = "dataBound",
        DATABINDING = "dataBinding",
        Widget = kendo.ui.Widget,
        keys = kendo.keys,
        FOCUSSELECTOR =  ">*",
        CHANGE = "change",
        REQUESTSTART = "requestStart",
        ERROR = "error",
        FOCUSED = "k-state-focused",
        FOCUSABLE = "k-focusable",
        SELECTED = "k-state-selected",
        KEDITITEM = "k-edit-item",
        STRING = "string",
        CLICK = "click",
        EDIT = "edit",
        REMOVE = "remove",
        proxy = $.proxy,
        progress = kendo.ui.progress,
        DataSource = kendo.data.DataSource;

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

            options = $.isArray(options) ? { data: options } : options;

            Widget.fn.init.call(that, element, options);

            options = that.options;

            that.wrapper = that.element;

            that._element();

            that._dataSource();

            that.template = kendo.template(options.template || "");
            that.altTemplate = kendo.template(options.altTemplate || options.template);
            that.editTemplate = kendo.template(options.editTemplate || "");

            that._navigatable();

            that._selectable();

            if (that.options.autoBind){
                that.dataSource.fetch();
            }

            kendo.notify(that);
        },

        events: [
            CHANGE,
            DATABINDING,
            DATABOUND,
            EDIT,
            REMOVE
        ],

        options: {
            name: "ListView",
            autoBind: true,
            template: "",
            altTemplate: "",
            editTemplate: ""
        },

        items: function() {
            return this.element.find(FOCUSSELECTOR);
        },

        setDataSource: function(dataSource) {
            this.options.dataSource = dataSource;
            this._dataSource();

            dataSource.fetch();
        },

        _dataSource: function() {
            var that = this;

            if (that.dataSource && that._refreshHandler) {
                that.dataSource.unbind(CHANGE, that._refreshHandler)
                                .unbind(REQUESTSTART, that._requestStartHandler)
                                .unbind(ERROR, that._errorHandler);
            } else {
                that._refreshHandler = proxy(that.refresh, that);
                that._requestStartHandler = proxy(that._requestStart, that);
                that._errorHandler = proxy(that._error, that);
            }

            that.dataSource = DataSource.create(that.options.dataSource)
                                .bind(CHANGE, that._refreshHandler)
                                .bind(REQUESTSTART, that._requestStartHandler)
                                .bind(ERROR, that._errorHandler);
        },

        _requestStart: function() {
            progress(this.element, true);
        },

        _error: function() {
            progress(this.element, false);
        },

        _element: function() {
            this.element.addClass("k-widget k-listview");
        },

        refresh: function(e) {
            var that = this,
                data = that.dataSource.view(),
                html = "",
                idx,
                length,
                template = that.template,
                altTemplate = that.altTemplate;

            if (e && e.action === "itemchange" && that.editable) {
                return;
            }

            that.trigger(DATABINDING);

            that._destroyEditable();

            for (idx = 0, length = data.length; idx < length; idx++) {
                if (idx % 2) {
                    html += altTemplate(data[idx]);
                } else {
                    html += template(data[idx]);
                }
            }

            that.element.html(html);

            that.trigger(DATABOUND);
        },

        _selectable: function() {
            var that = this,
                multi,
                current,
                selectable = that.options.selectable,
                navigatable = that.options.navigatable;

            if (selectable) {
                multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;

                that.selectable = new kendo.ui.Selectable(that.element, {
                    multiple: multi,
                    filter: FOCUSSELECTOR,
                    change: function() {
                        that.trigger(CHANGE);
                    }
                });

                if (navigatable) {
                    that.element.keydown(function(e) {
                        if (e.keyCode === keys.SPACEBAR) {
                            current = that.current();
                            e.preventDefault();
                            if(multi) {
                                if(!e.ctrlKey) {
                                    that.selectable.clear();
                                } else {
                                    if(current.hasClass(SELECTED)) {
                                        current.removeClass(SELECTED);
                                        current = null;
                                    }
                                }
                            } else {
                                that.selectable.clear();
                            }

                            that.selectable.value(current);
                        }
                    });
                }
            }
        },

        current: function(element) {
            var that = this,
                current = that._current;

            if (element !== undefined && element.length) {
                if (!current || current[0] !== element[0]) {
                    element.addClass(FOCUSED);
                    if (current) {
                        current.removeClass(FOCUSED);
                    }
                    that._current = element;
                }
            }

            return that._current;
        },

        _navigatable: function() {
            var that = this,
                navigatable = that.options.navigatable,
                element = that.element,
                currentProxy = proxy(that.current, that),
                clickCallback = function(e) {
                    currentProxy($(e.currentTarget));
                    if(!$(e.target).is(":button,a,:input,a>.k-icon,textarea")) {
                        element.focus();
                    }
                };

            if (navigatable) {
                element.attr("tabIndex", Math.max(element.attr("tabIndex") || 0, 0));
                element.bind({
                    focus: function() {
                        var current = that._current;
                        if(current && current.is(":visible")) {
                            current.addClass(FOCUSED);
                        } else {
                            currentProxy(element.find(FOCUSSELECTOR).first());
                        }
                    },
                    focusout: function() {
                        if (that._current) {
                            that._current.removeClass(FOCUSED);
                        }
                    },
                    keydown: function(e) {
                        var key = e.keyCode,
                            current = that.current();

                        if (keys.UP === key) {
                            that.current(current ? current.prev() : element.find(FOCUSSELECTOR).first());
                        } else if (keys.DOWN === key) {
                            that.current(current ? current.next() : element.find(FOCUSSELECTOR).first());
                        } else if (keys.PAGEUP == key) {
                            that._current = null;
                            that.dataSource.page(that.dataSource.page() - 1);
                        } else if (keys.PAGEDOWN == key) {
                            that._current = null;
                            that.dataSource.page(that.dataSource.page() + 1);
                        }
                    }
                });

                element.addClass(FOCUSABLE).delegate("." + FOCUSABLE + FOCUSSELECTOR, "mousedown", clickCallback);
            }
       },

       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.distroy();
               delete that.editable;
           }
       },

       _closeEditable: function(validate) {
           var that = this,
               editable = that.editable,
               data,
               container,
               valid = true;

           if (editable) {
               if (validate) {
                   valid = editable.end();
               }

               if (valid) {
                   data = that.dataSource.view()[editable.element.index()],
                   container = $(that.template(data))
                   that._destroyEditable();
                   editable.element.replaceWith(container);
               }
           }

           return valid;
       },

       edit: function(item) {
           var that = this,
               data = that.dataSource.view()[item.index()],
               container = $(that.editTemplate(data)).addClass(KEDITITEM);

            that.cancel();
            item.replaceWith(container);
            that.editable = container.kendoEditable({ model: data, clearContainer: false, errorTemplate: false }).data("kendoEditable");

            that.trigger(EDIT, { model: data, item: container });
       },

       save: function() {
           if (this._closeEditable(true)) {
               this.dataSource.sync();
           }
       },

       remove: function(item) {
           var that = this,
               dataSource = that.dataSource,
               data = dataSource.view()[item.index()];

           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,
               data,
               index = -1;

           if (that.editable) {
               index = that.editable.element.index();
           }

           if (index != -1) {
               data = dataSource.view()[index];
               dataSource.cancelChanges(data);
               that._closeEditable(false);
           }
       }

    });

    kendo.ui.plugin(ListView);
})(jQuery);
(function($,undefined) {

    // Imports ================================================================
    var kendo = window.kendo,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        extend = $.extend,
        deepExtend = kendo.deepExtend,
        keys = kendo.keys;

    // options can be: template (as string), cssClass, title, defaultValue
    var ToolTemplate = Class.extend({
        init: function(options) {
            var that = this;
            that.options = options;
        },

        getHtml: function() {
            var options = this.options;
            return kendo.template(options.template)({
                cssClass: options.cssClass,
                tooltip: options.title,
                initialValue: options.initialValue
            });
        }
    });

    var EditorUtils = {
        select: function(editor) {
            editor.trigger("select", {});
        },

        editorWrapperTemplate:
            '<table cellspacing="4" cellpadding="0" class="k-widget k-editor k-header"><tbody>' +
                '<tr><td class="k-editor-toolbar-wrap"><ul class="k-editor-toolbar"></ul></td></tr>' +
                '<tr><td class="k-editable-area"></td></tr>' +
            '</tbody></table>',

        buttonTemplate:
            '<li class="k-editor-button">' +
                '<a href="" class="k-tool-icon #= cssClass #" unselectable="on" title="#= tooltip #">#= tooltip #</a>' +
            '</li>',

        colorPickerTemplate:
            '<li class="k-editor-colorpicker">' +
                '<div class="k-widget k-colorpicker k-header #= cssClass #">' +
                    '<span class="k-tool-icon"><span class="k-selected-color"></span></span><span class="k-icon k-arrow-down"></span>' +
            '</div></li>',

        comboBoxTemplate:
            '<li class="k-editor-combobox">' +
                '<select title="#= tooltip #" class="#= cssClass #"></select>' +
//                '<div class="k-widget k-combobox k-header #= cssClass #">' +
//                    '<div class="k-dropdown-wrap k-state-default">' +
//                        '<input class="k-input" id="-input" title="#= tooltip #" type="text" value="#=initialValue#" />' +
//                        '<span class="k-select k-header"><span class="k-icon k-arrow-down">select</span></span>' +
//                    '</div><input style="display:none" type="text" value="inherit" /></div>' +
            '</li>',

        dropDownListTemplate:
            '<li class="k-editor-selectbox">' +
                '<select title="#= tooltip #" class="#= cssClass #"></select>' +
//                '<div class="k-selectbox k-header #= cssClass #"><div class="k-dropdown-wrap k-state-default">' +
//                    '<span class="k-input">#=initialValue#</span><span class="k-select"><span class="k-icon k-arrow-down">select</span></span>' +
//                '</div></div>' +
            '</li>',

        focusable: ".k-colorpicker,a.k-tool-icon:not(.k-state-disabled),.k-selectbox, .k-combobox .k-input",

        wrapTextarea: function(textarea) {

            var w = textarea.width(),
                h = textarea.height(),
                template = EditorUtils.editorWrapperTemplate,
                editorWrap = $(template).insertBefore(textarea).width(w).height(h),
                editArea = editorWrap.find(".k-editable-area"),
                toolsArea = editorWrap.find(".k-editor-toolbar");

            textarea.appendTo(editArea).addClass("k-content k-raw-content").hide();

            return textarea.closest(".k-editor");
        },

        renderTools: function(editor, tools) {
            var editorTools = {},
                currentTool, tool, i,
                nativeTools = editor._nativeTools,
                template,
                options,
                toolsArea = $(editor.element).closest(".k-editor").find(".k-editor-toolbar");

            if (tools) {
                for (j = 0; j < tools.length; j++) {
                    currentTool = tools[j];

                    if ($.isPlainObject(currentTool)) {
                        options = extend({ cssClass: "k-custom", type: "button", tooltip: "" }, currentTool);

                        if (options.name) {
                            options.cssClass = "k-" + options.name;
                        }

                        if (!options.template) {
                            if (options.type == "button") {
                                options.template = EditorUtils.buttonTemplate;
                            }
                        }
                    } else if (editor._tools[currentTool]) {
                        editorTools[currentTool] = editor._tools[currentTool];
                        options = editorTools[currentTool].options;
                    }

                    template = options.template;

                    if (template) {

                        if (template.getHtml) {
                            template = template.getHtml();
                        } else {
                            if (!$.isFunction(template)) {
                                template = kendo.template(template);
                            }

                            template = template(options);
                        }

                        tool = $(template).appendTo(toolsArea);

                        if (options.type == "button" && options.exec) {
                            tool.find(".k-tool-icon").click($.proxy(options.exec, editor.element[0]));
                        }
                    }
                }
            }

            for (j = 0; j < nativeTools.length; j++) {
                editorTools[nativeTools[j]] = editor._tools[nativeTools[j]];
            }

            editor.options.tools = editorTools;
        },

        createContentElement: function($textarea, stylesheets) {
            $textarea.hide();
            var iframe = $("<iframe />", { src: 'javascript:"<html></html>"', frameBorder: "0" })
                            .css("display", "")
                            .addClass("k-content")
                            .insertBefore($textarea)[0];

            var window = iframe.contentWindow || iframe;
            var document = window.document || iframe.contentDocument;

            var html = $textarea.val()
                        // <img>\s+\w+ creates invalid nodes after cut in IE
                        .replace(/(<\/?img[^>]*>)[\r\n\v\f\t ]+/ig, "$1")
                        // indented HTML introduces problematic ranges in IE
                        .replace(/[\r\n\v\f\t ]+/ig, " ");

            if (!html.length && $.browser.mozilla)
                html = '<br _moz_dirty="true" />';

            var rtlStyle = $textarea.closest(".k-rtl").length ? "direction:rtl;" : "";

            document.designMode = "On";
            document.open();
            document.write(
                    '<!DOCTYPE html><html><head>' +
                    '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
                    '<style type="text/css">' +
                        'html,body{padding:0;margin:0;font-family:Verdana,Geneva,sans-serif;background:#fff;}' +
                        'html{font-size:100%}body{font-size:.75em;line-height:1.5;padding-top:1px;margin-top:-1px;' +
                        rtlStyle +
                        '}' +
                        '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{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}' +
                        'ul,ol{padding-left:2.5em}' +
                        'a{color:#00a}' +
                        'code{font-size:1.23em}' +
                    '</style>' +
                    $.map(stylesheets, function(href){ return ['<link type="text/css" href="', href, '" rel="stylesheet"/>'].join(''); }).join('') +
                    '</head><body spellcheck="false">' +
                    html +
                    '</body></html>'
                );

            document.close();

            return window;
        },

        initializeContentElement: function(editor) {
            var isFirstKeyDown = true;

            editor.window = EditorUtils.createContentElement($(editor.textarea), editor.options.stylesheets);
            editor.document = editor.window.contentDocument || editor.window.document;
            editor.body = editor.document.body;

            $(editor.document)
                .bind({
                    keydown: function (e) {
                        if (e.keyCode === keys.F10) {
                            // Handling with timeout to avoid the default IE menu
                            setTimeout(function() {
                                var TABINDEX = "tabIndex",
                                    element = editor.wrapper,
                                    tabIndex = element.attr(TABINDEX);

                                // Chrome can't focus something which has already been focused
                                element.attr(TABINDEX, tabIndex || 0).focus().find("li:has(" + focusable + ")").first().focus();

                                if (!tabIndex && tabIndex !== 0) {
                                   element.removeAttr(TABINDEX);
                                }

                            }, 100);

                            e.preventDefault();
                            return;
                        }

                        var toolName = editor.keyboard.toolFromShortcut(editor.options.tools, e);

                        if (toolName) {
                            e.preventDefault();
                            if (!/undo|redo/.test(toolName)) {
                                editor.keyboard.endTyping(true);
                            }
                            editor.exec(toolName);
                            return false;
                        }

                        if (editor.keyboard.isTypingKey(e) && editor.pendingFormats.hasPending()) {
                            if (isFirstKeyDown) {
                                isFirstKeyDown = false;
                            } else {
                                var range = editor.getRange();
                                editor.pendingFormats.apply(range);
                                editor.selectRange(range);
                            }
                        }

                        editor.keyboard.clearTimeout();

                        editor.keyboard.keydown(e);
                    },
                    keyup: function (e) {
                        var selectionCodes = [8, 9, 33, 34, 35, 36, 37, 38, 39, 40, 40, 45, 46];

                        if ($.browser.mozilla && e.keyCode == keys.BACKSPACE) {
                            fixBackspace(editor, e);
                        }

                        if ($.inArray(e.keyCode, selectionCodes) > -1 || (e.keyCode == 65 && e.ctrlKey && !e.altKey && !e.shiftKey)) {
                            editor.pendingFormats.clear();
                            select(editor);
                        }

                        if (editor.keyboard.isTypingKey(e)) {
                            if (editor.pendingFormats.hasPending()) {
                                var range = editor.getRange();
                                editor.pendingFormats.apply(range);
                                editor.selectRange(range);
                            }
                        } else {
                            isFirstKeyDown = true;
                        }

                        editor.keyboard.keyup(e);
                    },
                    mousedown: function(e) {
                        editor.pendingFormats.clear();

                        var target = $(e.target);

                        if (!$.browser.gecko && e.which == 2 && target.is("a[href]"))
                        window.open(target.attr("href"), "_new");
                    },
                    mouseup: function () {
                        select(editor);
                    }
                });

            $(editor.window)
                .bind("blur", function () {
                    var old = editor.textarea.value,
                    value = editor.encodedValue();

                    editor.update(value);

                    if (value != old) {
                        editor.trigger("change");
                    }
                });

            $(editor.body)
                .bind("cut paste", function (e) {
                      editor.clipboard["on" + e.type](e);
                  });
        },

        fixBackspace: function(editor, e) {

            var range = editor.getRange(),
                startContainer = range.startContainer,
                dom = kendo.ui.editor.Dom;

            if (startContainer == editor.body.firstChild || !dom.isBlock(startContainer)
            || (startContainer.childNodes.length > 0 && !(startContainer.childNodes.length == 1 && dom.is(startContainer.firstChild, "br"))))
                return;

            var previousBlock = startContainer.previousSibling;

            while (previousBlock && !dom.isBlock(previousBlock))
                previousBlock = previousBlock.previousSibling;

            if (!previousBlock)
                return;

            var walker = editor.document.createTreeWalker(previousBlock, NodeFilter.SHOW_TEXT, null, false);

            var textNode;

            while (textNode = walker.nextNode())
                previousBlock = textNode;

            range.setStart(previousBlock, dom.isDataNode(previousBlock) ? previousBlock.nodeValue.length : 0);
            range.collapse(true);
            kendo.ui.editor.RangeUtils.selectRange(range);

            dom.remove(startContainer);

            e.preventDefault();
        },

        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 tools = Editor.fn._tools;
            tools[toolName] = tool;
            if (tools[toolName].options && tools[toolName].options.template) {
                tools[toolName].options.template.options.cssClass = "k-" + toolName;
            }
        },

        registerFormat: function(formatName, format) {
            kendo.ui.Editor.fn.options.formats[formatName] = format;
        }
    };

    var select = EditorUtils.select,
        focusable = EditorUtils.focusable,
        wrapTextarea = EditorUtils.wrapTextarea,
        renderTools = EditorUtils.renderTools,
        createContentElement = EditorUtils.createContentElement,
        initializeContentElement = EditorUtils.initializeContentElement,
        fixBackspace = EditorUtils.fixBackspace;

    var localization = {
        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",
        insertHtml: "Insert HTML",
        fontName: "Select font family",
        fontNameInherit: "(inherited font)",
        fontSize: "Select font size",
        fontSizeInherit: "(inherited size)",
        formatBlock: "Format",
        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."
    };

    var emptyFinder = function () { return { isFormatted: function () { return false } } };
    var supportedBrowser = !kendo.support.mobileOS || (kendo.support.mobileOS.ios && kendo.support.mobileOS.majorVersion >= 5);

    var Editor = Widget.extend({
        init: function (element, options) {
            /* suppress initialization in mobile webkit devices (w/o proper contenteditable support) */
            if (!supportedBrowser) {
                return;
            }

            var that = this,
                wrapper;

            Widget.fn.init.call(that, element, options);

            that.options = deepExtend({}, that.options, options);

            element = $(element);

            element.closest("form").bind("submit", function () {
                that.update();
            });

            for (var id in that._tools) {
                that._tools[id].name = id.toLowerCase();
            }

            that.textarea = element.attr("autocomplete", "off")[0];

            wrapper = that.wrapper = wrapTextarea(element);

            renderTools(that, that.options.tools);

            initializeContentElement(that);

            that.keyboard = new kendo.ui.editor.Keyboard([new kendo.ui.editor.TypingHandler(that), new kendo.ui.editor.SystemHandler(that)]);

            that.clipboard = new kendo.ui.editor.Clipboard(this);

            that.pendingFormats = new kendo.ui.editor.PendingFormats(this);

            that.undoRedoStack = new kendo.ui.editor.UndoRedoStack();

            if (options && options.value) {
                that.value(options.value);
            }

            function toolFromClassName(element) {
                var tool = $.grep(element.className.split(" "), function (x) {
                    return !/^k-(widget|tool-icon|state-hover|header|combobox|dropdown|selectbox|colorpicker)$/i.test(x);
                });
                return tool[0] ? tool[0].substring(2) : "custom";
            }

            function appendShortcutSequence(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;
            }

            var toolbarItems = ".k-editor-toolbar > li > *, .k-editor-toolbar > li select",
                buttons = ".k-editor-button .k-tool-icon",
                enabledButtons = buttons + ":not(.k-state-disabled)",
                disabledButtons = buttons + ".k-state-disabled";

             wrapper.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();
                    }
                }
            });

            wrapper
                .delegate(enabledButtons, "mouseenter", function() { $(this).addClass("k-state-hover")})
                .delegate(enabledButtons, "mouseleave", function() { $(this).removeClass("k-state-hover")})
                .delegate(buttons, "mousedown", false)
                .delegate(focusable, "keydown", function(e) {
                    var closestLi = $(this).closest("li"),
                    focusableTool = "li:has(" + focusable + ")",
                    focusElement,
                    keyCode = e.keyCode;

                    if (keyCode == keys.RIGHT) {
                        focusElement = closestLi.nextAll(focusableTool).first().find(focusable);
                    } else if (keyCode == keys.LEFT) {
                        focusElement = closestLi.prevAll(focusableTool).last().find(focusable);
                    } else if (keyCode == keys.ESC) {
                        focusElement = that;
                    } 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 = closestLi.prevAll(focusableTool).last().find(focusable);

                            if (focusElement.length) {
                                e.preventDefault();
                            } else {
                                return;
                            }
                        } else {
                            e.preventDefault();

                            focusElement = closestLi.nextAll(focusableTool).first().find(focusable);

                            if (focusElement.length == 0) {
                                focusElement = that;
                            }
                        }
                    }

                    if (focusElement) {
                        focusElement.focus();
                    }
                })
                .delegate(enabledButtons, "click", function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    that.exec(toolFromClassName(this));
                })
                .delegate(disabledButtons, "click", function(e) { e.preventDefault(); })
                .find(toolbarItems)
                    .each(function () {
                        var toolName = toolFromClassName(this),
                            options = that.options,
                            tool = options.tools[toolName],
                            description = options.localization[toolName],
                            $this = $(this);

                        if (!tool) {
                            return;
                        }

                        if (toolName == "fontSize" || toolName == "fontName") {
                            var inheritText = options.localization[toolName + "Inherit"] || localization[toolName + "Inherit"]
                            options[toolName][0].Text = inheritText;
                            $this.find("input").val(inheritText).end()
                                 .find("span.k-input").text(inheritText).end();
                        }

                        tool.initialize($this, {
                            title: appendShortcutSequence(description, tool),
                            editor: that
                        });

                    });

                that.bind("select", function() {
                    var range = that.getRange();

                    var nodes = kendo.ui.editor.RangeUtils.textNodes(range);

                    if (!nodes.length) {
                        nodes = [range.startContainer];
                    }

                    wrapper.find(toolbarItems)
                        .each(function () {
                            var tool = that.options.tools[toolFromClassName(this)];
                            if (tool) {
                                tool.update($(this), nodes, that.pendingFormats);
                            }
                        });
                });

            $(document)
                .bind("DOMNodeInserted", function(e) {
                    if ($.contains(e.target, that.wrapper[0]) || that.wrapper[0] == e.target) {
                        // preserve updated value before re-initializing
                        // don't use update() to prevent the editor from encoding the content too early
                        that.textarea.value = that.value();
                        that.wrapper.find("iframe").remove();
                        initializeContentElement(that);
                    }
                })
                .bind("mousedown", function(e) {
                    try {
                        if (that.keyboard.isTypingInProgress())
                            that.keyboard.endTyping(true);

                        if (!that.selectionRestorePoint) {
                            that.selectionRestorePoint = new kendo.ui.editor.RestorePoint(that.getRange());
                        }
                    } catch (e) { }
                });

            kendo.notify(that);
        },

        events: [
            "select",
            "change",
            "execute",
            "error",
            "paste"
        ],

        options: {
            name: "Editor",
            localization: localization,
            formats: {},
            encoded: true,
            stylesheets: [],
            dialogOptions: {
                modal: true, resizable: false, draggable: true,
                animation: false
            },
            fontName: [
                { Text: localization.fontNameInherit,  Value: "inherit" },
                { 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: localization.fontSizeInherit,  Value: "inherit" },
                { 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: [
                "bold",
                "italic",
                "underline",
                "strikethrough",
                "fontName",
                "fontSize",
                "foreColor",
                "backColor",
                "justifyLeft",
                "justifyCenter",
                "justifyRight",
                "justifyFull",
                "insertUnorderedList",
                "insertOrderedList",
                "indent",
                "outdent",
                "formatBlock",
                "createLink",
                "unlink",
                "insertImage"
                //"style",
                //"subscript",
                //"superscript",
            ]
        },

        _nativeTools: [
            "insertLineBreak",
            "insertParagraph",
            "redo",
            "undo"
        ],

        _tools: {
            undo: { options: { key: "Z", ctrl: true } },
            redo: { options: { key: "Y", ctrl: true } }
        },

        value: function (html) {
            var body = this.body,
                dom = kendo.ui.editor.Dom;
            if (html === undefined) return kendo.ui.editor.Serializer.domToXhtml(body);

            this.pendingFormats.clear();

            // handle null value passed as a parameter
            html = html || "";

            // Some browsers do not allow setting CDATA sections through innerHTML so we encode them as comments
            html = html.replace(/<!\[CDATA\[(.*)?\]\]>/g, "<!--[CDATA[$1]]-->");

            // Encode script tags to avoid execution and lost content (IE)
            html = html.replace(/<script([^>]*)>(.*)?<\/script>/ig, "<telerik:script $1>$2<\/telerik:script>");

            // Add <br/>s to empty paragraphs in mozilla
            if ($.browser.mozilla)
                html = html.replace(/<p([^>]*)>(\s*)?<\/p>/ig, '<p $1><br _moz_dirty="" /><\/p>');

            if ($.browser.msie && parseInt($.browser.version) < 9) {
                // 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"');

                body.innerHTML = html;
                dom.remove(body.firstChild);

                $(body).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 {
                body.innerHTML = html;
                if ($.browser.msie) {
                    // having unicode characters creates denormalized DOM tree in IE9
                    dom.normalize(body);
                }
            }

            this.selectionRestorePoint = null;
            this.update();
        },

        focus: function () {
            this.window.focus();
        },

        update: function (value) {
            this.textarea.value = value || this.options.encoded ? this.encodedValue() : this.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.focus();
            var selection = this.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
        },

        getRange: function () {
            var selection = this.getSelection();
            var range = selection.rangeCount > 0 ? selection.getRangeAt(0) : this.createRange();

            if (range.startContainer == this.document && range.endContainer == this.document && range.startOffset == 0 && range.endOffset == 0) {
                range.setStart(this.body, 0);
                range.collapse(true);
            }

            return range;
        },

        selectedHtml: function() {
            return kendo.ui.editor.Serializer.domToXhtml(this.getRange().cloneContents());
        },

        paste: function (html) {
            this.clipboard.paste(html);
        },

        exec: function (name, params) {
            var that = this,
                range, body, id,
                tool = "", pendingTool;

            name = name.toLowerCase();

            // restore selection
            if (!that.keyboard.isTypingInProgress()) {
                that.focus();

                range = that.getRange();
                body = that.document.body;
            }

            // exec tool
            for (id in that.options.tools) {
                if (id.toLowerCase() == name) {
                    tool = that.options.tools[id];
                    break;
                }
            }

            if (tool) {
                range = that.getRange();

                if (!/undo|redo/i.test(name) && tool.willDelayExecution(range)) {
                    // clone our tool to apply params only once
                    pendingTool = $.extend({}, tool);
                    $.extend(pendingTool.options, { params: params });
                    that.pendingFormats.toggle(pendingTool);
                    select(that);
                    return;
                }

                var command = tool.command ? tool.command(extend({ range: range }, params)) : null;

                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(function () { select(that); }, that);
                        return;
                    }
                }

                select(that);
            }
        }
    });

    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: function() {
        },

        willDelayExecution: function() {
            return false;
        }

    });

    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, pendingFormats) {
            var isPending = pendingFormats.isPending(this.name),
                isFormatted = this.options.finder.isFormatted(nodes),
                isActive = isPending ? !isFormatted : isFormatted;

            $ui.toggleClass("k-state-active", isActive);
        }
    });

    // Exports ================================================================

    extend(kendo.ui, {
        editor:{
            ToolTemplate: ToolTemplate,
            EditorUtils: EditorUtils,
            Tool: Tool,
            FormatTool: FormatTool
        }
    });

})(jQuery);
(function($) {

var kendo = window.kendo,
    Class = kendo.Class,
    map = $.map,
    extend = $.extend,
    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(",")),
    blockElements = "div,p,h1,h2,h3,h4,h5,h6,address,applet,blockquote,button,center,dd,dir,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,pre,script,table,tbody,td,tfoot,th,thead,tr,ul".split(","),
    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".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 && parseInt($.browser.version) >= 8) {
    normalize = function(parent) {
        if (parent.nodeType == 1 && parent.firstChild) {
            var prev = parent.firstChild,
                node = prev;

            while (node = node.nextSibling) {
                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,
    amp = /&/g,
    openTag = /</g,
    closeTag = />/g,
    nbsp = /\u00a0/g,
    bom = /\ufeff/g;
var 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(",");

var Dom = {
    findNodeIndex: function(node) {
        var i = 0;
        while (node = node.previousSibling) 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);
        node.deleteData(offset, node.length);
        newNode.deleteData(0, offset);
        Dom.insertAfter(newNode, node);
    },

    attrEquals: function(node, attributes) {
        for (var key in attributes) {
            var value = node[key];

            if (key == FLOAT)
                value = node[$.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,
    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) {
            return x = parseInt(x).toString(16), x.length > 1 ? x : "0" + x;
        }).join("");
    },

    encode: function (value) {
        return value.replace(amp, "&amp;")
                .replace(openTag, "&lt;")
                .replace(closeTag, "&gt;")
                .replace(nbsp, "&nbsp;");
    },

    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) {
        if (node.nodeType == 3)
            return node;

        var result = null;

        for (var child = node.lastChild; child; child = child.previousSibling)
            if (result = Dom.lastTextNode(child))
                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)];
    },

    scrollTo: function (node) {
        node.ownerDocument.body.scrollTop = $(Dom.isDataNode(node) ? node.parentNode : node).offset().top;
    },

    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);
    },

    trim: function (parent) {
        for (var i = parent.childNodes.length - 1; i >= 0; i--) {
            var node = parent.childNodes[i];
            if (Dom.isDataNode(node)) {
                if (node.nodeValue.replace(bom, "").length == 0)
                    Dom.remove(node);
                if (Dom.isWhitespace(node))
                    Dom.insertBefore(node, parent);
            } else if (node.className != KMARKER) {
                Dom.trim(node);
                if (node.childNodes.length == 0 && !Dom.isEmpty(node))
                    Dom.remove(node);
            }
        }

        return parent;
    },

    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) {
        var newElement = Dom.create(referenceElement.ownerDocument, tagName),
            attributes = referenceElement.attributes,
            i, len, name, value, attribute;

        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;
    },

    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;
        }
        return extend(element, attributes);
    },

    style: function (node, value) {
        $(node).css(value || {});
    },

    unstyle: function (node, value) {
        for (var key in value) {
            if (key == FLOAT)
                key = $.support.cssFloat ? CSSFLOAT : STYLEFLOAT;

            node.style[key] = "";
        }

        if (node.style.cssText == "")
            node.removeAttribute(STYLE);
    },

    inlineStyle: function(document, name, attributes) {
        var span = $(Dom.create(document, name, attributes)),
            style;

        document.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;
    },

    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;
    }
}

kendo.ui.editor.Dom = Dom;

})(jQuery);
(function($, undefined) {

// Imports ================================================================
var doc = document,
    kendo = window.kendo,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    extend = $.extend;

var fontSizeMappings = 'xx-small,x-small,small,medium,large,x-large,xx-large'.split(','),
    quoteRe = /"/g,
    brRe = /<br[^>]*>/i,
    emptyPRe = /<p><\/p>/i;

var Serializer = {
    domToXhtml: function(root) {
        var result = [];
        var tagMap = {
            'telerik:script': { start: function (node) { result.push('<script'); attr(node); result.push('>'); }, end: function () { result.push('</script>') } },
            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>') } },
            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 (node) {
                    result.push('</span>');
                }
            }
        };

        function attr(node) {
            var specifiedAttributes = [],
                attributes = node.attributes,
                trim = $.trim;

            if (dom.is(node, 'img')) {
                var width = node.style.width,
                    height = node.style.height,
                    $node = $(node);

                if (width) {
                    $node.attr('width', parseInt(width));
                    dom.unstyle(node, { width: undefined });
                }

                if (height) {
                    $node.attr('height', parseInt(height));
                    dom.unstyle(node, { height: undefined });
                }
            }

            for (var i = 0, l = attributes.length; i < l; i++) {
                var attribute = attributes[i];
                var name = attribute.nodeName;
                // In IE < 8 the 'value' attribute is not returned as 'specified'. The same goes for type="text"
                if (attribute.specified || (name == 'value' && node.value != '') || (name == 'type' && attribute.nodeValue == 'text'))
                    if (name.indexOf('_moz') < 0 && name != 'complete')
                        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 (var i = 0, l = specifiedAttributes.length; i < l; i++) {
                var attribute = specifiedAttributes[i];
                var attributeName = attribute.nodeName;
                var attributeValue = attribute.nodeValue;

                result.push(' ');
                result.push(attributeName);
                result.push('="');
                if (attributeName == 'style') {
                    // In IE < 8 the style attribute does not return proper nodeValue
                    var css = trim(attributeValue || node.style.cssText).split(';');

                    for (var cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
                        var pair = css[cssIndex];
                        if (pair.length) {
                            var propertyAndValue = pair.split(':');
                            var property = trim(propertyAndValue[0].toLowerCase()),
                                value = trim(propertyAndValue[1]);

                            if (property == "font-size-adjust" || property == "font-stretch") {
                                continue;
                            }

                            if (property.indexOf('color') >= 0)
                                value = dom.toHex(value);

                            if (property.indexOf('font') >= 0) {
                                value = value.replace(quoteRe, "'");
                            }

                            result.push(property);
                            result.push(':');
                            result.push(value);
                            result.push(';');
                        }
                    };
                } else if (attributeName == 'src' || attributeName == 'href') {
                    result.push(node.getAttribute(attributeName, 2));
                } else {
                    result.push(dom.fillAttrs[attributeName] ? attributeName : attributeValue);
                }

                result.push('"');
            }
        }

        function children(node, skip) {
            for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling)
                child(childNode, skip);
        }

        function child(node, skip) {
            var nodeType = node.nodeType,
                tagName, mapper,
                parent, value, previous;

            if (nodeType == 1) {
                tagName = dom.name(node);

                if (tagName == "" || (node.attributes['_moz_dirty'] && dom.is(node, 'br')))
                    return;

                mapper = tagMap[tagName];

                if (mapper) {
                    mapper.start(node);
                    children(node);
                    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 = node.nodeValue;

                if (!skip && $.support.leadingWhitespace) {
                    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(dom.encode(value));

            } 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('>');
                }
            }
        }

        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
});

})(jQuery);
(function($) {

    // Imports ================================================================
    var doc = document,
        kendo = window.kendo,
        Class = kendo.Class,
        extend = $.extend,
        Editor = kendo.ui.editor,
        dom = Editor.Dom,
        findNodeIndex = dom.findNodeIndex,
        isDataNode = dom.isDataNode,
        findClosestAncestor = dom.findClosestAncestor,
        getNodeLength = dom.getNodeLength,
        normalize = dom.normalize;

var START_TO_START = 0,
    START_TO_END = 1,
    END_TO_END = 2,
    END_TO_START = 3;

var SelectionUtils = {
    selectionFromWindow: function(window) {
        if ($.browser.msie && $.browser.version < 9) {
            return new W3CSelection(window.document);
        }

        return window.getSelection();
    },

    selectionFromRange: function(range) {
        var document = RangeUtils.documentFromRange(range);
        return SelectionUtils.selectionFromDocument(document);
    },

    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) {
        if (toStart)
            this.setEnd(this.startContainer, this.startOffset);
        else
            this.setStart(this.endContainer, this.endOffset);
    },

    // Editing Methods

    deleteContents: function () {
        var range = this.cloneRange();

        if (this.startContainer != this.commonAncestorContainer)
            this.setStartAfter(findClosestAncestor(this.commonAncestorContainer, this.startContainer));

        this.collapse(true);

        (function deleteSubtree(iterator) {
            while (iterator.next())
                iterator.hasPartialSubtree() ? deleteSubtree(iterator.getSubtreeIterator())
                                            : iterator.remove();
        })(new RangeIterator(range));
    },

    cloneContents: function () {
        // clone subtree
        var document = RangeUtils.documentFromRange(this);
        return (function cloneSubtree(iterator) {
                for (var node, frag = document.createDocumentFragment(); 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 range = this.cloneRange();

        if (this.startContainer != this.commonAncestorContainer)
            this.setStartAfter(findClosestAncestor(this.commonAncestorContainer, this.startContainer));

        this.collapse(true);

        var self = this;

        var document = RangeUtils.documentFromRange(this);

        return (function extractSubtree(iterator) {
            for (var node, frag = document.createDocumentFragment(); node = iterator.next(); ) {
                iterator.hasPartialSubtree() ? node = node.cloneNode(false) : iterator.remove(self.originalRange);

                if (iterator.hasPartialSubtree())
                    node.appendChild(extractSubtree(iterator.getSubtreeIterator()));

                frag.appendChild(node);
            }

            return frag;
        })(new RangeIterator(range));
    },

    insertNode: function (node) {
        if (isDataNode(this.startContainer)) {
            if (this.startOffset != this.startContainer.nodeValue.length)
                dom.splitDataNode(this.startContainer, this.startOffset);

            dom.insertAfter(node, this.startContainer);
        } else {
            dom.insertAt(this.startContainer, node, this.startOffset);
        }

        this.setStart(this.startContainer, this.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 current = this._current = this._next;
        this._next = this._current && this._current.nextSibling != this._end ?
        this._current.nextSibling : null;

        if (isDataNode(this._current)) {
            if (this.range.endContainer == this._current)
                (current = current.cloneNode(true)).deleteData(this.range.endOffset, current.length - this.range.endOffset);

            if (this.range.startContainer == this._current)
                (current = current.cloneNode(true)).deleteData(0, this.range.startOffset);
        }

        return current;
    },

    traverse: function (callback) {
        function next() {
            this._current = this._next;
            this._next = this._current && this._current.nextSibling != this._end ? this._current.nextSibling : null;
            return this._current;
        }

        var current;

        while (current = next.call(this)) {
            if (this.hasPartialSubtree())
                this.getSubtreeIterator().traverse(callback);
            else
                callback(current)
        }

        return current;
    },

    remove: function (originalRange) {
        var inStartContainer = this.range.startContainer == this._current;
        var inEndContainer = this.range.endContainer == this._current;

        if (isDataNode(this._current) && (inStartContainer || inEndContainer)) {
            var start = inStartContainer ? this.range.startOffset : 0;
            var end = inEndContainer ? this.range.endOffset : this._current.length;
            var delta = end - start;

            if (originalRange && (inStartContainer || inEndContainer)) {
                if (this._current == originalRange.startContainer && start <= originalRange.startOffset)
                    originalRange.startOffset -= delta;

                if (this._current == originalRange.endContainer && end <= originalRange.endOffset)
                    originalRange.endOffset -= delta;
            }

            this._current.deleteData(start, delta);
        } else {
            var parent = this._current.parentNode;

            if (originalRange && (this.range.startContainer == parent || this.range.endContainer == parent)) {
                var nodeIndex = findNodeIndex(this._current);

                if (parent == originalRange.startContainer && nodeIndex <= originalRange.startOffset)
                    originalRange.startOffset -= 1;

                if (parent == originalRange.endContainer && nodeIndex < originalRange.endOffset)
                    originalRange.endOffset -= 1;
            }

            dom.remove(this._current);
        }
    },

    hasPartialSubtree: function () {
        return !isDataNode(this._current) &&
        (dom.isAncestorOrSelf(this._current, this.range.startContainer) ||
            dom.isAncestorOrSelf(this._current, this.range.endContainer));
    },

    getSubtreeIterator: function () {
        var subRange = this.range.cloneRange();
        subRange.selectNodeContents(this._current);

        if (dom.isAncestorOrSelf(this._current, this.range.startContainer))
            subRange.setStart(this.range.startContainer, this.range.startOffset);
        if (dom.isAncestorOrSelf(this._current, this.range.endContainer))
            subRange.setEnd(this.range.endContainer, this.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 () {
        this.ownerDocument.selection.empty();
    },

    getRangeAt: function () {
        var textRange, range = new W3CRange(this.ownerDocument), selection = this.ownerDocument.selection, element;

        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 {
            adoptEndPoint(textRange, range, true);
            adoptEndPoint(textRange, range, 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 adoptContainer(textRange, range, start) {
    // find anchor node and offset
    var container = range[start ? 'startContainer' : 'endContainer'];
    var offset = range[start ? 'startOffset' : 'endOffset'], textOffset = 0;
    var anchorNode = isDataNode(container) ? container : container.childNodes[offset] || null;
    var anchorParent = isDataNode(container) ? container.parentNode : container;
    // visible data nodes need a text offset
    if (container.nodeType == 3 || container.nodeType == 4)
        textOffset = offset;

    // create a cursor element node to position range (since we can't select text nodes)
    var cursorNode = anchorParent.insertBefore(dom.create(range.ownerDocument, 'a'), anchorNode);

    var cursor = range.ownerDocument.body.createTextRange();
    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, start) {
    var cursorNode = dom.create(range.ownerDocument, 'a'), cursor = textRange.duplicate();
    cursor.collapse(start);
    var parent = cursor.parentElement();
    do {
        parent.insertBefore(cursorNode, cursorNode.previousSibling);
        cursor.moveToElementText(cursorNode);
    } while (cursor.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', textRange) > 0 && cursorNode.previousSibling);

    cursor.setEndPoint(start ? 'EndToStart' : 'EndToEnd', textRange);

    var target = cursorNode.nextSibling;

    if (!target) {
        // at end of text node
        target = cursorNode.previousSibling;

        if (target && isDataNode(target)) { // in case of collapsed range in empty tag
            range.setEnd(target, target.nodeValue.length);
            dom.remove(cursorNode);
        } else {
            range.selectNodeContents(parent);
            dom.remove(cursorNode);
            range.endOffset -= 1; // cursorNode was in parent
        }

        return;
    }

    dom.remove(cursorNode);

    if (isDataNode(target))
        range[start ? 'setStart' : 'setEnd'](target, cursor.text.length);
    else
        range[start ? 'setStartBefore' : 'setEndBefore'](target);
}

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))) {
                    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) {
        var that = this;
        that.range = range;
        that.rootNode = RangeUtils.documentFromRange(range);
        that.body = that.rootNode.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;
    },

    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.nodeType == 3 && node.nodeValue.length < offset) {
            offset -= node.nodeValue.length;
            node = node.nextSibling;
        }

        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) {
        if (expand && range.collapsed) {
            this.addCaret(range);
            range = RangeUtils.expand(range);
        }

        var rangeBoundary = range.cloneRange();

        rangeBoundary.collapse(false);
        this.end = dom.create(RangeUtils.documentFromRange(range), 'span', { className: 'k-marker' });
        rangeBoundary.insertNode(this.end);

        rangeBoundary = range.cloneRange();
        rangeBoundary.collapse(true);
        this.start = this.end.cloneNode(true);
        rangeBoundary.insertNode(this.start);

        range.setStartBefore(this.start);
        range.setEndAfter(this.end);

        normalize(range.commonAncestorContainer);

        return range;
    },

    remove: function (range) {
        var start = this.start,
            end = this.end;

        normalize(range.commonAncestorContainer);

        while (!start.nextSibling && start.parentNode) start = start.parentNode;
        while (!end.previousSibling && end.parentNode) end = end.parentNode;

        var shouldNormalizeStart = (start.previousSibling && start.previousSibling.nodeType == 3)
                                && (start.nextSibling && start.nextSibling.nodeType == 3);

        var shouldNormalizeEnd = (end.previousSibling && end.previousSibling.nodeType == 3)
                                && (end.nextSibling && end.nextSibling.nodeType == 3);

        start = start.nextSibling;
        end = end.previousSibling;

        var collapsed = false;
        var collapsedToStart = false;
        // collapsed range
        if (start == this.end) {
            collapsedToStart = !!this.start.previousSibling;
            start = end = this.start.previousSibling || this.end.nextSibling;
            collapsed = true;
        }

        dom.remove(this.start);
        dom.remove(this.end);

        if (start == null || end == null) {
            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 (this.caret)
            this.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);
    },

    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);
    },

    getMarkers: function(range) {
        var markers = [];

        new RangeIterator(range).traverse(function (node) {
            if (node.className == 'k-marker')
                markers.push(node);
        });

        return markers;
    },

    image: function (range) {
        var nodes = [];

        new RangeIterator(range).traverse(function (node) {
            if (dom.is(node, 'img'))
                nodes.push(node);
        });

        if (nodes.length == 1)
            return nodes[0];
    },

    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 == 0 || endOffset == 0)
            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 document = RangeUtils.documentFromRange(range);

        if (node == document || node == document.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 != 0 && endOffset != 0;
    }
};

extend(Editor, {
    SelectionUtils: SelectionUtils,
    W3CRange: W3CRange,
    RangeIterator: RangeIterator,
    W3CSelection: W3CSelection,
    RangeEnumerator: RangeEnumerator,
    RestorePoint: RestorePoint,
    Marker: Marker,
    RangeUtils: RangeUtils
});

})(jQuery);
(function($) {

    // Imports ================================================================
    var doc = document,
        kendo = window.kendo,
        Class = kendo.Class,
        Editor = kendo.ui.editor,
        EditorUtils = Editor.EditorUtils,
        registerTool = EditorUtils.registerTool,
        dom = Editor.Dom,
        RangeUtils = Editor.RangeUtils,
        selectRange = RangeUtils.selectRange,
        Tool = Editor.Tool,
        ToolTemplate = Editor.ToolTemplate,
        RestorePoint = Editor.RestorePoint,
        Marker = Editor.Marker,
        extend = $.extend;

var Command = Class.extend({
    init: function(options) {
        var that = this;
        that.options = options;
        that.restorePoint = new RestorePoint(options.range);
        that.marker = new Marker();
        that.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);
        selectRange(range);
    },

    undo: function () {
        var point = this.restorePoint;
        point.body.innerHTML = point.html;
        selectRange(point.toRange());
    },

    redo: function () {
        this.exec();
    },

    exec: function () {
        var that = this,
        range = that.lockRange(true);
        that.formatter.editor = that.editor;
        that.formatter.toggle(range);
        that.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;
        selectRange(this.endRestorePoint.toRange());
    },

    undo: function () {
        this.body.innerHTML = this.startRestorePoint.html;
        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 range = editor.getRange();
        var startRestorePoint = new RestorePoint(range);

        editor.clipboard.paste(this.options.value || '');
        editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));

        editor.focus();
    }
});

var InsertHtmlTool = Tool.extend({
    initialize: function(ui, initOptions) {
        var editor = initOptions.editor,
            title = editor.options.localization.insertHtml;

        new Editor.SelectBox(ui, {
            dataSource: editor.options.insertHtml || [],
            dataTextField: "text",
            dataValueField: "value",
            change: function (e) {
                Tool.exec(editor, 'insertHtml', this.value());
            },
            title:editor.options.localization.insertHtml,
            highlightFirst: false
        });
    },

    command: function (commandArguments) {
        return new InsertHtmlCommand(commandArguments);
    },

    update: function(ui, nodes) {
        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 editor = this.editor,
            keyboard = editor.keyboard;
            isTypingKey = keyboard.isTypingKey(e);

        if (isTypingKey && !keyboard.isTypingInProgress()) {
            var range = editor.getRange();
            this.startRestorePoint = new RestorePoint(range);

            keyboard.startTyping($.proxy(function () {
                editor.selectionRestorePoint = this.endRestorePoint = new RestorePoint(editor.getRange());
                editor.undoRedoStack.push(new GenericCommand(this.startRestorePoint, this.endRestorePoint));
            }, this));

            return true;
        }

        return false;
    },

    keyup: function (e) {
        var keyboard = this.editor.keyboard;

        if (keyboard.isTypingInProgress()) {
            keyboard.endTyping();
            return true;
        }

        return false;
    }
});

var SystemHandler = Class.extend({
    init: function(editor) {
        this.editor = editor;
        this.systemCommandIsInProgress = false;
    },

    createUndoCommand: function () {
        this.endRestorePoint = new RestorePoint(this.editor.getRange());
        this.editor.undoRedoStack.push(new GenericCommand(this.startRestorePoint, this.endRestorePoint));
        this.startRestorePoint = this.endRestorePoint;
    },

    changed: function () {
        if (this.startRestorePoint)
            return this.startRestorePoint.html != this.editor.body.innerHTML;

        return false;
    },

    keydown: function (e) {
        var editor = this.editor,
            keyboard = editor.keyboard;

        if (keyboard.isModifierKey(e)) {

            if (keyboard.isTypingInProgress())
                keyboard.endTyping(true);

            this.startRestorePoint = new RestorePoint(editor.getRange());
            return true;
        }

        if (keyboard.isSystem(e)) {
            this.systemCommandIsInProgress = true;

            if (this.changed()) {
                this.systemCommandIsInProgress = false;
                this.createUndoCommand();
            }

            return true;
        }

        return false;
    },

    keyup: function (e) {
        if (this.systemCommandIsInProgress && this.changed()) {
            this.systemCommandIsInProgress = false;
            this.createUndoCommand(e);
            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);
    },

    toolFromShortcut: function (tools, e) {
        var key = String.fromCharCode(e.keyCode);

        for (var toolName in tools) {
            var toolOptions = 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() {
        this.typingInProgress = false;
        if (this.onEndTyping)
            this.onEndTyping();
    },

    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) {
        for (var i = 0; i < this.handlers.length; i++)
            if (this.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 MSWordFormatCleaner()];
    },

    htmlToFragment: function(html) {
        var editor = this.editor,
            container = dom.create(editor.document, 'div');

        container.innerHTML = html;

        var fragment = editor.document.createDocumentFragment();

        while (container.firstChild)
            fragment.appendChild(container.firstChild);

        return fragment;
    },

    isBlock: function(html) {
        return /<(div|p|ul|ol|table|h[1-6])/i.test(html);
    },

    oncut: function(e) {
        var editor = this.editor,
            startRestorePoint = new RestorePoint(editor.getRange());
        setTimeout(function() {
            editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
        });
    },

    onpaste: function(e) {
        var editor = this.editor,
            range = editor.getRange(),
            startRestorePoint = new RestorePoint(range),
            clipboardNode = dom.create(editor.document, 'div', {className:'k-paste-container', innerHTML: '\ufeff'});

        editor.body.appendChild(clipboardNode);

        if (editor.body.createTextRange) {
            e.preventDefault();
            var r = editor.createRange();
            r.selectNodeContents(clipboardNode);
            editor.selectRange(r);
            var textRange = editor.body.createTextRange();
            textRange.moveToElementText(clipboardNode);
            $(editor.body).unbind('paste');
            textRange.execCommand('Paste');
            $(editor.body).bind('paste', arguments.callee);
        } else {
            var clipboardRange = editor.createRange();
            clipboardRange.selectNodeContents(clipboardNode);
            selectRange(clipboardRange);
        }

        setTimeout(function() {
            selectRange(range);
            dom.remove(clipboardNode);

            if (clipboardNode.lastChild && dom.is(clipboardNode.lastChild, 'br'))
                dom.remove(clipboardNode.lastChild);

            var args = { html: clipboardNode.innerHTML };
            editor.trigger("paste", args);
            editor.clipboard.paste(args.html, true);
            editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
            Editor.EditorUtils.select(editor);
        });
    },

    splittableParent: function(block, node) {
        if (block)
            return dom.parentOfType(node, ['p', 'ul', 'ol']) || node.parentNode;

        var parent = node.parentNode;
        var body = node.ownerDocument.body;

        if (dom.isInline(parent)) {
            while (parent.parentNode != body && !dom.isBlock(parent.parentNode))
                parent = parent.parentNode;
        }

        return parent;
    },

    paste: function (html, clean) {
        var editor = this.editor,
            i, l;

        for (i = 0, l = this.cleaners.length; i < l; i++)
            if (this.cleaners[i].applicable(html))
                html = this.cleaners[i].clean(html);

        if (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);

        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;

        if (!/body|td/.test(dom.name(parent)) && (block || dom.isInline(parent))) {
            range.selectNode(caret);
            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 />'));
        }

        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.scrollTo(caret);
        marker.removeCaret(range);
        selectRange(range);
    }
});

var MSWordFormatCleaner = Class.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>/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>',
            /<\/?(meta|link|style|o:|v:|x:)[^>]*>((?:.|\n)*?<\/(meta|link|style|o:|v:|x:)[^>]*>)?/ig, '', /* external references and namespaced tags */
            /style=(["|'])\s*\1/g, '' /* empty style attributes */
        ];
    },

    applicable: function(html) {
        return /class="?Mso|style="[^"]*mso-/i.test(html);
    },

    listType: function(html) {
        if (/^[\u2022\u00b7\u00a7\u00d8o]\u00a0+/.test(html))
            return 'ul';

        if (/^\s*\w+[\.\)]\u00a0{2,}/.test(html))
            return 'ol';
    },

    lists: function(html) {
        var placeholder = dom.create(document, 'div', {innerHTML: html});
        var blockChildren = $(dom.blockElements.join(','), placeholder);

        var lastMargin = -1, lastType, levels = {'ul':{}, 'ol':{}}, li = placeholder;

        for (var i = 0; i < blockChildren.length; i++) {
            var p = blockChildren[i];
            var html = p.innerHTML.replace(/<\/?\w+[^>]*>/g, '').replace(/&nbsp;/g, '\u00a0');
            var type = this.listType(html);

            if (!type || dom.name(p) != 'p') {
                if (p.innerHTML == '') {
                    dom.remove(p);
                } else {
                    levels = {'ul':{}, 'ol':{}};
                    li = placeholder;
                    lastMargin = -1;
                }
                continue;
            }

            var margin = parseFloat(p.style.marginLeft || 0);
            var 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 (var key in levels)
                    for (var child in levels[key])
                        if ($.contains(list, levels[key][child]))
                            delete levels[key][child];
            }

            dom.remove(p.firstChild);
            li = dom.create(document, 'li', {innerHTML:p.innerHTML});
            list.appendChild(li);
            dom.remove(p);
            lastMargin = margin;
            lastType = type;
        }
        return placeholder.innerHTML;
    },

    stripEmptyAnchors: function(html) {
        return html.replace(/<a([^>]*)>\s*<\/a>/ig, function(a, attributes) {
            if (!attributes || attributes.indexOf("href") < 0) {
                return "";
            }

            return a;
        });
    },

    clean: function(html) {
        for (var i = 0, l = this.replacements.length; i < l; i+= 2)
            html = html.replace(this.replacements[i], this.replacements[i+1]);

        html = this.stripEmptyAnchors(html);
        html = this.lists(html);
        html = html.replace(/\s+class="?[^"\s>]*"?/ig, '');

        return html;
    }
});

extend(Editor, {
    Command: Command,
    GenericCommand: GenericCommand,
    InsertHtmlCommand: InsertHtmlCommand,
    InsertHtmlTool: InsertHtmlTool,
    UndoRedoStack: UndoRedoStack,
    TypingHandler: TypingHandler,
    SystemHandler: SystemHandler,
    Keyboard: Keyboard,
    Clipboard: Clipboard,
    MSWordFormatCleaner: MSWordFormatCleaner
});

registerTool("insertHtml", new InsertHtmlTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Insert HTML", initialValue: "Insert HTML"})}));

})(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) {
        var that = this;
        that.finder = new InlineFormatFinder(format);
        that.attributes = extend({}, format[0].attr, values);
        that.tag = format[0].tags[0];
    },

    wrap: function(node) {
        return dom.wrap(node, dom.create(node.ownerDocument, this.tag, this.attributes));
    },

    activate: function(range, nodes) {
        var that = this;
        if (that.finder.isFormatted(nodes)) {
            that.split(range);
            that.remove(nodes);
        } else {
            that.apply(nodes);
        }
    },

    toggle: function (range) {
        var nodes = RangeUtils.textNodes(range);

        if (nodes.length > 0) {
            this.activate(range, nodes);
        }
    },

    apply: function (nodes) {
        var that = this,
        formatNodes = [],
        i, l, node, formatNode;

        for (i = 0, l = nodes.length; i < l; i++) {
            node = nodes[i];
            formatNode = that.finder.findSuitable(node);
            if (formatNode) {
                dom.attr(formatNode, that.attributes);
            } else {
                formatNode = that.wrap(node);
            }

            formatNodes.push(formatNode);
        }

        that.consolidate(formatNodes);
    },

    remove: function (nodes) {
        var that = this,
        i, l, formatNode;

        for (i = 0, l = nodes.length; i < l; i++) {
            formatNode = that.finder.findFormat(nodes[i]);
            if (formatNode) {
                if (that.attributes && that.attributes.style) {
                    dom.unstyle(formatNode, that.attributes.style);
                    if (!formatNode.style.cssText) {
                        dom.unwrap(formatNode);
                    }
                } else {
                    dom.unwrap(formatNode);
                }
            }
        }
    },

    split: function (range) {
        var nodes = RangeUtils.textNodes(range),
        l = nodes.length,
        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) {
        var that = this;
        that.format = format;
        that.greedyProperty = greedyProperty;
        InlineFormatFinder.fn.init.call(that, format);
    },

    getInlineCssValue: function(node) {
        var attributes = node.attributes,
            trim = $.trim,
            i, l, attribute, name, attributeValue, css, pair, cssIndex, len, 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;
                    }
                }
            }
        }

        return;
    },

    getFormatInner: function (node) {
        var $node = $(dom.isDataNode(node) ? node.parentNode : node),
            parents = $node.parents().andSelf(),
            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) {
        var that = this;

        InlineFormatter.fn.init.call(that, format, values);

        that.greedyProperty = greedyProperty;
        that.values = values;
        that.finder = new GreedyInlineFormatFinder(format, greedyProperty)
    },

    activate: function(range, nodes) {
        var that = this;
        that.split(range);

        if (that.greedyProperty) {
            var camelCase = that.greedyProperty.replace(/-([a-z])/, function(all, letter){return letter.toUpperCase()});
            that[that.values.style[camelCase] == "inherit" ? "remove" : "apply"](nodes);
        } else {
            that.apply(nodes);
        }
    }
});

function inlineFormatWillDelayExecution (range) {
    return range.collapsed && !RangeUtils.isExpandable(range);
}

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) }
        }));

        this.willDelayExecution = inlineFormatWillDelayExecution;
    }
});

var FontTool = Tool.extend({
    init: function(options) {
        var that = this;
        Tool.fn.init.call(that, options);

        // IE has single selection hence we are using select box instead of combobox
        that.options = options;
        that.type = ($.browser.msie || kendo.support.touch) ? "kendoDropDownList" : "kendoComboBox";
        that.format = [{ tags: ["span"] }],
        that.finder = new GreedyInlineFormatFinder(that.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);
            }
        }))
    },

    willDelayExecution: inlineFormatWillDelayExecution,

    update: function(ui, nodes, pendingFormats) {
        var that = this,
            list = ui.data(that.type),
            pendingFormat = pendingFormats.getPending(that.name),
            format = (pendingFormat && pendingFormat.params) ? pendingFormat.params.value : that.finder.getFormat(nodes);

        list.close();
        list.value(format);
    },

    initialize: function (ui, initOptions) {
        var editor = initOptions.editor,
            toolName = this.options.name;

        ui[this.type]({
            dataTextField: "Text",
            dataValueField: "Value",
            dataSource: editor.options[toolName],
            change: function (e) {
                Tool.exec(editor, toolName, this.value());
            },
            highlightFirst: false
        });

        ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");

        ui.data(this.type).value("inherit");
    }

});

var ColorTool = Tool.extend({
    init: function(options) {
        Tool.fn.init.call(this, options);

        this.options = options;
        this.format = [{ tags: dom.inlineElements }];
    },

    update: function(ui) {
        ui.data("kendoColorPicker").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);
            }
        }));
    },

    willDelayExecution: inlineFormatWillDelayExecution,

    initialize: function(ui, initOptions) {
        var editor = initOptions.editor,
            toolName = this.name;

        new Editor.ColorPicker(ui, {
            value: "#000000",
            change: function (e) {
                Tool.exec(editor, toolName, e.value);
            }
        });
    }
});

var StyleTool = Tool.extend({
    init: function(options) {
        var that = this;
        Tool.fn.init.call(that, options);

        that.format = [{ tags: ["span"] }];
        that.finder = new GreedyInlineFormatFinder(that.format, "className");
    },

    command: function (commandArguments) {
        return new Editor.FormatCommand(extend(commandArguments, {
            formatter: function () {
                return new GreedyInlineFormatter(this.format, { className: commandArguments.value });
            }
        }));
    },

    update: function(ui, nodes) {
        var list = ui.data("kendoDropDownList");
        list.close();
        list.value(this.finder.getFormat(nodes));
    },

    initiliaze: function(ui, initOptions) {
        var editor = initOptions.editor;

        ui.kendoDropDownList({
            data: editor["style"],
            title: editor.options.localization.style,
            itemCreate: function (e) {
                var style = dom.inlineStyle(editor.document, "span", {className : e.dataItem.Value});

                e.html = '<span unselectable="on" style="display:block;' + style +'">' + e.html + '</span>';
            },
            change: function (e) {
                Tool.exec(editor, "style", e.value);
            }
        });
    }

});

extend(Editor, {
    InlineFormatFinder: InlineFormatFinder,
    InlineFormatter: InlineFormatter,
    GreedyInlineFormatFinder: GreedyInlineFormatFinder,
    GreedyInlineFormatter: GreedyInlineFormatter,
    InlineFormatTool: InlineFormatTool,
    FontTool: FontTool,
    ColorTool: ColorTool,
    StyleTool: StyleTool
});

registerTool("style", new Editor.StyleTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Indent", initialValue: "Styles"})}));

registerFormat("bold", [ { tags: ["strong"] }, { 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"] }, { 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"}} } ]);
registerTool("underline", new InlineFormatTool({ key: "U", ctrl: true, format: formats.underline, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Underline"})}));

registerFormat("strikethrough", [ { tags: ["del"] }, { 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", template: new ToolTemplate({template: EditorUtils.comboBoxTemplate, title: "Font Name", initialValue: "(inherited font)"})}));

registerTool("fontSize", new FontTool({cssAttr:"font-size", domAttr:"fontSize", name:"fontSize", template: new ToolTemplate({template: EditorUtils.comboBoxTemplate, title: "Font Size", initialValue: "(inherited size)"})}));

})(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,
    Tool = Editor.Tool,
    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 == null || !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++) {
            candidate = dom.ofType(nodes[i], format[0].tags) ? nodes[i] : dom.parentOfType(nodes[i], format[0].tags);
            if (!candidate)
                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;

        for (i = 0, len = format.length; i < len; i++) {
            node = sourceNode;
            tags = format[i].tags;
            attributes = format[i].attr;

            while (node) {
                if (dom.ofType(node, tags) && dom.attrEquals(node, attributes))
                    return node;
                node = node.parentNode;
            }
        }
        return null;
    },

    getFormat: function (nodes) {
        var findFormat = $.proxy(function(node) { return this.findFormat(dom.isDataNode(node) ? node.parentNode : node); }, this),
            result = findFormat(nodes[0]),
            i;

        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 that = this,
            formatNodes = dom.is(nodes[0], "img") ? [nodes[0]] : that.finder.findSuitable(nodes),
            formatToApply = formatNodes.length ? EditorUtils.formatByName(dom.name(formatNodes[0]), that.format) : that.format[0],
            tag = formatToApply.tags[0],
            attributes = extend({}, formatToApply.attr, that.values),
            i, len;

        if (formatNodes.length) {
            for (i = 0, len = formatNodes.length; i < len; i++) {
                dom.attr(formatNodes[i], attributes);
            }
        } else {
            that.wrap(tag, attributes, nodes);
        }
    },

    remove: function (nodes) {
        var i, l, formatNode, namedFormat;

        for (i = 0, l = nodes.length; i < l; i++) {
            formatNode = this.finder.findFormat(nodes[i]);
            if (formatNode) {
                if (dom.ofType(formatNode, ["p", "img", "li"])) {
                    namedFormat = EditorUtils.formatByName(dom.name(formatNode), this.format);
                    if (namedFormat.attr.style) {
                        dom.unstyle(formatNode, namedFormat.attr.style);
                    }
                    if (namedFormat.attr.className) {
                        dom.removeClass(formatNode, namedFormat.attr.className);
                    }
                } else {
                    dom.unwrap(formatNode);
                }
            }
        }
    },

    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,
            blocks = dom.blockParents(nodes),
            formatTag = format[0].tags[0],
            i, len, list, formatter, range;

        if (blocks.length) {
            for (i = 0, len = blocks.length; i < len; i++) {
                if (dom.is(blocks[i], "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 {
                    dom.changeTag(blocks[i], formatTag);
                }
            }
        } 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) }
        }));
    }
});

var FormatBlockTool = Tool.extend({
    init: function (options) {
        Tool.fn.init.call(this, options);
        this.finder = new BlockFormatFinder([{ tags: dom.blockElements }]);
    },

    command: function (commandArguments) {
        return new FormatCommand(extend(commandArguments, {
            formatter: function () { return new GreedyBlockFormatter([{ tags: [commandArguments.value] }], {}); }
        }));
    },

    update: function(ui, nodes) {
        var list;
        if (ui.is("select")) {
            list = ui.data("kendoSelectBox");
        } else {
            list = ui.find("select").data("kendoSelectBox");
        }
        list.close();
        list.value(this.finder.getFormat(nodes));
    },

    initialize: function(ui, initOptions) {
        var editor = initOptions.editor,
            toolName = "formatBlock";

        new Editor.SelectBox(ui, {
            dataTextField: "Text",
            dataValueField: "Value",
            dataSource: editor.options.formatBlock,
            title: editor.options.localization.formatBlock,
            change: function (e) {
                Tool.exec(editor, toolName, this.value());
            },
            highlightFirst: false
        });

        ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");
    }
});

extend(Editor, {
    BlockFormatFinder: BlockFormatFinder,
    BlockFormatter: BlockFormatter,
    GreedyBlockFormatter: GreedyBlockFormatter,
    FormatCommand: FormatCommand,
    BlockFormatTool: BlockFormatTool,
    FormatBlockTool: FormatBlockTool
});

registerTool("formatBlock", new FormatBlockTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Format Block", initialValue: "Select Block Type"})}));

registerFormat("justifyLeft", [ { tags: dom.blockElements, attr: { style: { textAlign: "left"}} }, { tags: ["img"], attr: { style: { "float": "left"}} } ]);
registerTool("justifyLeft", new BlockFormatTool({format: formats.justifyLeft, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Left"})}));

registerFormat("justifyCenter", [ { tags: dom.blockElements, attr: { style: { textAlign: "center"}} }, { tags: ["img"], attr: { style: { display: "block", marginLeft: "auto", marginRight: "auto"}} } ]);
registerTool("justifyCenter", new BlockFormatTool({format: formats.justifyCenter, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Center"})}));

registerFormat("justifyRight", [ { tags: dom.blockElements, attr: { style: { textAlign: "right"}} }, { tags: ["img"], attr: { style: { "float": "right"}} } ]);
registerTool("justifyRight", new BlockFormatTool({format: formats.justifyRight, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Right"})}));

registerFormat("justifyFull", [ { tags: dom.blockElements, attr: { style: { textAlign: "justify"}} } ]);
registerTool("justifyFull", new BlockFormatTool({format: formats.justifyFull, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Full"})}));

})(jQuery);
(function($) {

// Imports ================================================================
var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    Command = Editor.Command,
    Tool = Editor.Tool,
    BlockFormatter = Editor.BlockFormatter,
    normalize = dom.normalize,
    RangeUtils = Editor.RangeUtils,
    registerTool = Editor.EditorUtils.registerTool;

var ParagraphCommand = Command.extend({
    init: function(options) {
        this.options = options;
        Command.fn.init.call(this, options);
    },

    exec: function () {
        var range = this.getRange(),
            document = RangeUtils.documentFromRange(range),
            parent, previous, next,
            emptyParagraphContent = $.browser.msie ? '' : '<br _moz_dirty="" />',
            paragraph, marker, li, heading, rng,
            // necessary while the emptyParagraphContent is empty under IE
            blocks = 'p,h1,h2,h3,h4,h5,h6'.split(','),
            startInBlock = dom.parentOfType(range.startContainer, blocks),
            endInBlock = dom.parentOfType(range.endContainer, blocks),
            shouldTrim = (startInBlock && !endInBlock) || (!startInBlock && endInBlock);

        range.deleteContents();

        marker = dom.create(document, 'a');
        range.insertNode(marker);

        normalize(marker.parentNode);

        li = dom.parentOfType(marker, ['li']);
        heading = dom.parentOfType(marker, 'h1,h2,h3,h4,h5,h6'.split(','));

        if (li) {
            rng = range.cloneRange();
            rng.selectNode(li);

            // hitting 'enter' in empty li
            if (RangeUtils.textNodes(rng).length == 0) {
                paragraph = dom.create(document, 'p');

                if (li.nextSibling) {
                    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 && !marker.nextSibling) {
            paragraph = dom.create(document, '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);

            function clean(node) {
                if (node.firstChild && dom.is(node.firstChild, 'br')) {
                    dom.remove(node.firstChild);
                }

                if (dom.isDataNode(node) && node.nodeValue == '') {
                    node = node.parentNode;
                }

                if (node && !dom.is(node, 'img')) {
                    while (node.firstChild && node.firstChild.nodeType == 1) {
                        node = node.firstChild;
                    }

                    if (node.innerHTML == '') {
                        node.innerHTML = emptyParagraphContent;
                    }
                }
            }

            clean(previous);
            clean(next);

            // normalize updates the caret display in Gecko
            normalize(previous);
        }

        normalize(next);

        if (dom.is(next, 'img')) {
            range.setStartBefore(next);
        } else {
            range.selectNodeContents(next);
        }

        range.collapse(true);

        dom.scrollTo(next);

        RangeUtils.selectRange(range);
    }

});

var NewLineCommand = Command.extend({
    init: function(options) {
        this.options = options;
        Command.fn.init.call(this, options);
    },

    exec: function () {
        var range = this.getRange();
        range.deleteContents();
        var br = dom.create(RangeUtils.documentFromRange(range), 'br');
        range.insertNode(br);
        normalize(br.parentNode);

        if (!$.browser.msie && (!br.nextSibling || dom.isWhitespace(br.nextSibling))) {
            //Gecko and WebKit cannot put the caret after only one br.
            var filler = br.cloneNode(true);
            filler.setAttribute('_moz_dirty', '');
            dom.insertAfter(filler, br);
        }
        range.setStartAfter(br);
        range.collapse(true);
        RangeUtils.selectRange(range);
    }
});

extend(Editor, {
    ParagraphCommand: ParagraphCommand,
    NewLineCommand: NewLineCommand
});

registerTool("insertLineBreak", new Tool({ key: 13, shift: true, command: NewLineCommand }));
registerTool("insertParagraph", new Tool({ key: 13, command: ParagraphCommand }));

})(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;
        }

        for (i = 0; i < formatNodes.length; i++) {
            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;
    },

    wrap: function(list, nodes) {
        var li = dom.create(list.ownerDocument, 'li');

        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];

            if (dom.is(node, 'li')) {
                list.appendChild(node);
                continue;
            }

            if (dom.is(node, 'ul') || dom.is(node, 'ol')) {
                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);
        if (nodes.length) {
            var start = dom.parentOfType(nodes[0], ['li']);
            var 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);
                    }
                }
            }
        }
    },

    apply: function (nodes) {
        var tag = this.tag,
            commonAncestor = nodes.length == 1 ? dom.parentOfType(nodes[0], ['ul','ol']) : dom.commonAncestor.apply(null, nodes);

        if (!commonAncestor)
            commonAncestor = dom.parentOfType(nodes[0], ["td"]) || nodes[0].ownerDocument.body;

        if (dom.isInline(commonAncestor))
            commonAncestor = dom.blockParentOrBody(commonAncestor);

        var ancestors = [];

        var formatNode = this.finder.findSuitable(nodes);

        if (!formatNode)
            formatNode = new ListFormatFinder(tag == 'ul' ? 'ol' : 'ul').findSuitable(nodes);

        var childNodes = dom.significantChildNodes(commonAncestor);

        if (!childNodes.length) {
            childNodes = nodes;
        }

        if (/table|tbody/.test(dom.name(commonAncestor))) {
            childNodes = $.map(nodes, function(node) { return dom.parentOfType(node, ["td"]) });
        }

        for (var i = 0; i < childNodes.length; i++) {
            var child = childNodes[i];
            var nodeName = dom.name(child);
            if (this.suitable(child, nodes) && (!formatNode || !dom.isAncestorOrSelf(formatNode, child))) {

                if (formatNode && (nodeName == 'ul' || nodeName == 'ol')) {
                    // merging lists
                    $.each(child.childNodes, function () { ancestors.push(this) });
                    dom.remove(child);
                } else {
                    ancestors.push(child);
                }
            }
        }

        if (ancestors.length == childNodes.length && commonAncestor != nodes[0].ownerDocument.body && !/table|tbody|tr|td/.test(dom.name(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);

        var prev = formatNode.previousSibling;
        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;
        }

        var 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);
        }
    },

    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++)
            if (formatNode = this.finder.findFormat(nodes[i]))
                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: "Remove Link"})}));
registerTool("insertOrderedList", new ListTool({tag:'ol', template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Remove Link"})}));

})(jQuery);
(function($, undefined) {

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,
    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);
        if (attributes.innerHTML != undefined) {
            var markers = RangeUtils.getMarkers(range);
            var document = RangeUtils.documentFromRange(range);
            range.deleteContents();
            var a = dom.create(document, 'a', attributes);
            range.insertNode(a);

            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 {
            var 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.attributes = null;
        cmd.async = true;
        cmd.formatter = new LinkFormatter();
    },

    exec: function () {
        var range = this.getRange();

        var collapsed = range.collapsed;

        range = this.lockRange(true);

        var nodes = textNodes(range);

        var initialText = null;

        var self = this;

        function apply(e) {
            var href = $('#k-editor-link-url', dialog.element).val();

            if (href && href != 'http://') {
                self.attributes = { href: href };

                var title = $('#k-editor-link-title', dialog.element).val();
                if (title)
                    self.attributes.title = title;

                var text = $('#k-editor-link-text', dialog.element).val();
                if (text !== initialText)
                    self.attributes.innerHTML = text || href;

                var target = $('#k-editor-link-target', dialog.element).is(':checked');
                if (target)
                    self.attributes.target = '_blank';

                self.formatter.apply(range, self.attributes);
            }
            close(e);
            if (self.change)
                self.change();
        }

        function close(e) {
            e.preventDefault();
            dialog.destroy();

            dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();

            self.releaseRange(range);
        }

        var a = nodes.length ? self.formatter.finder.findSuitable(nodes[0]) : null;

        var shouldShowText = nodes.length <= 1 || (nodes.length == 2 && collapsed);

        var windowContent =
            '<div class="k-editor-dialog">' +
                '<ol>' +
                    '<li class="k-form-text-row"><label for="k-editor-link-url">Web address</label><input type="text" class="k-input" id="k-editor-link-url"/></li>' +
                    (shouldShowText ? '<li class="k-form-text-row"><label for="k-editor-link-text">Text</label><input type="text" class="k-input" id="k-editor-link-text"/></li>' : '') +
                    '<li class="k-form-text-row"><label for="k-editor-link-title">Tooltip</label><input type="text" class="k-input" id="k-editor-link-title"/></li>' +
                    '<li class="k-form-checkbox-row"><input type="checkbox" id="k-editor-link-target"/><label for="k-editor-link-target">Open link in new window</label></li>' +
                '</ol>' +
                '<div class="k-button-wrapper">' +
                    '<button class="k-dialog-insert k-button">Insert</button>' +
                    '&nbsp;or&nbsp;' +
                    '<a href="#" class="k-dialog-close k-link">Close</a>' +
                '</div>' +
            '</div>';

        var dialog = $(windowContent).appendTo(document.body).kendoWindow($.extend({}, this.editor.options.dialogOptions, {
            title: "Insert link",
            close: close
        }))
            .hide()
            .find('.k-dialog-insert').click(apply).end()
            .find('.k-dialog-close').click(close).end()
            .find('.k-form-text-row input').keydown(function (e) {
                if (e.keyCode == 13)
                    apply(e);
                else if (e.keyCode == 27)
                    close(e);
            }).end()
            // IE < 8 returns absolute url if getAttribute is not used
            .find('#k-editor-link-url').val(a ? a.getAttribute('href', 2) : 'http://').end()
            .find('#k-editor-link-text').val(nodes.length > 0 ? (nodes.length == 1 ? nodes[0].nodeValue : nodes[0].nodeValue + nodes[1].nodeValue) : '').end()
            .find('#k-editor-link-title').val(a ? a.title : '').end()
            .find('#k-editor-link-target').attr('checked', a ? a.target == '_blank' : false).end()
            .show()
            .data('kendoWindow')
            .center();

        if (shouldShowText && nodes.length > 0)
            initialText = $('#k-editor-link-text', dialog.element).val();

        $('#k-editor-link-url', dialog.element).focus().select();
    },

    redo: function () {
        var that = this,
            range = that.lockRange(true);

        that.formatter.apply(range, that.attributes);
        that.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) {
        $ui.attr('unselectable', 'on')
           .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"})}));

})(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,
    INSERTIMAGE = "Insert Image",
    KEDITORIMAGEURL = "#k-editor-image-url",
    KEDITORIMAGETITLE = "#k-editor-image-title";

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;
        if (attributes.src && attributes.src != "http://") {
            if (!img) {
                img = dom.create(RangeUtils.documentFromRange(range), "img", attributes);
                img.onload = img.onerror = function () {
                    img.removeAttribute("complete");
                    img.removeAttribute("width");
                    img.removeAttribute("height");
                }
                range.deleteContents();
                range.insertNode(img);
                range.setStartAfter(img);
                range.setEndAfter(img);
                RangeUtils.selectRange(range);
                return true;
            } else {
                dom.attr(img, attributes);
            }
        }

        return false;
    },

    redo: function () {
        var that = this,
            range = that.lockRange();

        if (!that.insertImage(RangeUtils.image(range), range)) {
            that.releaseRange(range);
        }
    },

    exec: function () {
        var that = this,
            insertImage = that.insertImage,
            range = that.lockRange(),
            applied = false,
            img = RangeUtils.image(range),
            windowContent, dialog;

        function apply(e) {
            that.attributes = {
                src: $(KEDITORIMAGEURL, dialog.element).val(),
                alt: $(KEDITORIMAGETITLE, dialog.element).val()
            };

            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);
            }
        }

//        var fileBrowser = that.editor.fileBrowser;
//        var showBrowser = fileBrowser && fileBrowser.selectUrl !== undefined;
//
        function activate() {
//            if (showBrowser) {
//                new $t.imageBrowser($(this).find(".k-image-browser"), extend(fileBrowser, { apply: apply, element: that.editor.element, localization: that.editor.options.localization }));
//            }
        }

        windowContent =
            '<div class="k-editor-dialog">' +
                '<ol>' +
                    '<li class="k-form-text-row"><label for="k-editor-image-url">Web address</label><input type="text" class="k-input" id="k-editor-image-url"/></li>' +
                    '<li class="k-form-text-row"><label for="k-editor-image-title">Tooltip</label><input type="text" class="k-input" id="k-editor-image-title"/></li>' +
                '</ol>' +
                '<div class="k-button-wrapper">' +
                    '<button class="k-dialog-insert k-button">Insert</button>' +
                    '&nbsp;or&nbsp;' +
                    '<a href="#" class="k-dialog-close k-link">Close</a>' +
                '</div>' +
            '</div>';

        dialog = $(windowContent)
                .appendTo(document.body)
                .kendoWindow(extend({}, that.editor.options.dialogOptions, {
                    title: INSERTIMAGE,
                    close: close
                }))
                .hide()
                .find(".k-dialog-insert").click(apply).end()
                .find(".k-dialog-close").click(close).end()
                .find(".k-form-text-row input").keydown(keyDown).end()
                //.toggleClass("k-imagebrowser", showBrowser)
                // 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()
                .show()
                .data("kendoWindow")
                .center();

        $(KEDITORIMAGEURL, dialog.element).focus().select();
    }

});

kendo.ui.editor.ImageCommand = ImageCommand;

registerTool("insertImage", new Editor.Tool({ command: ImageCommand, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: INSERTIMAGE}) }));

})(jQuery);
(function($, undefined) {

var kendo = window.kendo,
    Widget = kendo.ui.Widget,
    DropDownList = kendo.ui.DropDownList,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    CHANGE = "change",
    KSTATESELECTED = "k-state-selected",
    SELECTEDCLASS = "." + KSTATESELECTED,
    SELECTEDCOLORCLASS = ".k-selected-color",
    UNSELECTABLE = "unselectable",
    BACKGROUNDCOLOR = "background-color",
    keys = kendo.keys,
    template = kendo.template(
'<div class="k-colorpicker-popup">' +
   '<ul class="k-reset">'+
        '# for(var i = 0; i < colors.length; i++) { #' +
            '<li class="k-item #= colors[i] == value ? "k-state-selected" : "" #">' +
                '<div style="background-color:\\##= colors[i] #"></div>'+
            '</li>' +
        '# } #' +
   '</ul>' +
'</div>');

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

        Widget.fn.init.call(that, element, options);

        element = that.element;
        options = that.options;

        that._value = options.value;

        that.popup = $(template({
                        colors: options.colors,
                        value: options.value.substring(1)
                     }))
                     .kendoPopup({
                        anchor: element,
                        toggleTarget: element.find(".k-icon")
                     })
                     .delegate(".k-item", "click", function(e) {
                         that.select($(e.currentTarget).find("div").css(BACKGROUNDCOLOR));
                     })
                     .find("*")
                     .attr(UNSELECTABLE, "on")
                     .end()
                     .data("kendoPopup");

        element.attr("tabIndex", 0) // add the color picker to the tab order
               .keydown(function(e) {
                   that.keydown(e);
               })
               .focus(function () {
                   element.css("outline", "1px dotted #000");
               })
               .blur(function() {
                   element.css("outline", "");
               })
               .delegate(".k-tool-icon", "click", function() {
                   that.select();
               })
               .find("*")
               .attr(UNSELECTABLE, "on");

        if (that._value) {
            element.find(SELECTEDCOLORCLASS).css(BACKGROUNDCOLOR, that._value);
        }
    },

    options: {
        name: "ColorPicker",
        colors: "000000,7f7f7f,880015,ed1c24,ff7f27,fff200,22b14c,00a2e8,3f48cc,a349a4,ffffff,c3c3c3,b97a57,ffaec9,ffc90e,efe4b0,b5e61d,99d9ea,7092be,c8bfe7".split(","),
        value: null
    },

    events: [
        CHANGE
    ],

    select: function(color) {
        var that = this;

        if (color) {
            color = dom.toHex(color);
            if (!that.trigger(CHANGE, { value: color })) {
                that.value(color);
                that.close();
            }
        } else {
            that.trigger(CHANGE, { value: that._value })
        }
    },

    open: function() {
        this.popup.open();
    },

    close: function() {
        this.popup.close();
    },

    toggle: function() {
        this.popup.toggle();
    },

    keydown: function(e) {
        var that = this,
            popup = that.popup.element,
            visible = that.popup.visible(),
            selected,
            next,
            prev,
            preventDefault = false,
            keyCode = e.keyCode;

        if (keyCode == keys.DOWN) {
            if (!visible) {
                that.open();
            } else {
                selected = popup.find(SELECTEDCLASS);

                if (selected[0]) {
                    next = selected.next();
                } else {
                    next = popup.find("li:first");
                }

                if (next[0]) {
                    selected.removeClass(KSTATESELECTED);
                    next.addClass(KSTATESELECTED);
                }
            }

            preventDefault = true;
        } else if (keyCode == keys.UP) {
            if (visible) {
                selected = popup.find(SELECTEDCLASS);
                prev = selected.prev();

                if (prev[0]) {
                    selected.removeClass(KSTATESELECTED);
                    prev.addClass(KSTATESELECTED);
                }
            }
            preventDefault = true;
        } else if (keyCode == keys.TAB || keyCode == keys.RIGHT || keyCode == keys.LEFT) {
            that.close();
        } else if (keyCode == keys.ENTER) {
            popup.find(SELECTEDCLASS).click();
            preventDefault = true;
        }

        if (preventDefault) {
            e.preventDefault();
        }
    },

    value: function(value) {
        var that = this;

        if (value === undefined) {
            return that._value;
        } else {
            value = dom.toHex(value);

            that._value = value;

            that.element.find(SELECTEDCOLORCLASS)
                        .css(BACKGROUNDCOLOR, value);
        }
    }
});

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

        DropDownList.fn.init.call(that, element, options);

        that.value(that.options.title);
    },
    options: {
        name: "SelectBox"
    },
    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);
           that._current.removeClass("k-state-selected");
           that.current(null);
           that._oldIndex = that.selectedIndex = -1;
        }
    }
});

kendo.ui.editor.ColorPicker = ColorPicker;
kendo.ui.editor.SelectBox = SelectBox;

})(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 property = dom.name(node) != "td" ? "marginLeft" : "paddingLeft";
    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() == 0) {
                        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)) + 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:blockElements}], {style:{marginLeft:30}});

            formatter.apply(nodes);
        }
    },

    remove: function(nodes) {
        var formatNodes = this.finder.findSuitable(nodes),
            targetNode, i, len, list, listParent, siblings;

        for (i = 0, len = formatNodes.length; i < len; i++) {
            var 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];
            }

            var marginLeft = parseInt(indent(targetNode)) - 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, extend(options, {command:OutdentCommand}));

        this.finder = new BlockFormatFinder([{tags:blockElements}]);
    },

    initialize: function(ui) {
        ui.attr("unselectable", "on")
           .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({template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Outdent"})}));

})(jQuery);
(function($) {

// Imports ================================================================
var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    RangeUtils = Editor.RangeUtils,
    Marker = Editor.Marker;

var PendingFormats = Class.extend({
    init: function(editor) {
        this.editor = editor;
        this.formats = [];
    },

    apply: function(range) {
        if (!this.hasPending())
            return;

        var marker = new Marker();

        marker.addCaret(range);

        var caret = range.startContainer.childNodes[range.startOffset];

        var target = caret.previousSibling;

        /* under IE, target is a zero-length text node. go figure. */
        if (!target.nodeValue)
            target = target.previousSibling;

        range.setStart(target, target.nodeValue.length - 1);

        marker.add(range);

        if (RangeUtils.textNodes(range).length == 0) {
            marker.remove(range);
            range.collapse(true);
            this.editor.selectRange(range);
            return;
        }

        var textNode = marker.end.previousSibling.previousSibling;

        var pendingFormat,
            formats = this.formats;

        for (var i = 0; i < formats.length; i++) {
            pendingFormat = formats[i];

            var command = pendingFormat.command(extend({ range: range }, pendingFormat.options.params));
            command.editor = this.editor;
            command.exec();

            range.selectNode(textNode);
        }

        marker.remove(range);

        if (textNode.parentNode) {
            range.setStart(textNode, 1);
            range.collapse(true);
        }

        this.clear();

        this.editor.selectRange(range);
    },

    hasPending: function() {
        return this.formats.length > 0;
    },

    isPending: function(format) {
        return !!this.getPending(format);
    },

    getPending: function(format) {
        var formats = this.formats;
        for (var i = 0; i < formats.length; i++)
            if (formats[i].name == format)
                return formats[i];

        return;
    },

    toggle: function(format) {
        var formats = this.formats;

        for (var i = 0; i < formats.length; i++)
            if (formats[i].name == format.name) {
                if (formats[i].params && formats[i].params.value != format.params.value)
                    formats[i].params.value = format.params.value;
                else
                    formats.splice(i, 1);

                return;
            }

        formats.push(format);
    },

    clear: function() {
        this.formats = [];
    }

});

extend(Editor, {
    PendingFormats: PendingFormats
});

})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.ui.NumericTextBox.Description
     *
     * @section
     * <p>
     *    The NumericTextBox widget can convert an INPUT element into a numeric, percentage or currency textbox.
     *    The type is defined depending on the specified format. The widget renders spin buttons and with their help you can
     *    increment/decrement the value with a predefined step. The NumericTextBox widget accepts only numeric entries.
     *    The widget uses <em>kendo.culture.current</em> culture in order to determine number precision and other culture
     *    specific properties.
     * </p>
     *
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Creating a NumericTextBox from existing INPUT element
     * @example
     * <input id="textBox" />
     *
     * @exampleTitle NumericTextBox initialization
     * @example
     * $(document).ready(function(){
     *  $("#textBox").kendoNumericTextBox();
     * });
     *
     * @section
     * <p>
     *  When a <b>NumericTextBox</b> is initialized, it will automatically
     *  wraps the input element with span element and will render spin
     *  buttons.
     * </p>
     * <h3>Configuring NumericTextBox behaviors</h3>
     * <p>
     *  The <b>NumericTextBox</b> provides configuration options that can be
     *  easily set during initialization. Among the properties that can be
     *  controlled:
     * </p>
     * <ul>
     *  <li>Value of the <b>NumericTextBox</b></li>
     *  <li>Minimum and/or maximum values</li>
     *  <li>Increment step</li>
     *  <li>Precision of the number</li>
     *  <li>Number format (any valid number format is allowed)</li>
     * </ul>
     * <p>
     *  To see a full list of available properties and values, review the
     *  Slider Configuration API documentation tab.
     * </p>
     * @exampleTitle Customizing NumericTextBox defaults
     * @example
     *  $("#textbox").kendoNumericTextBox({
     *      value: 10,
     *      min: -10,
     *      max: 100,
     *      step: 0.75,
     *      format: "n",
     *      decimals: 3
     *  });
     * @section
     * @exampleTitle Create Currency NumericTextBox widget
     * @example
     *  $("#textbox").kendoNumericTextBox({
     *      format: "c2" //Define currency type and 2 digits precision
     *  });
     * @section
     * @exampleTitle Create Percentage NumericTextBox widget
     * @example
     *  $("#textbox").kendoNumericTextBox({
     *      format: "p",
     *      value: 0.15 // 15 %
     *  });
     *
     * @section
     * <h3>Accessing an Existing NumericTextBox</h3>
     * <p>
     *  You can reference an existing <b>NumericTextBox</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing NumericTextBox instance
     * @example
     * var numericTextBox = $("#numericTextBox").data("kendoNumericTextBox");
     *
     */
    var kendo = window.kendo,
        keys = kendo.keys,
        ui = kendo.ui,
        Widget = ui.Widget,
        parse = kendo.parseFloat,
        touch = kendo.support.touch,
        CHANGE = "change",
        DISABLED = "disabled",
        INPUT = "k-input",
        SPIN = "spin",
        TOUCHEND = "touchend",
        MOUSEDOWN = touch ? "touchstart" : "mousedown",
        MOUSEUP = touch ? "touchmove " + TOUCHEND : "mouseup mouseleave",
        HIDE = "k-hide-text",
        DEFAULT = "k-state-default",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter mouseleave",
        POINT = ".",
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        TYPE = touch ? "number" : "text",
        NULL = null,
        proxy = $.proxy,
        decimals = {
            190 : ".",
            188 : ","
        };

    var NumericTextBox = Widget.extend(/** @lends kendo.ui.NumericTextBox.prototype */{
        /**
         * @constructs
         * @extends kendo.ui.Widget
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options
         * @option {Number} [value] <null> Specifies the value of the NumericTextBox widget.
         * _example
         *  // specify in the HTML
         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
         * <br />
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50
         * });
         * @option {Number} [min] <null> Specifies the smallest value the user can enter.
         * _example
         *  // specify in the HTML
         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
         * <br />
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50
         * });
         * @option {Number} [max] <null> Specifies the largest value the user can enter.
         * _example
         *  // specify in the HTML
         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
         * <br />
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50
         * });
         * @option {Number} [decimals] <null> Specifies the number precision. If not set precision defined by current culture is used.
         * _example
         *  // specify in the HTML
         * &lt;input id="numeric" value="10" type="number" min="0" max="1" step=".1" decimals="1"/&gt;
         * <br />
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 1,
         *     step: 0.1,
         *     decimals: 1
         * });
         * @option {Number} [step] <1> Specifies the increment/decrement step.
         * _example
         *  // specify in the HTML
         * &lt;input id="numeric" value="10" type="number" /&gt;
         * <br />
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 1,
         *     step: 0.1
         * });
         * @option {String} [format] <n> Specifies the format of the number. Any valid number format is allowed.
         * _example
         * $("#numeric").kendoNumericTextBox({
         *    format: "p0", // format as percentage with % sign
         *    min: 0,
         *    max: 1,
         *    step: 0.01
         * });
         * @option {String} [placeholder] <""> Specifies the text displayed when the input is empty.
         * _example
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50,
         *     placeHolder: "Select A Value"
         * });
         * @option {String} [upArrowText] <Increase value> Specifies the text of the tooltip on the up arrow.
         * _example
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50,
         *     upArrowText: "More",
         *     downArrowText: "Less"
         * });
         * @option {String} [downArrowText] <Decrease value> Specifies the text of the tooltip on the down arrow.
         * _example
         * // specify on widget initialization
         * $("#numeric").kendoNumericTextBox({
         *     min: 0,
         *     max: 100,
         *     value: 50,
         *     upArrowText: "More",
         *     downArrowText: "Less"
         * });
         */
         init: function(element, options) {
             var that = this,
             isStep = options && options[step] !== undefined,
             min, max, step, value, format;

             Widget.fn.init.call(that, element, options);

             options = that.options;
             element = that.element.addClass(INPUT)
                           .bind({
                               keydown: proxy(that._keydown, that),
                               paste: proxy(that._paste, that),
                               blur: proxy(that._focusout, that)
                           });

             element.closest("form")
                    .bind("reset", function() {
                        setTimeout(function() {
                            that.value(element[0].value);
                        });
                    });

             that._wrapper();
             that._arrows();
             that._input();

             that._text.focus(proxy(that._click, that));

             min = parse(element.attr("min"));
             max = parse(element.attr("max"));
             step = 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;
             }

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

             value = options.value;
             that.value(value !== NULL ? value : element.val());

             that.enable(!element.is('[disabled]'));

             kendo.notify(that);
         },

        options: {
            name: "NumericTextBox",
            decimals: NULL,
            min: NULL,
            max: NULL,
            value: NULL,
            step: 1,
            format: "n",
            placeholder: "",
            upArrowText: "Increase value",
            downArrowText: "Decrease value"
        },
        events: [
             /**
             * Fires when the value is changed
             * @name kendo.ui.NumericTextBox#change
             * @event
             * @param {Event} e
             * @example
             * $("#numeric").kendoNumericTextBox({
             *     change: function(e) {
             *         // handle event
             *     }
             * });
             * @exampleTitle To set after initialization
             * @example
             * // get a reference to the numeric textbox widget
             * var numeric = $("#numeric").data("kendoNumericTextBox");
             * // bind to the change event
             * numeric.bind("change", function(e) {
             *     // handle event
             * });
             */
            CHANGE,
             /**
             * Fires when the value is changed from the spin buttons
             * @name kendo.ui.NumericTextBox#spin
             * @event
             * @param {Event} e
             * @example
             * $("#numeric").kendoNumericTextBox({
             *     spin: function(e) {
             *         // handle event
             *     }
             * });
             * @exampleTitle To set after initialization
             * @example
             * // get a reference to the numeric textbox widget
             * var numeric = $("#numeric").data("kendoNumericTextBox");
             * // bind to the spin event
             * numeric.bind("spin", function(e) {
             *     // handle event
             * });
             */
            SPIN
        ],
        /**
        * Enable/Disable the numerictextbox widget.
        * @param {Boolean} enable The argument, which defines whether to enable/disable tha numerictextbox.
        * @example
        * // get a reference to the numeric textbox
        * var textbox = $("#textbox").data("kendoNumericTextBox");
        *
        * // disables the numerictextbox
        * numerictextbox.enable(false);
        *
        * // enables the numerictextbox
        * numerictextbox.enable(true);
        */
        enable: function(enable) {
            var that = this,
                text = that._text.add(that.element),
                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
                upArrow = that._upArrow.unbind(MOUSEDOWN),
                downArrow = that._downArrow.unbind(MOUSEDOWN);

            that._toggleText(true);

            if (enable === false) {
                wrapper
                    .removeClass(DEFAULT)
                    .addClass(STATEDISABLED)

                text.attr(DISABLED, DISABLED);
            } else {
                wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .bind(HOVEREVENTS, that._toggleHover);

                text.removeAttr(DISABLED);

                upArrow.bind(MOUSEDOWN, function(e) {
                    e.preventDefault();
                    that._spin(1);
                    that._upArrow.addClass(SELECTED);
                });

                downArrow.bind(MOUSEDOWN, function(e) {
                    e.preventDefault();
                    that._spin(-1);
                    that._downArrow.addClass(SELECTED);
                });
            }
        },

        /**
        * Gets/Sets the value of the numerictextbox.
        * @param {Number | String} value The value to set.
        * @returns {Number} The value of the numerictextbox.
        * @example
        * // get a referene to the numeric textbox
        * var numerictextbox = $("#textbox").data("kendoNumericTextBox");
        *
        * // get the value of the numerictextbox.
        * var value = numerictextbox.value();
        *
        * // set the value of the numerictextbox.
        * numerictextbox.value("10.20");
        */
        value: function(value) {
            var that = this, adjusted;

            if (value === undefined) {
                return that._value;
            }

            value = parse(value);
            adjusted = that._adjust(value);

            if (value !== adjusted) {
                return;
            }

            that._update(value);
            that._old = that._value;
        },

        _adjust: function(value) {
            var that = this,
            options = that.options,
            min = options.min,
            max = options.max;

            if (min !== NULL && value < min) {
                value = min;
            } else if (max !== NULL && value > max) {
                value = max;
            }

            return value;
        },

        _arrows: function() {
            var that = this,
            arrows,
            options = that.options,
            element = that.element;

            arrows = element.siblings(".k-icon");

            if (!arrows[0]) {
                arrows = $(buttonHtml("up", options.upArrowText) + buttonHtml("down", options.downArrowText))
                        .insertAfter(element);

                arrows.wrapAll('<span class="k-select"/>');
            }

            arrows.bind(MOUSEUP, function(e) {
                if (!touch || kendo.eventTarget(e) != e.currentTarget || e.type === TOUCHEND) {
                    clearTimeout( that._spinning );
                }
                arrows.removeClass(SELECTED);
            });

            that._upArrow = arrows.eq(0);
            that._downArrow = arrows.eq(1);
        },

        _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),
                    value = input.value.substring(0, idx),
                    format = that._format(that.options.format),
                    group = format[","],
                    groupRegExp = new RegExp("\\" + group, "g"),
                    extractRegExp = new RegExp("([\\d\\" + group + "]+)(\\" + format[POINT] + ")?(\\d+)?"),
                    result = extractRegExp.exec(value),
                    caretPosition = 0;

                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;
                that.trigger(CHANGE);

                // trigger the DOM change event so any subscriber gets notified
                that.element.trigger(CHANGE);
            }
        },

        _focusin: function() {
            var that = this;
            that._toggleText(false);
            that.element.focus();
            that._inputWrapper.addClass(FOCUSED);
        },

        _focusout: function() {
            var that = this;

            that._inputWrapper.removeClass(FOCUSED);
            that._blur();
        },

        _format: function(format) {
            var that = this,
                options = that.options,
                numberFormat = kendo.culture().numberFormat;


            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.show()[0],
                wrapper = that.wrapper,
                text;


            text = wrapper.find(POINT + CLASSNAME);

            if (!text[0]) {
                text = $("<input />").insertBefore(element).addClass(CLASSNAME);
            }

            element.type = TYPE;
            text[0].type = TYPE;

            text[0].style.cssText = element.style.cssText;
            that._text = text.attr("readonly", true).addClass(element.className);
        },

        _keydown: function(e) {
            var that = this,
                key = e.keyCode;

            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());
            }

            if (that._prevent(key) && !e.ctrlKey) {
                e.preventDefault();
            }
        },

        _paste: function(e) {
            var that = this,
                element = e.target,
                value = element.value;

            setTimeout(function() {
                if (parse(element.value) === NULL) {
                    that._update(value);
                }
            });
        },

        _prevent: function(key) {
            var that = this,
                element = that.element[0],
                value = element.value,
                options = that.options,
                min = options.min,
                numberFormat = that._format(options.format),
                separator = numberFormat[POINT],
                precision = options.decimals,
                idx = caret(element),
                prevent = true,
                end;

            if (precision === NULL) {
                precision = numberFormat.decimals;
            }

            if ((key > 16 && key < 21)
             || (key > 32 && key < 37)
             || (key > 47 && key < 58)
             || (key > 95 && key < 106)
             || key == 45 /* INSERT */
             || key == 46 /* DELETE */
             || key == keys.LEFT
             || key == keys.RIGHT
             || key == keys.TAB
             || key == keys.BACKSPACE
             || key == keys.ENTER) {
                prevent = false;
            } else if (decimals[key] === separator && precision > 0 && value.indexOf(separator) == -1) {
                prevent = false;
            } else if ((min === NULL || min < 0) && value.indexOf("-") == -1 && (key == 189 || key == 109) && idx == 0) { //sign
                prevent = false;
            } else if (key == 110 && precision > 0 && value.indexOf(separator) == -1) {
                end = value.substring(idx);

                element.value = value.substring(0, idx) + separator + end;
            }

            return prevent;
        },

        _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 = parse(element.val()) || 0;

            if (document.activeElement != element[0]) {
                that._focusin();
            }

            value += that.options.step * parse(step);

            that._update(that._adjust(value));

            that.trigger(SPIN);
        },

        _toggleHover: function(e) {
            if (!touch) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            }
        },

        _toggleText: function(toggle) {
            var that = this;

            toggle = !!toggle;
            that._text.toggle(toggle);
            that.element.toggle(!toggle);
        },

        _update: function(value) {
            var that = this,
                options = that.options,
                format = options.format,
                decimals = options.decimals,
                numberFormat = that._format(format),
                isNotNull;

            if (decimals === NULL) {
                decimals = numberFormat.decimals;
            }

            value = parse(value);

            isNotNull = value !== NULL;

            if (isNotNull) {
                value = parseFloat(value.toFixed(decimals));
            }

            that._value = value = that._adjust(value);
            that._text.val(isNotNull ? kendo.toString(value, format) : options.placeholder);
            that.element.val(isNotNull ? value.toString().replace(POINT, numberFormat[POINT]) : "");
        },

        _wrapper: function() {
            var that = this,
                element = that.element,
                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 = element[0].style.cssText;
            element[0].style.width = "";
            that.wrapper = wrapper.addClass("k-widget k-numerictextbox").show();
            that._inputWrapper = $(wrapper[0].firstChild);
        }
    });

    function buttonHtml(className, text) {
        return '<span unselectable="on" class="k-link"><span unselectable="on" class="k-icon k-arrow-' + className + '" title="' + text + '">' + text + '</span></span>'
    }

    function caret(element, position) {
        var range,
            isPosition = position !== undefined;

        if (document.selection) {
            element.focus();
            var range = document.selection.createRange();
            if (isPosition) {
                range.move("character", position);
                range.select();
            } else {
                var rangeElement = element.createTextRange(),
                    rangeDuplicated = rangeElement.duplicate();
                    rangeElement.moveToBookmark(range.getBookmark());
                    rangeDuplicated.setEndPoint('EndToStart', rangeElement);

                position = rangeDuplicated.text.length;

            }
        } else if (element.selectionStart !== undefined) {
            if (isPosition) {
                element.focus();
                element.setSelectionRange(position, position);
            } else {
                position = element.selectionStart;
            }
        }

        return position;
    }

    ui.plugin(NumericTextBox);
})(jQuery);
(function ($, undefined) {
    /**
     * @name kendo.ui.Menu.Description
     *
     * @section
     * <p>
     *  The <b>Menu</b> displays hierarchical data as a multi-level menu. It provides rich styling for unordered lists
     *  of items, and can be used for both navigation and executing JavaScript commands. Items can be defined and
     *  initialized from HTML, or the API can be used to add and remove items.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Create a HTML hierarchical list of items
     * @example
     * <ul id="menu">
     *     <li>Item 1
     *         <ul>
     *             <li>Item 1.1</li>
     *             <li>Item 1.2</li>
     *         </ul>
     *     </li>
     *     <li>Item 2</li>
     * </ul>
     *
     * @section
     * <p>
     *  Initialization of a <strong>Menu</strong> should occur after the DOM is fully loaded. It is recommended that
     *  initialization the <strong>Menu</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a Menu using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#menu").kendoMenu();
     * });
     *
     * @exampleTitle Initialize the Menu using JSON data object
     * @example
     * $(document).ready(function() {
     *  $("#menu").kendoMenu({
     *   dataSource: [
     *    {
     *     text: "Menu Item 1",
     *     items: [
     *      { text: "Sub Menu Item 1" },
     *      { text: "Sub Menu Item 2" }
     *     ]
     *    },
     *    {
     *     text: "Menu Item 2"
     *    }
     *   ]
     *  })
     * });
     *
     * @section
     * <h3>Customizing Menu Animations</h3>
     * <p>
     *  By default, the <b>Menu</b> uses a slide animation to expand and
     *  reveal sub-items as the mouse hovers. Animations can be easily
     *  customized using configuration properties, changing the animation
     *  style and delay. Menu items can also be configured to open on click
     *  instead of on hover.
     * </p>
     *
     * @exampleTitle Changing Menu animation and open behavior
     * @example
     * $("#menu").kendoMenu({
     *  animation: {
     *   open: { effects: "fadeIn" },
     *   hoverDelay: 500
     *  },
     *  openOnClick: true
     * });
     *
     * @section
     * <h3>Dynamically configuring Menu items</h3>
     * <p>
     *  The <b>Menu</b> API provides several methods for dynamically adding
     *  or removing Items. To add items, provide the new item as a JSON
     *  object along with a reference item that will be used to determine the
     *  placement in the hierarchy.
     * </p>
     * <p>
     *  A reference item is simply a target Menu Item HTML element that
     *  already exists in the Menu. Any valid jQuery selector can be used to
     *  obtain a reference to the target item. For examples, see the
     *  <a href="../menu/api.html" title="Menu API demos">Menu API demos</a>.
     *  Removing an item only requires a reference to the target element that
     *  should be removed.
     * </p>
     *
     * @exampleTitle Dynamically add a new root Menu item
     * @example
     * var menu = $("#menu").kendoMenu().data("kendoMenu");
     * menu.insertAfter(
     *  { text: "New Menu Item" },
     *  menu.element.children("li:last")
     * );
     *
     * @section
     * <h3>Accessing an Existing Menu</h3>
     * <p>
     *  You can reference an existing <b>Menu</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
     *  Once a reference has been established, you can use the API to control
     *  its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Menu instance
     * @example
     * var menu = $("#menu").data("kendoMenu");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        touch = kendo.support.touch,
        extend = $.extend,
        proxy = $.proxy,
        each = $.each,
        template = kendo.template,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(ul|a|div)$/i,
        IMG = "img",
        OPEN = "open",
        MENU = "k-menu",
        LINK = "k-link",
        LAST = "k-last",
        CLOSE = "close",
        CLICK = touch ? "touchend" : "click",
        TIMER = "timer",
        FIRST = "k-first",
        IMAGE = "k-image",
        EMPTY = ":empty",
        SELECT = "select",
        ZINDEX = "zIndex",
        MOUSEENTER = "mouseenter",
        MOUSELEAVE = "mouseleave",
        KENDOPOPUP = "kendoPopup",
        SLIDEINRIGHT = "slideIn:right",
        DEFAULTSTATE = "k-state-default",
        DISABLEDSTATE = "k-state-disabled",
        groupSelector = ".k-group",
        allItemsSelector = ".k-item",
        disabledSelector = ".k-item.k-state-disabled",
        itemSelector = ".k-item:not(.k-state-disabled)",
        linkSelector = ".k-item:not(.k-state-disabled) > .k-link",

        templates = {
            group: template(
                "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
                    "#= 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) #'>" +
                    "#= itemWrapper(data) #" +
                    "# if (item.items) { #" +
                    "#= subGroup({ items: item.items, menu: menu, group: { expanded: item.expanded } }) #" +
                    "# } #" +
                "</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 = {
            /** @ignore */
            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";
                }

                return result;
            },
            /** @ignore */
            textClass: function(item) {
                return LINK;
            },
            /** @ignore */
            textAttributes: function(item) {
                return item.url ? " href='" + item.url + "'" : "";
            },
            /** @ignore */
            arrowClass: function(item, group) {
                var result = "k-icon";

                if (group.horizontal) {
                    result += " k-arrow-down";
                } else {
                    result += " k-arrow-right";
                }

                return result;
            },
            /** @ignore */
            text: function(item) {
                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
            },
            /** @ignore */
            tag: function(item) {
                return item.url ? "a" : "span";
            },
            /** @ignore */
            groupAttributes: function(group) {
                return group.expanded !== true ? " style='display:none'" : "";
            },
            /** @ignore */
            groupCssClass: function(group) {
                return "k-group";
            }
        };

    function getEffectDirection(direction, root) {
        direction = direction.split(" ")[!root+0] || direction;
        return direction.replace("top", "up").replace("bottom", "down");
    }

    function parseDirection(direction, root) {
        direction = direction.split(" ")[!root+0] || direction;
        var output = { origin: [ "bottom", "left" ], position: [ "top", "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
            .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");
        item
            .children("a:focus")
            .parent()
            .addClass("k-state-active");

        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-icon").remove();

        item.filter(":has(.k-group)")
            .children(".k-link:not(:has([class*=k-arrow]))")
            .each(function () {
                var item = $(this),
                    parent = item.parent().parent();

                item.append("<span class='k-icon " + (parent.hasClass(MENU + "-horizontal") ? "k-arrow-down" : "k-arrow-next") + "'/>");
            });
    }

    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({/** @lends kendo.ui.Menu.prototype */
        /**
         * Creates a Menu instance.
         * @constructs
         * @extends kendo.ui.Widget
         * @class Menu UI widget
         * @param {Selector} element DOM element
         * @param {Object} options Configuration options.
         * @option {Object} [animation] A collection of <b>Animation</b> objects, used to change default animations. A value of false will disable all animations in the widget.
         * <p>Available animations for the <b>Menu</b> are listed below.  Each animation has a reverse options which is used for the <b>close</b> effect by default, but can be over-ridden
         * by setting the <b>close</b> animation.  Each animation also has a direction which can be set off the animation (i.e. <b>slideIn:Down</b>).</p>
         * <div class="details-list">
         * <dl>
         *     <dt><b>slideIn</b></dt>
         *     <dd>Menu content slides in from the top</dd>
         *     <dt><b>fadeIn</b></dt>
         *     <dd>Menu content fades in</dd>
         *     <dt><b>expand</b></dt>
         *     <dd>Menu content expands from the top down. Similar to slideIn.</dd>
         * </dl>
         * </div>
         * _example
         *  $("#menu").kendoMenu({
         *      animation: { open: { effects: "fadeIn" } }
         *  });
         * @option {Animation} [animation.open] The animation that will be used when opening sub menus.
         * @option {Animation} [animation.close] The animation that will be used when closing sub menus.
         * @option {String} [orientation] <"horizontal"> Root menu orientation. Could be horizontal or vertical.
         * _example
         *  $("#menu").kendoMenu({
         *      orientation: "vertical"
         *  });
         * @option {Boolean} [closeOnClick] <true> Specifies that sub menus should close after item selection (provided they won't navigate).
         * _example
         *  $("#menu").kendoMenu({
         *      closeOnClick: false
         *  });
         * @option {Boolean} [openOnClick] <false> Specifies that the root sub menus will be opened on item click.
         * _example
         *  $("#menu").kendoMenu({
         *      openOnClick: true
         *  });
         * @option {Number} [hoverDelay] <100> Specifies the delay in ms before the menu is opened/closed - used to avoid accidental closure on leaving.
         * _example
         *  $("#menu").kendoMenu({
         *      hoverDelay: 200
         *  });
         * @option {String} [direction] <"default"> Specifies Menu opening direction. Can be "top", "bottom", "left", "right".
         * You can also specify different direction for root and sub menu items, separating them with space. The example below will initialize the root menu to open upwards and
         * its sub menus to the left.
         * _example
         * $("#menu").kendoMenu({
         *     direction: "top left"
         * });
         */
        init: function(element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            element = that.wrapper = that.element;

            options = that.options;

            if (options.dataSource) {
                that.element.empty().append($(Menu.renderGroup({
                    items: options.dataSource,
                    group: {
                        firstLevel: true,
                        horizontal: that.element.hasClass(MENU + "-horizontal"),
                        expanded: true
                    },
                    menu: {}
                })).children());
            }

            that._updateClasses();

            if (options.animation === false) {
                options.animation = { open: { show: true, effects: {} }, close: { hide: true, effects: {} } };
            }

            that.nextItemZIndex = 100;

            element.delegate(disabledSelector, CLICK, false)
                   .delegate(itemSelector, CLICK, proxy(that._click , that));

            if (!touch) {
                element.delegate(itemSelector, MOUSEENTER, proxy(that._mouseenter, that))
                       .delegate(itemSelector, MOUSELEAVE, proxy(that._mouseleave, that))
                       .delegate(linkSelector, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover);
            } else {
                options.openOnClick = true;
                element.delegate(linkSelector, "touchstart touchend", that._toggleHover);
            }

            $(document).click(proxy( that._documentClick, that ));
            that.clicked = false;

            kendo.notify(that);
        },

        events: [
            /**
            * Fires before a sub menu gets opened.
            * @name kendo.ui.Menu#open
            * @event
            * @param {Event} e
            * @param {Element} e.item The opened item
            * @example
            *  $("#menu").kendoMenu({
            *      open: function(e) {
            *          // handle event
            *      }
            *  });
            * @exampleTitle To set after initialization
            * @example
            *  // get a reference to the menu widget
            *  var menu = $("#menu").data("kendoMenu");
            *  // bind to the open event
            *  menu.bind("open", function(e) {
            *      // handle event
            *  });
            */
            OPEN,
            /**
            * Fires after a sub menu gets closed.
            * @name kendo.ui.Menu#close
            * @event
            * @param {Event} e
            * @param {Element} e.item The closed item
            * @example
            *  $("#menu").kendoMenu({
            *      close: function(e) {
            *          // handle event
            *      }
            *  });
            * @exampleTitle To set after initialization
            * @example
            *  // get a reference to the menu widget
            *  var menu = $("#menu").data("kendoMenu");
            *  // bind to the close event
            *  menu.bind("close", function(e) {
            *      // handle event
            *  });
            */
            CLOSE,
            /**
            * Fires when a menu item gets selected.
            * @name kendo.ui.Menu#select
            * @event
            * @param {Event} e
            * @param {Element} e.item The selected item
            * @example
            *  $("#menu").kendoMenu({
            *      select: function(e) {
            *          // handle event
            *      }
            *  });
            * @exampleTitle To set after initialization
            * @example
            *  // get a reference to the menu widget
            *  var menu = $("#menu").data("kendoMenu");
            *  // bind to the select event
            *  menu.bind("select", function(e) {
            *      // handle event
            *  });
            */
            SELECT
        ],

        options: {
            name: "Menu",
            animation: {
                open: {
                    duration: 200,
                    show: true
                },
                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
        },

        /**
         *
         * Enables or disables an item of a <strong>Menu</strong>. This can optionally be accomplished on
         * initialization by setting the <b>disabled="disabled"</b> on the desired menu item html element.
         *
         * @param {Selector} element
         * Target element
         *
         * @param {Boolean} enable
         * Desired state
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * // disable the li menu item with the id "secondItem"
         * menu.enable("#secondItem", false);
         */
        enable: function (element, enable) {
            this._toggleDisabled(element, enable !== false);

            return this;
        },

        disable: function (element) {
            this._toggleDisabled(element, false);

            return this;
        },

        /**
         *
         * Appends an item to a <strong>Menu</strong> in the specified referenceItem's sub menu.
         *
         * @param {Selector} item
         * Target item, specified as a JSON object. Can also handle an array of such objects.
         *
         * @param {Item} referenceItem
         * A reference item to append the new item in.
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * //
         * menu.append(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
         *         items: [{                                    // Sub item collection
         *              text: "Sub Item 1"
         *         },
         *         {
         *              text: "Sub Item 2"
         *         }]
         *     },
         *     {
         *         text: "Item 3",
         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         */
        append: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find("> .k-group, .k-animation-container > .k-group") : null);

            each(inserted.items, function () {
                inserted.group.append(this);
                updateFirstLast(this);
            });

            updateArrow(referenceItem);
            updateFirstLast(inserted.group.find(".k-first, .k-last"));

            return this;
        },

        /**
         *
         * Inserts an item into a <strong>Menu</strong> before the specified referenceItem.
         *
         * @param {Selector} item
         * Target item, specified as a JSON object. Can also handle an array of such objects.
         *
         * @param {Selector} referenceItem
         * A reference item to insert the new item before
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * //
         * menu.insertBefore(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
         *         items: [{                                    // Sub item collection
         *              text: "Sub Item 1"
         *         },
         *         {
         *              text: "Sub Item 2"
         *         }]
         *     },
         *     {
         *         text: "Item 3",
         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         */
        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);

            return this;
        },

        /**
         *
         * Inserts an item into a <strong>Menu</strong> after the specified referenceItem.
         *
         * @param {Selector} item
         * Target item, specified as a JSON object. Can also handle an array of such objects.
         *
         * @param {Selector} referenceItem
         * A reference item to insert the new item after.
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * //
         * menu.insertAfter(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
         *         items: [{                                    // Sub item collection
         *              text: "Sub Item 1"
         *         },
         *         {
         *              text: "Sub Item 2"
         *         }]
         *     },
         *     {
         *         text: "Item 3",
         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         *
         */
        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);

            return this;
        },

        _insert: function (item, referenceItem, parent) {
            var that = this;

            if (!referenceItem || !referenceItem.length) {
                parent = that.element;
            }

            var plain = $.isPlainObject(item),
                items,
                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) {
                            return $(Menu.renderItem({
                                group: groupData,
                                item: extend(value, { index: idx })
                            }));
                        });
            } else {
                items = $(item);

                updateItemClasses(items);
            }

            return { items: items, group: parent };
        },

        /**
         *
         * Removes a specified item(s) from a <strong>Menu</strong>.
         *
         * @param {Selector} element
         * Target item selector.
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * // remove the item with the id "Item1"
         * menu.remove("#Item1");
         *
         */
        remove: function (element) {
            element = this.element.find(element);

            var that = this,
                parent = element.parentsUntil(that.element, allItemsSelector),
                group = element.parent("ul");

            element.remove();

            if (group && !group.children(allItemsSelector).length) {
                var container = group.parent(".k-animation-container");
                container.length ? container.remove() : group.remove();
            }

            if (parent.length) {
                parent = parent.eq(0);

                updateArrow(parent);
                updateFirstLast(parent);
            }

            return that;
        },

        /**
         *
         * Opens a sub-menu of a specified item(s) in a <strong>Menu</strong>.
         *
         * @param {Selector} element
         * Target item selector.
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * // open the sub menu of "Item1"
         * menu.open("#Item1");
         *
         */
        open: function (element) {
            var that = this,
                options = that.options,
                horizontal = options.orientation == "horizontal",
                direction = options.direction;
            element = that.element.find(element);

            if (/^(top|bottom|default)$/.test(direction)) {
                direction = horizontal ? (direction + " right").replace("default", "bottom") : "right";
            }

            element.each(function () {
                var li = $(this);

                clearTimeout(li.data(TIMER));

                li.data(TIMER, setTimeout(function () {
                    var ul = li.find(".k-group:first:hidden"), popup;

                    if (ul[0] && that.trigger(OPEN, { item: li[0] }) === false) {
                        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),
                            effects = options.animation.open.effects,
                            openEffects = effects !== undefined ? effects : "slideIn:" + getEffectDirection(direction, root);

                        if (!popup) {
                            popup = ul.kendoPopup({
                                origin: directions.origin,
                                position: directions.position,
                                collision: parentHorizontal ? "fit" : "fit flip",
                                anchor: li,
                                appendTo: li,
                                animation: {
                                    open: extend(true, { effects: openEffects }, options.animation.open),
                                    close: options.animation.close
                                }
                            }).data(KENDOPOPUP);
                        } else {
                            popup = ul.data(KENDOPOPUP);
                            popup.options.origin = directions.origin;
                            popup.options.position = directions.position;
                            popup.options.animation.open.effects = openEffects;
                        }

                        popup.open();
                    }

                }, that.options.hoverDelay));
            });

            return that;
        },

        /**
         *
         * Closes a sub-menu of a specified item(s) in a <strong>Menu</strong>.
         *
         * @param {Selector} element Target item selector.
         *
         * @returns {Menu}
         * Returns the Menu object to support chaining.
         *
         * @example
         * // get a reference to the menu widget
         * var menu = $("#menu").data("kendoMenu");
         * // close the sub menu of "Item1"
         * menu.close("#Item1");
         *
         */
        close: function (element) {
            var that = this;
            element = that.element.find(element);

            element.each(function () {
                var li = $(this);

                clearTimeout(li.data(TIMER));

                li.data(TIMER, setTimeout(function () {
                    var ul = li.find(".k-group:first:visible"), popup;
                    if (ul[0] && that.trigger(CLOSE, { item: li[0] }) === false) {
                        li.css(ZINDEX, li.data(ZINDEX));
                        li.removeData(ZINDEX);

                        popup = ul.data(KENDOPOPUP);
                        popup.close();
                    }
                }, that.options.hoverDelay));
            });

            return that;
        },

        _toggleDisabled: function (element, enable) {
            element = this.element.find(element);
            element.each(function () {
                $(this)
                    .toggleClass(DEFAULTSTATE, enable)
                    .toggleClass(DISABLEDSTATE, !enable);
            });
        },

        _toggleHover: function(e) {
            var target = $(kendo.eventTarget(e)).closest(allItemsSelector);

            if (!target.parents("li." + DISABLEDSTATE).length) {
                target.toggleClass("k-state-hover", e.type == MOUSEENTER || e.type == "touchstart");
            }
        },

        _updateClasses: function() {
            var that = this;

            that.element.addClass("k-widget k-reset k-header " + MENU).addClass(MENU + "-" + that.options.orientation);

            var items = that.element
                            .find("li > ul")
                            .addClass("k-group")
                            .end()
                            .find("> li,.k-group > li")
                            .addClass("k-item");

            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 (!that.options.openOnClick || that.clicked) {
                if (!contains(e.currentTarget, e.relatedTarget) && hasChildren) {
                    that.open(element);
                }
            }

            if (that.options.openOnClick && that.clicked) {
                element.siblings().each(proxy(function (_, sibling) {
                    that.close(sibling);
                }, that));
            }
        },

        _mouseleave: function (e) {
            var that = this,
                element = $(e.currentTarget),
                hasChildren = (element.children(".k-animation-container").length || element.children(groupSelector).length);

            if (!that.options.openOnClick && !contains(e.currentTarget, e.relatedTarget) && hasChildren) {
                that.close(element);
            }
        },

        _click: function (e) {
            var that = this, openHandle,
                target = $(kendo.eventTarget(e)),
                link = target.closest("." + LINK),
                href = link.attr("href"),
                element = target.closest(allItemsSelector);

            if (element.hasClass(DISABLEDSTATE)) {
                e.preventDefault();
                return;
            }

            if (touch) {
                element.siblings().each(proxy(function (_, sibling) {
                    that.close(sibling);
                }, that));
            }

            if (!e.handled) // We shouldn't stop propagation.
                that.trigger(SELECT, { item: element[0] });

            e.handled = true;

            if (that.options.closeOnClick && !(href && href.length > 0) && !element.children(groupSelector + ",.k-animation-container").length) {
                that.close(link.parentsUntil(that.element, allItemsSelector));
            }

            if ((!element.parent().hasClass(MENU) || !that.options.openOnClick) && !touch) {
                return;
            }

            e.preventDefault();

            that.clicked = true;
            openHandle = element.children(".k-animation-container, .k-group").is(":visible") ? CLOSE : OPEN;
            that[openHandle](element);
        },

        _documentClick: function (e) {
            var that = this;

            if (contains(that.element[0], e.target)) {
                return;
            }

            if (that.clicked) {
                that.clicked = false;
                that.close(that.element.find(".k-item>.k-animation-container:visible").parent());
            }
        }
    });

    // client-side rendering
    extend(Menu, {
        renderItem: function (options) {
            options = extend({ menu: {}, group: {} }, options);

            var empty = templates.empty,
                item = options.item,
                menu = options.menu;

            return templates.item(extend(options, {
                image: item.imageUrl ? templates.image : empty,
                sprite: item.spriteCssClass ? templates.sprite : empty,
                itemWrapper: templates.itemWrapper,
                arrow: item.items ? 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));
        }
    });

    kendo.ui.plugin(Menu);

})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        extend = $.extend,
        isFunction = $.isFunction,
        isPlainObject = $.isPlainObject,
        inArray = $.inArray,
        Binder = kendo.data.ModelViewBinder,
        Validator = ui.Validator,
        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) {
        return field.type || $.type(field) || "string";
    }

    function createAttributes(options) {
        var field = (options.model.fields || options.model)[options.field],
            type = fieldType(field),
            validation = 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;
    }

    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);
            attr[kendo.attr("format")] = options.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);
        }
    };

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

            Widget.fn.init.call(that, element, options);

            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 || {},
                type = fieldType(modelField),
                isCustomEditor = isObject && field.editor,
                editor = isCustomEditor ? field.editor : editors[type],
                container = that.element.find("[data-container-for=" + fieldName + "]");

            editor = editor ? editor : editors["string"];

            if (modelField) {
                container = container.length ? container : that.element;
                editor(container, extend(true, {}, isObject ? field : { field: fieldName }, { model: model }));

                if (isCustomEditor) {
                    container.find(":input:not(:button), select").each(function() {
                        var bindAttr = kendo.attr("bind"),
                            binding = this.getAttribute(bindAttr) || "";

                        if (binding.indexOf("value:") === -1) {
                            binding += ",value:" + fieldName;

                            $(this).attr(bindAttr, binding);
                        }
                    });
                }
           }
        },

        _validate: function(e) {
            var that = this,
                model = that.options.model,
                isBoolean = typeof e.value === "boolean",
                input,
                values = {};

            values[e.field] = e.value;

            input = $(':input[' + kendo.attr("bind") + '="' + (isBoolean ? 'checked:' : 'value:') + e.field + '"]', that.element);

            if (!that.validatable.validateInput(input) || that.trigger(CHANGE, { values: values })) {
                e.preventDefault();
            }
        },

        end: function() {
            return this.validatable.validate();
        },

        distroy: function() {
            kendo.unbind(this.element);

            this.element.removeData("kendoValidator")
                .removeData("kendoEditable");
        },

        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 = {};

            if (!$.isArray(fields)) {
                fields = [fields];
            }

            for (idx = 0, length = fields.length; idx < length; idx++) {
                var field = fields[idx],
                    isObject = isPlainObject(field),
                    fieldName = isObject ? field.field : field,
                    modelField = (model.fields || model)[fieldName],
                    type = fieldType(modelField),
                    validation = modelField ? (modelField.validation || {}) : {};

                for (var rule in validation) {
                    if (isFunction(validation[rule])) {
                        rules[rule] = validation[rule];
                    }
                }

                that.editor(field, modelField);
            }

            kendo.bind(container, that.options.model);

            that.options.model.bind("set", $.proxy(that._validate, that));

            that.validatable = container.kendoValidator({
                validateOnBlur: false,
                errorTemplate: that.options.errorTemplate || undefined,
                /*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>', */
                rules: rules }).data("kendoValidator");

            container.find(":input:visible:first").focus();
        }
   });

   ui.plugin(Editable);
})(jQuery);
(function($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        DROPDOWNLIST = "kendoDropDownList",
        NUMERICTEXTBOX = "kendoNumericTextBox",
        DATEPICKER = "kendoDatePicker",
        proxy = $.proxy,
        POPUP = "kendoPopup",
        EQ = "Is equal to",
        NEQ = "Is not equal to",
        Widget = ui.Widget;

    var booleanTemplate =
            '<div>' +
                '<input type="hidden" name="filters[0].field" value="#=field#"/>' +
                '<input type="hidden" name="filters[0].operator" value="eq"/>' +
                '<div class="k-filter-help-text">#=messages.info#</div>'+
                '<label>#=messages.isTrue#'+
                    '<input type="radio" name="filters[0].value" value="true"/>' +
                '</label>' +
                '<label>#=messages.isFalse#'+
                    '<input type="radio" name="filters[0].value" value="false"/>' +
                '</label>' +
                '<button type="submit" class="k-button">#=messages.filter#</button>'+
                '<button type="reset" class="k-button">#=messages.clear#</button>'+
            '</div>';

    var defaultTemplate =
            '<div>' +
                '<input type="hidden" name="filters[0].field" value="#=field#"/>' +
                '#if(extra){#' + '<input type="hidden" name="filters[1].field" value="#=field#"/>' + '#}#'+
                '<div class="k-filter-help-text">#=messages.info#</div>'+
                '<select name="filters[0].operator">'+
                    '#for(var op in operators){#'+
                        '<option value="#=op#">#=operators[op]#</option>'+
                    '#}#'+
                '</select>'+
                '<input name="filters[0].value" class="k-widget k-input k-autocomplete" type="text" data-#=ns#type="#=type#"/>'+
                '#if(extra){#'+
                    '<select name="logic" class="k-filter-and">'+
                        '<option value="and">And</option>'+
                        '<option value="or">Or</option>'+
                    '</select>'+
                    '<select name="filters[1].operator">'+
                        '#for(var op in operators){#'+
                            '<option value="#=op#">#=operators[op]#</option>'+
                        '#}#'+
                    '</select>'+
                    '<input name="filters[1].value" class="k-widget k-input k-autocomplete" type="text" data-#=ns#type="#=type#"/>'+
                '#}#'+
                '<button type="submit" class="k-button">#=messages.filter#</button>'+
                '<button type="reset" class="k-button">#=messages.clear#</button>'+
            '</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 value(dom, value) {
        var widget = dom.data(DROPDOWNLIST) || dom.data(NUMERICTEXTBOX) || dom.data(DATEPICKER);

        if (widget) {
            widget.value(value);
        } else if (dom.is(":radio")) {
            dom.filter("[value=" + value + "]").attr("checked", "checked");
        } else {
            dom.val(value);
        }
    }

    function toObject(array) {
        var result = {},
            idx,
            length,
            name,
            members,
            member,
            value,
            interimResult,
            previousMember,
            parentResult;

        for (idx = 0, length = array.length; idx < length; idx++) {
            members = array[idx].name.split(/[\.\[\]]+/);

            members = $.grep(members, function(value){ return value });

            value = array[idx].value;

            interimResult = result;

            parentResult = result;

            for (member = 0; member < members.length - 1; member++) {
                name = members[member];

                if (!isNaN(name)) {
                    previousMember = members[member-1];

                    if (!$.isArray(parentResult[previousMember])) {
                        interimResult = parentResult[previousMember] = [];
                    }
                }

                parentResult = interimResult;

                interimResult = interimResult[name] = interimResult[name] || {};
            }

            interimResult[members[member]] = value;
        }

        return result;
    }

    var FilterMenu = Widget.extend({
        init: function(element, options) {
            var that = this,
                type = "string",
                link,
                field,
                getter,
                operators;

            Widget.fn.init.call(that, element, options);

            operators = options.operators || {};
            element = that.element;
            options = that.options;

            link = element.addClass("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.click(proxy(that._click, that));

            that.dataSource = options.dataSource.bind("change", proxy(that.refresh, that));

            that.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;
                    that._parse = proxy(field.parse, field);
                }
            }

            operators = operators[type] || options.operators[type];

            that.form = $('<form class="k-filter-menu k-group"/>');
            that.form.html(kendo.template(type === "boolean" ? booleanTemplate : defaultTemplate)({
                field: that.field,
                ns: kendo.ns,
                messages: options.messages,
                extra: options.extra,
                operators: operators,
                type: type
            }));

            that.popup = that.form[POPUP]({
                anchor: link,
                open: proxy(that._open, that)
            }).data(POPUP);

            that.link = link;

            that.form
                .bind({
                    submit: proxy(that._submit, that),
                    reset: proxy(that._reset, that)
                })
                .find("select")
                [DROPDOWNLIST]()
                .end()
                .find("[" + kendo.attr("type") + "=number]")
                [NUMERICTEXTBOX]()
                .end()
                .find("[" + kendo.attr("type") + "=date]")
                [DATEPICKER]();

            that.refresh();
        },

        refresh: function() {
            var that = this,
                expression = that.dataSource.filter() || { filters: [], logic: "and" };

            if (that._populateForm(expression)) {
                that.link.addClass("k-state-active");
            } else {
                that.link.removeClass("k-state-active");
            }
        },

        _populateForm: function(expression) {
            var that = this,
                filters = expression.filters,
                idx,
                length,
                form = that.form,
                found = false,
                current = 0,
                filter;

            for (idx = 0, length = filters.length; idx < length; idx++) {
                filter = filters[idx];
                if (filter.field == that.field) {
                    value(form.find("[name='filters[" + current + "].value']"), that._parse(filter.value));
                    value(form.find("[name='filters[" + current + "].operator']"), filter.operator);
                    value(form.find("[name=logic]"), expression.logic);

                    current++;
                    found = true;
                } else if (filter.filters) {
                    found = found || that._populateForm(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 != "";
            });

            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 = $.grep(filter.filters, function(expr) {
                        return expr.field != that.field;
                    });

                    return filter.filters.length;
                }

                return filter.field != that.field;
            });

            if (!expression.filters.length) {
                expression = null;
            }

            that.dataSource.filter(expression);
        },

        _submit: function(e) {
            var that = this;

            e.preventDefault();

            that.filter(toObject(that.form.serializeArray()));

            that.popup.close();
        },

        _reset: function(e) {
            this.clear();
            this.popup.close();
        },

        _click: function(e) {
            e.preventDefault();
            e.stopPropagation();
            this.popup.toggle();
        },

        _open: function() {
            $(".k-filter-menu").not(this.form).each(function() {
                $(this).data(POPUP).close();
            });
        },

        options: {
            name: "FilterMenu",
            extra: true,
            type: "string",
            operators: {
                string: {
                    eq: EQ,
                    neq: NEQ,
                    startswith: "Starts with",
                    contains: "Contains",
                    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"
                }
            },
            messages: {
                info: "Show rows with value that:",
                isTrue: "is true",
                isFalse: "is false",
                filter: "Filter",
                clear: "Clear"
            }
        }
    });

    ui.plugin(FilterMenu);
})(jQuery);
/**
 * @fileOverview Provides a PanelBar implementation which can be used to
 * display a hierarchical data as a multi-level, expandable panel bar.
 */

(function($, undefined) {
    /**
     * @name kendo.ui.PanelBar.Description
     *
     * @section
     * <p>
     *  The <strong>PanelBar</strong> displays hierarchical data as a multi-level, expandable widget that is useful for
     *  constained areas of a page. Its structure may be defined in HTML or configured dynamically through its API. The
     *  content for items can also be loaded via AJAX by specifying a content URL.
     * </p>
     * <h3>Getting Started</h3>
     * <p>
     *  A <strong>PanelBar</strong> can be created by targeting the root element of a HTML list. A
     *  <strong>PanelBar</strong> will utilize this list to define its structure and content.
     * </p>
     *
     * @exampleTitle Create a list of items
     * @example
     * <ul id="panelBar">
     *     <li>
     *         Item 1
     *             <ul>
     *                 <li>Sub Item 1</li>
     *                 <li>Sub Item 2</li>
     *             </ul>
     *     <li>
     *     <li>Item 2</li>
     * </ul>
     *
     * @section
     * <p></p>
     * <p>
     *  Initialization of a <strong>PanelBar</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>PanelBar</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize the PanelBar via an ID selector
     * @example
     * $(document).ready(function() {
     *     $("#panelBar").kendoPanelBar();
     * });
     *
     * @section
     * <p>
     *  <strong>PanelBar</strong> items may contain nested content (including markup) within a <strong>div</strong>
     *  element. Text content located outside nested content will be used as the title of the item.
     * </p>
     *
     * @exampleTitle Create a list of items in HTML with nested content
     * @example
     * <ul id="panelBar">
     *     <li>Item with no content</li>
     *     <li>Item with content
     *         <div>This is nested content of a PanelBar item.</div>
     *     </li>
     * </ul>
     *
     * @section
     * <p>A <strong>PanelBar</strong> will preserve the content defined within an item.</p>
     *
     * @exampleTitle Initialize the PanelBar via an ID selector
     * @example
     * var panelBar = $("#panelbar").kendoPanelBar();
     *
     * @exampleTitle Initialize a PanelBar using JSON data object
     * @example
     * $("#panelbar").kendoPanelBar({
     *     dataSource: [
     *         {
     *             text: "Item 1",
     *             items: [
     *                 { text: "Sub Item 1" },
     *                 { text: "Sub Item 2" }
     *             ]
     *         },
     *         { text: "Item 2" }
     *     ]
     * });
     *
     * @section
     * <h3>Loading Content with AJAX</h3>
     * <p>
     *  While any valid technique for loading AJAX content can be used, the <strong>PanelBar</strong> provides built-in
     *  support for asynchronously loading content from URLs. These URLs should return HTML fragments that can be
     *  loaded in the <strong>PanelBar</strong> item content area.
     * </p>
     *
     * @exampleTitle Create a list of items with a target for dynamic content
     * @example
     * <ul id="panelBar">
     *     <li>Item 1
     *         <ul>
     *             <li>Sub Item 1</li>
     *         </ul>
     *     </li>
     *     <li>Item 2</li>
     *     <li>
     *         Item with Dynamic Content
     *         <div></div>
     *     </li>
     * </ul>
     *
     * @exampleTitle Load a PanelBar item content asynchronously via AJAX
     * @example
     * $("#panelBar").kendoPanelBar({
     *     contentUrls:[
     *         null,
     *         null,
     *         "html-content-snippet.html"
     *     ]
     * });
     *
     * @section
     * <p>
     *  When the <strong>PanelBar</strong> loads remote content via AJAX, the server response is cached in-memory so
     *  that subsequent expand/collapse actions do not trigger subsequent AJAX requests.
     * </p>
     * <h3>Customizing PanelBar Animations</h3>
     * <p>
     *  By default, a <strong>PanelBar</strong> uses animations to expand and reveal sub-items when an item header is
     *  clicked. These animations can be modified in configuration via the open and close animation properties. A
     *  <strong>PanelBar</strong> can also be configured to only allow one panel to remain open at a time.
     * </p>
     *
     * @exampleTitle Changing PanelBar animation and expandMode behavior
     * @example
     * $("#panelBar").kendoPanelBar({
     *     animation: {
     *         open : { effects: "fadeIn" }
     *     },
     *     expandMode: "single"
     * });
     *
     * @section
     * <h3>Dynamically Configuring PanelBar Items</h3>
     * <p>
     *  The <strong>PanelBar</strong> API provides several methods for dynamically adding or removing Items. To add
     *  items, provide the new item as a JSON object along with a reference item that will be used to determine its
     *  placement in the items hierarchy. Note: The reference item is optional when appending.
     * </p>
     * <p>
     *  A reference item is a target <strong>PanelBar</strong> item HTML element that already exists in the PanelBar.
     *  Any valid selector can be used to obtain a reference to the target item.
     * </p>
     * <p>Removing an item only requires a reference to the target element that should be removed.</p>
     *
     * @exampleTitle Dynamically adding a new root PanelBar item
     * @example
     * var panelBar = $("#panelBar").kendoPanelBar().data("kendoPanelBar");
     *
     * panelBar.insertAfter(
     *      { text: "New PanelBar Item" },
     *      panelBar.element.children("li:last")
     * );
     *
     * @section
     * <h3>Accessing an Existing PanelBar</h3>
     * <p>
     *  You can reference an existing <strong>PanelBar</strong> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing PanelBar instance
     * @example
     * var panelBar = $("#panelBar").data("kendoPanelBar");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        extend = $.extend,
        each = $.each,
        template = kendo.template,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(ul|a|div)$/i,
        IMG = "img",
        HREF = "href",
        LAST = "k-last",
        LINK = "k-link",
        ERROR = "error",
        CLICK = "click",
        ITEM = ".k-item",
        IMAGE = "k-image",
        FIRST = "k-first",
        EXPAND = "expand",
        SELECT = "select",
        CONTENT = "k-content",
        COLLAPSE = "collapse",
        CONTENTURL = "contentUrl",
        MOUSEENTER = "mouseenter",
        MOUSELEAVE = "mouseleave",
        CONTENTLOAD = "contentLoad",
        ACTIVECLASS = ".k-state-active",
        GROUPS = "> .k-panel",
        CONTENTS = "> .k-content",
        SELECTEDCLASS = ".k-state-selected",
        DISABLEDCLASS = ".k-state-disabled",
        HIGHLIGHTEDCLASS = ".k-state-highlighted",
        clickableItems = ITEM + ":not(.k-state-disabled) .k-link",
        disabledItems = ITEM + ".k-state-disabled .k-link",
        defaultState = "k-state-default",
        VISIBLE = ":visible",
        EMPTY = ":empty",
        SINGLE = "single",
        animating = false,

        templates = {
            content: template(
                "<div class='k-content'#= contentAttributes(data) #>#= content(item) #</div>"
            ),
            group: template(
                "<ul 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 class='#= wrapperCssClass(group, item) #'>" +
                    "#= itemWrapper(data) #" +
                    "# if (item.items) { #" +
                    "#= subGroup({ items: item.items, panelBar: panelBar, group: { expanded: item.expanded } }) #" +
                    "# } #" +
                "</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 (index == 0) {
                    result += " k-first"
                }

                if (index == group.length-1) {
                    result += " k-last";
                }

                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, group) {
                var result = "k-icon";

                if (group.horizontal) {
                    result += " k-arrow-down";
                } else {
                    result += " k-arrow-right";
                }

                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(group) {
                return "k-group k-panel";
            },
            contentAttributes: function(content) {
                return content.active !== true ? " style='display:none'" : "";
            },
            content: function(item) {
                return item.content ? item.content : item.contentUrl ? "" : "&nbsp;";
            },
            contentUrl: function(item) {
                return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
            }
        };

    function updateItemClasses (item, panelElement) {
        item = $(item).addClass("k-item");

        item
            .children(IMG)
            .addClass(IMAGE);
        item
            .children("a")
            .addClass(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")
            .removeAttr("disabled");
        item
            .filter(":not([class*=k-state])")
            .children("a:focus")
            .parent()
            .addClass(ACTIVECLASS.substr(1));
        item
            .find(">div")
            .addClass(CONTENT)
            .css({ display: "none" });

        item.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("<span class='" + LINK + "'/>");
            }
        });

        panelElement
            .find(" > li > ." + LINK)
            .addClass("k-header");
    }

    function updateArrow (items) {
        items = $(items);

        items.children(".k-link").children(".k-icon").remove();

        items
            .filter(":has(.k-panel),:has(.k-content)")
            .children(".k-link:not(:has([class*=k-arrow]))")
            .each(function () {
                var item = $(this),
                    parent = item.parent();

                item.append("<span class='k-icon " + (parent.hasClass(ACTIVECLASS.substr(1)) ? "k-arrow-up k-panelbar-collapse" : "k-arrow-down 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({/** @lends kendo.ui.PanelBar.prototype */
        /**
         *
         * Creates a PanelBar instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {Selector} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Object} [animation]
         * A collection of visual animations used when <strong>PanelBar</strong> items are opened or closed through
         * user interactions. Setting this option to <strong>false</strong> will disable all animations.
         *
         * _exampleTitle Defining custom animations when opening and closing items
         * _example
         * $("#panelBar").kendoPanelBar({
         *     animation: {
         *         // fade-out closing items over 1000 milliseconds
         *         close: {
         *             duration: 1000,
         *             effects: "fadeOut"
         *         },
         *        // fade-in and expand opening items over 500 milliseconds
         *        open: {
         *            duration: 500,
         *            effects: "expandVertical fadeIn"
         *        }
         *    }
         * });
         *
         * @option {Object} [animation.open]
         * The visual animation(s) that will be used when opening items.
         *
         * _exampleTitle Defining a custom animation when opening items that executes over 200 milliseconds
         * _example
         * $("#panelBar").kendoPanelBar({
         *     animation: {
         *         open: {
         *             duration: 200,
         *             effects: "expandVertical"
         *         }
         *     }
         * });
         *
         * @option {Number} [animation.open.duration] <200>
         * The number of milliseconds used for the visual animation when an item is opened.
         *
         * _exampleTitle Defining a custom animation for opening items that executes over 1000 milliseconds
         * _example
         * $("#panelBar").kendoPanelBar({
         *  animation: {
         *       open: {
         *           duration: 1000
         *       }
         *    }
         * });
         *
         * @option {String} [animation.open.effects] <"expandVertical">
         * A whitespace-delimited string of animation effects that are used when an item is expanded. Options include
         * <strong>"expandVertical"</strong> and <strong>"fadeIn"</strong>.
         *
         * @option {Boolean} [animation.open.show] <true>
         *
         * @option {Object} [animation.close]
         * The visual animation(s) that will be used when <strong>PanelBar</strong> items are closed.
         *
         * _exampleTitle Defining a custom animation for closing items that
         * executes over 200 milliseconds
         * _example
         * $("#panelBar").kendoPanelBar({
         *     animation: {
         *         close: {
         *             duration: 200,
         *             effects: "fadeOut"
         *         }
         *     }
         * });
         *
         * @option {Number} [animation.close.duration] <200>
         * The number of milliseconds used for the visual animation when a <strong>PanelBar</strong> item is closed.
         *
         * _exampleTitle Animating all closing items for 1000 milliseconds
         * _example
         * $("#panelBar").kendoPanelBar({
         *     animation: {
         *         close: {
                       duration: 1000
                   }
         *   }
         * });
         *
         * @option {String} [animation.close.effects]
         * A whitespace-delimited string of animation effects that are utilized when a <strong>PanelBar</strong> item
         * is closed. Options include <strong>"fadeOut"</strong>.
         *
         * _exampleTitle Fading-out all closing items for 1000 milliseconds
         * _example
         * $("#panelBar").kendoPanelBar({
         *     animation: {
         *         close: {
         *             duration: 1000,
         *             effects: "fadeOut"
         *         }
         *     }
         * });
         *
         * @option {String} [expandMode] <"multiple">
         * Specifies how the <strong>PanelBar</strong> items are displayed when opened and closed. The following values
         * are available:
         * <div class="details-list">
         *  <dl>
         *   <dt>"single"</dt>
         *   <dd>Display one item at a time when an item is opened; opening an item will close the previously opened item.</dd>
         *   <dt>"multiple"</dt>
         *   <dd>Display multiple values at one time; opening an item has no visual impact on any other items in the <strong>PanelBar</strong>.</dd>
         *  </dl>
         * </div>
         *
         * _example
         * $("#panelBar").kendoPanelBar({
         *     expandMode: "single"
         * });
         *
         */
        init: function(element, options) {
            var that = this,
                content;

            Widget.fn.init.call(that, element, options);

            element = that.wrapper = that.element;

            options = that.options;

            if (options.dataSource) {
                element.empty().append($(PanelBar.renderGroup({
                    items: options.dataSource,
                    group: {
                        firstLevel: true,
                        expanded: true
                    },
                    panelBar: {}
                })).children());
            }

            that._updateClasses();

            if (options.animation === false) {
                options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
            }

            element
                .delegate(clickableItems, CLICK, $.proxy(that._click, that))
                .delegate(clickableItems, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover)
                .delegate(disabledItems, CLICK, false);

            if (options.contentUrls) {
                element.find("> .k-item")
                    .each(function(index, item) {
                        $(item).find("." + LINK).data(CONTENTURL, options.contentUrls[index]);
                    });
            }

            content = element.find("li" + ACTIVECLASS + " > ." + CONTENT);

            if (content.length > 0) {
                that.expand(content.parent(), false);
            }

            kendo.notify(that);
        },

        events: [
                /**
                 *
                 * Triggered when an item of a PanelBar is expanded.
                 *
                 * @name kendo.ui.PanelBar#expand
                 * @event
                 *
                 * @param {Event} e
                 *
                 * @param {Element} e.item
                 * The expanding item of the PanelBar.
                 *
                 * @exampleTitle Attach expand event handler during initialization; detach via unbind()
                 * @example
                 * // event handler for expand
                 * var onExpand = function(e) {
                 *     // access the expanded item via e.item (HTMLElement)
                 * };
                 *
                 * // attach expand event handler during initialization
                 * var panelBar = $("#panelBar").kendoPanelBar({
                 *     expand: onExpand
                 * });
                 *
                 * // detach expand event handler via unbind()
                 * panelBar.data("kendoPanelBar").unbind("expand", onExpand);
                 *
                 * @exampleTitle Attach expand event handler via bind(); detach via unbind()
                 * @example
                 * // event handler for expand
                 * var onExpand = function(e) {
                 *     // access the expanded item via e.item (HTMLElement)
                 * };
                 *
                 * // attach expand event handler via bind()
                 * $("#panelBar").data("kendoPanelBar").bind("expand", onExpand);
                 *
                 * // detach expand event handler via unbind()
                 * $("#panelBar").data("kendoPanelBar").unbind("expand", onExpand);
                 *
                 */
                EXPAND,

                /**
                 *
                 * Triggered when an item of a PanelBar is collapsed.
                 *
                 * @name kendo.ui.PanelBar#collapse
                 * @event
                 *
                 * @param {Event} e
                 *
                 * @param {Element} e.item
                 * The collapsing item of the PanelBar.
                 *
                 * @exampleTitle Attach collapse event handler during initialization; detach via unbind()
                 * @example
                 * // event handler for collapse
                 * var onCollapse = function(e) {
                 *     // access the collapsed item via e.item (HTMLElement)
                 * };
                 *
                 * // attach collapse event handler during initialization
                 * var panelBar = $("#panelBar").kendoPanelBar({
                 *     collapse: onCollapse
                 * });
                 *
                 * // detach collapse event handler via unbind()
                 * panelBar.data("kendoPanelBar").unbind("collapse", onCollapse);
                 *
                 * @exampleTitle Attach collapse event handler via bind(); detach via unbind()
                 * @example
                 * // event handler for collapse
                 * var onCollapse = function(e) {
                 *     // access the collapsed item via e.item (HTMLElement)
                 * };
                 *
                 * // attach collapse event handler via bind()
                 * $("#panelBar").data("kendoPanelBar").bind("collapse", onCollapse);
                 *
                 * // detach collapse event handler via unbind()
                 * $("#panelBar").data("kendoPanelBar").unbind("collapse", onCollapse);
                 *
                 */
                COLLAPSE,

                /**
                 *
                 * Triggered when an item of a PanelBar is selected.
                 *
                 * @name kendo.ui.PanelBar#select
                 * @event
                 *
                 * @param {Event} e
                 *
                 * @param {Element} e.item
                 * The selected item of the PanelBar.
                 *
                 * @exampleTitle Attach select event handler during initialization; detach via unbind()
                 * @example
                 * // event handler for select
                 * var onSelect = function(e) {
                 *     // access the selected item via e.item (HTMLElement)
                 * };
                 *
                 * // attach select event handler during initialization
                 * var panelBar = $("#panelBar").kendoPanelBar({
                 *     select: onSelect
                 * });
                 *
                 * // detach select event handler via unbind()
                 * panelBar.data("kendoPanelBar").unbind("select", onSelect);
                 *
                 * @exampleTitle Attach select event handler via bind(); detach via unbind()
                 * @example
                 * // event handler for select
                 * var onSelect = function(e) {
                 *     // access the selected item via e.item (HTMLElement)
                 * };
                 *
                 * // attach select event handler via bind()
                 * $("#panelBar").data("kendoPanelBar").bind("select", onSelect);
                 *
                 * // detach select event handler via unbind()
                 * $("#panelBar").data("kendoPanelBar").unbind("select", onSelect);
                 *
                 */
                SELECT,

                /**
                 * Fires when AJAX request results in an error.
                 * @name kendo.ui.PanelBar#error
                 * @event
                 * @param {Event} e
                 * @param {jqXHR} e.xhr The jqXHR object used to load the content
                 * @param {String} e.status The returned status.
                 * @example
                 * $("#panelBar").kendoPanelBar({
                 *     error: function(e) {
                 *         // handle event
                 *     }
                 * });
                 *
                 * @exampleTitle To set after intialization
                 * @example
                 * // get a reference to the panel bar
                 * var panelBar = $("#panelBar").data("kendoPanelBar");
                 * // bind the error ajax event
                 * panelBar.bind("error", function(e) {
                 *     // handle event
                 * });
                 */
                ERROR,
                /**
                 * Fires when content is fetched from an AJAX request.
                 * @name kendo.ui.PanelBar#contentLoad
                 * @event
                 * @param {Event} e
                 * @param {Element} e.item The selected item
                 * @param {Element} e.contentElement The loaded content element
                 * @example
                 * $("#panelBar").kendoPanelBar({
                 *     contentLoad: function(e) {
                 *         // handle event
                 *     }
                 * });
                 * @exampleTitle To set after intialization
                 * @example
                 * // get a reference to the panel bar
                 * var panelBar = $("#panelBar").data("kendoPanelBar");
                 * // bind the contentLoad event
                 * panelBar.bind("contentLoad", function(e) {
                 *     // handle event
                 * });
                 */
                CONTENTLOAD
            ],
        options: {
            name: "PanelBar",
            animation: {
                expand: {
                    effects: "expand:vertical",
                    duration: 200,
                    show: true
                },
                collapse: { // if collapse animation effects are defined, they will be used instead of expand.reverse
                    duration: 200
                }
            },
            expandMode: "multiple"
        },

        /**
         *
         * Expands the specified item(s) of a <strong>PanelBar</strong>.
         *
         * @example
         * // access an existing PanelBar instance
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // expand the element with ID, "item1"
         * panelBar.expand($("#item1"));
         * // expand the element with ID, "item2" without visual animations
         * panelBar.expand($("#item2"), false);
         * // expand all list items that start with ID, "item"
         * panelBar.expand($('[id^="item"]'));
         *
         * @param {Selector} element
         * The <strong>PanelBar</strong> item(s) to be expanded, expressed as a selector.
         *
         * @param {Boolean} [useAnimation]
         * Temporariliy enables (<b>true</b>) or disables (<b>false</b>) any visual animation(s) when expanding items.
         *
         * @returns {PanelBar}
         * Returns the PanelBar object to support chaining.
         *
         */
        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(HIGHLIGHTEDCLASS).removeClass(HIGHLIGHTEDCLASS.substr(1));
                    item.addClass(HIGHLIGHTEDCLASS.substr(1));

                    if (!useAnimation) {
                        animBackup = that.options.animation;
                        that.options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
                    }

                    if (!that._triggerEvent(EXPAND, item)) {
                        that._toggleItem(item, false, null);
                    }

                    if (!useAnimation) {
                        that.options.animation = animBackup;
                    }
                }
            });

            return that;
        },

        /**
         *
         * Collapses the specified item(s) of a <strong>PanelBar</strong>.
         *
         * @example
         * // access an existing PanelBar instance
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // collapse the element with ID, "item1"
         * panelBar.collapse($("#item1"));
         * // collapse the element with ID, "item2" without visual animations
         * panelBar.collapse($("#item2"), false);
         * // collapse all list items that start with ID, "item"
         * panelBar.collapse($('[id^="item"]'));
         *
         * @param {Selector} element
         * The <strong>PanelBar</strong> item(s) to be collapsed, expressed as a string containing a selector
         * expression or represented by a <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
         *
         * @param {Boolean} [useAnimation]
         * Temporarily enables (<strong>true</strong>) or disables (<strong>false</strong>) any visual animation(s)
         * when collapsing items.
         *
         * @returns {PanelBar}
         * Returns the PanelBar object to support chaining.
         *
         */
        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(HIGHLIGHTEDCLASS.substr(1));

                    if (!useAnimation) {
                        animBackup = that.options.animation;
                        that.options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
                    }

                    if (!that._triggerEvent(COLLAPSE, item)) {
                        that._toggleItem(item, true, null);
                    }

                    if (!useAnimation) {
                        that.options.animation = animBackup;
                    }
                }

            });

            return that;
        },

        _toggleDisabled: function (element, enable) {
            element = this.element.find(element);
            element
                .toggleClass(defaultState, enable)
                .toggleClass(DISABLEDCLASS.substr(1), !enable);
        },

        /**
         *
         * Selects the specified item of the <strong>PanelBar</strong>. If this method is invoked without arguments, it
         * returns the currently selected item.
         *
         * @param {String | Selector} element
         * The <strong>PanelBar</strong> item to be selected, expressed as a string containing a selector expression or
         * represented by a <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
         *
         * @example
         * // access an existing PanelBar instance
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // select the item with ID, "item1"
         * panelBar.select("#item1");
         *
         */
        select: function (element) {
            var that = this;
            element = that.element.find(element);

            if (arguments.length === 0) {
                return that.element.find(".k-item > " + SELECTEDCLASS).parent();
            }

            element.each(function (index, item) {
                item = $(item);
                var link = item.children("." + LINK);

                if (item.is(DISABLEDCLASS)) {
                    return that;
                }

                $(SELECTEDCLASS, that.element).removeClass(SELECTEDCLASS.substr(1));
                $(HIGHLIGHTEDCLASS, that.element).removeClass(HIGHLIGHTEDCLASS.substr(1));

                link.addClass(SELECTEDCLASS.substr(1));
                link.parentsUntil(that.element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTEDCLASS.substr(1));
            });

            return that;
        },

        /**
         *
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the specified item(s) of the
         * <strong>PanelBar</strong>.
         *
         * @example
         * // access an existing PanelBar instance
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // enable the item of the PanelBar with ID, "item1"
         * panelBar.enable($("#item1"), true);
         * // disable the currently selected item of the PanelBar
         * var item = panelBar.select();
         * panelBar.enable(item, false);
         * // disable all list items that start with ID, "item"
         * panelBar.enable($('[id^="item"]'), false);
         *
         * @param {String | Selector} element
         * The <strong>PanelBar</strong> item(s) to be enabled (<b>true</b>) or disabled (<b>false</b>), expressed as a
         * string containing a selector expression or represented by a
         * <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
         *
         * @param {Boolean} enable
         * The desired state - enabled (<strong>true</strong>) or disabled (<strong>false</strong>) - of the target
         * element(s).
         *
         */
        enable: function (element, state) {
            this._toggleDisabled(element, state !== false);

            return this;
        },

        disable: function (element) {
            this._toggleDisabled(element, false);

            return this;
        },

        /**
         *
         * Appends an item to the PanelBar.
         *
         * @param {Selector} item
         * Target item, specified as the JSON representation of an object. You can pass item text, content or
         * contentUrl here. Can handle an HTML string or array of such strings or JSON.
         *
         * @param {Item} referenceItem
         * A reference item to append the new item in
         *
         * @returns {PanelBar}
         * Returns the PanelBar object to support chaining.
         *
         * @example
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * panelBar.append(
         *     [
         *         {
         *             text: "Item 1",
         *             url: "http://www.kendoui.com/"                  // link URL if navigation is needed (optional)
         *         },
         *         {
         *             text: "Item 2",
         *             content: "text"                                 // content within an item
         *         },
         *         {
         *             text: "Item 3",
         *             contentUrl: "partialContent.html"               // content URL to load within an item
         *         },
         *         {
         *             text: "Item 4",
         *             imageUrl: "http://www.kendoui.com/test.jpg",    // item image URL, optional
         *             // sub-item collection
         *             items:
         *                 [
         *                     { text: "Sub Item 1" },
         *                     { text: "Sub Item 2" }
         *                 ]
         *         },
         *         {
         *             text: "Item 5",
         *             // item image sprite CSS class, optional
         *             spriteCssClass: "imageClass3"
         *         }
         *      ],
         *      referenceItem
         * );
         *
         */
        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 (idx) {
                inserted.group.append(this);

                var contents = inserted.contents[idx];
                if (contents)
                    $(this).append(contents);

                updateFirstLast(this);
            });

            updateArrow(referenceItem);
            updateFirstLast(inserted.group.find(".k-first, .k-last"));
            inserted.group.height("auto");

            return this;
        },

        /**
         *
         * Inserts a PanelBar item before the specified referenceItem
         *
         * @param {Selector} item
         * Target item, specified as a JSON object. You can pass item text, content or contentUrl here. Can handle an
         * TML string or array of such strings or JSON.
         *
         * @param {Item} referenceItem
         * A reference item to insert the new item before.
         *
         * @returns {PanelBar}
         * Returns the PanelBar object to support chaining.
         *
         * @example
         * panelBar.insertBefore(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         content: "text"                              // Content for the content element
         *     },
         *     {
         *         text: "Item 3",
         *         contentUrl: "partialContent.html"            // From where to load the item content
         *     },
         *     {
         *         text: "Item 4",
         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
         *         items: [{                                    // Sub item collection.
         *              text: "Sub Item 1"
         *         },
         *         {
         *              text: "Sub Item 2"
         *         }]
         *     },
         *     {
         *         text: "Item 5",
         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         *
         */
        insertBefore: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.parent());

            each(inserted.items, function (idx) {
                referenceItem.before(this);

                var contents = inserted.contents[idx];
                if (contents)
                    $(this).append(contents);

                updateFirstLast(this);
            });

            updateFirstLast(referenceItem);
            inserted.group.height("auto");

            return this;
        },

        /**
         * Inserts a PanelBar item after the specified referenceItem
         * @param {Selector} item Target item, specified as a JSON object. You can pass item text, content or contentUrl here. Can handle an HTML string or array of such strings or JSON.
         * @param {Item} referenceItem A reference item to insert the new item after
         * @example
         * panelBar.insertAfter(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         content: "text"                              // Content for the content element
         *     },
         *     {
         *         text: "Item 3",
         *         contentUrl: "partialContent.html"            // From where to load the item content
         *     },
         *     {
         *         text: "Item 4",
         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
         *         items: [{                                    // Sub item collection.
         *              text: "Sub Item 1"
         *         },
         *         {
         *              text: "Sub Item 2"
         *         }]
         *     },
         *     {
         *         text: "Item 5",
         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         */
        insertAfter: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.parent());

            each(inserted.items, function (idx) {
                referenceItem.after(this);

                var contents = inserted.contents[idx];
                if (contents)
                    $(this).append(contents);

                updateFirstLast(this);
            });

            updateFirstLast(referenceItem);
            inserted.group.height("auto");

            return this;
        },

        /**
         *
         * Removes the specified PanelBar item(s).
         *
         * @param {Selector} element Target item selector.
         *
         * @example
         * // get a reference to the panel bar
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // remove Item 1
         * panelBar.remove("#Item1");
         *
         */
        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;
        },

        /**
         * Reloads the content of a <strong>PanelBar</strong> from an AJAX request.
         * @param {Selector} element Target element
         * @example
         * // get a reference to the panel bar
         * var panelBar = $("#panelBar").data("kendoPanelBar");
         * // reload the panel basr
         * panelBar.reload();
         */
        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));
            });
        },

        _insert: function (item, referenceItem, parent) {
            var that = this, contents = [];

            if (!referenceItem || !referenceItem.length) {
                parent = that.element;
            }

            var plain = $.isPlainObject(item),
                items,
                groupData = {
                    firstLevel: parent.hasClass("k-panelbar"),
                    expanded: parent.parent().hasClass("k-state-active"),
                    length: parent.children().length
                };

            if (referenceItem && !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 })
                                }));
                            }
                        });
                contents = $.map(plain ? [ item ] : item, function (value, idx) {
                            if (value.content || value.contentUrl) {
                                return $(PanelBar.renderContent({
                                    item: extend(value, { index: idx })
                                }));
                            } else {
                                return false;
                            }
                        });
            } else {
                items = $(item);

                updateItemClasses(items, that.element);
            }

            return { items: items, group: parent, contents: contents };
        },

        _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;

            that.element.addClass("k-widget k-reset k-header k-panelbar");

            var panels = that.element
                                .find("li > ul")
                                .not(function () {
                                        return $(this).parentsUntil(".k-panelbar", "div").length;
                                    })
                                .addClass("k-group k-panel")
                                .add(that.element);

            var items = panels
                            .find("> li:not(" + ACTIVECLASS + ") > ul")
                            .css({ display: "none" })
                            .end()
                            .find("> li");

            items.each(function () {
                updateItemClasses(this, that.element);
            });

            updateArrow(items);
            updateFirstLast(items);
        },

        _click: function (e) {
            var that = this,
                target = $(e.currentTarget),
                element = that.element;

            if (target.parents("li" + DISABLEDCLASS).length) {
                return;
            }

            if (target.closest(".k-widget")[0] != element[0]) {
                return;
            }

            var link = target.closest("." + LINK),
                item = link.closest(ITEM);

            $(SELECTEDCLASS, element).removeClass(SELECTEDCLASS.substr(1));
            $(HIGHLIGHTEDCLASS, element).removeClass(HIGHLIGHTEDCLASS.substr(1));

            link.addClass(SELECTEDCLASS.substr(1));
            link.parentsUntil(that.element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTEDCLASS.substr(1));

            var contents = item.find(GROUPS).add(item.find(CONTENTS)),
                href = link.attr(HREF),
                isAnchor = link.data(CONTENTURL) || (href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1));

            if (contents.data("animating")) {
                return;
            }

            if (that._triggerEvent(SELECT, item)) {
                e.preventDefault();
            }

            if (isAnchor || contents.length) {
                e.preventDefault();
            } else {
                return;
            }

            if (that.options.expandMode == SINGLE) {
                if (that._collapseAllExpanded(item)) {
                    return;
                }
            }

            if (contents.length) {
                var visibility = contents.is(VISIBLE);

                if (!that._triggerEvent(!visibility ? EXPAND : COLLAPSE, item)) {
                    that._toggleItem(item, visibility, e);
                }
            }
        },

        _toggleItem: function (element, isVisible, e) {
            var that = this,
                childGroup = element.find(GROUPS);

            if (childGroup.length) {

                this._toggleGroup(childGroup, isVisible);

                if (e) {
                    e.preventDefault();
                }
            } else {

                var content = element.find("> ."  + CONTENT);

                if (content.length) {
                    if (e) {
                        e.preventDefault();
                    }

                    if (!content.is(EMPTY)) {
                        that._toggleGroup(content, isVisible);
                    } else {
                        that._ajaxRequest(element, content, isVisible);
                    }
                }
            }
        },

        _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;
            }

            visibility && element.css("height", element.height()); // Set initial height on visible items (due to a Chrome bug/feature).
            element.css("height");

            element
                .parent()
                .toggleClass(defaultState, visibility)
                .toggleClass(ACTIVECLASS.substr(1), !visibility)
                .find("> .k-link > .k-icon")
                    .toggleClass("k-arrow-up", !visibility)
                    .toggleClass("k-panelbar-collapse", !visibility)
                    .toggleClass("k-arrow-down", visibility)
                    .toggleClass("k-panelbar-expand", visibility);

            if (visibility) {
                animation = extend( hasCollapseAnimation ? collapse
                                    : extend({ reverse: true }, animation), { show: false, hide: true });
            }

            element
                .kendoStop(true, true)
                .kendoAnimate( animation );
        },

        _collapseAllExpanded: function (item) {
            var that = this;

            if (item.find("> ." + LINK).hasClass("k-header")) {
                var groups = item.find(GROUPS).add(item.find(CONTENTS));
                if (groups.is(VISIBLE) || groups.length == 0) {
                    return true;
                } else {
                    var children = $(that.element).children();
                    children.find(GROUPS).add(children.find(CONTENTS))
                            .filter(function () { return $(this).is(VISIBLE) })
                            .each(function (index, content) {
                                that._toggleGroup($(content), true);
                            });
                }
            }
        },

        _ajaxRequest: function (element, contentElement, isVisible) {

            var that = this,
                statusIcon = element.find(".k-panelbar-collapse, .k-panelbar-expand"),
                link = element.find("." + LINK),
                loadingIconTimeout = setTimeout(function () {
                    statusIcon.addClass("k-loading");
                }, 100),
                data = {};

            $.ajax({
                type: "GET",
                cache: false,
                url: link.data(CONTENTURL) || link.attr(HREF),
                dataType: "html",
                data: data,

                error: function (xhr, status) {
                    if (that.trigger(ERROR, { xhr: xhr, status: status })) {
                        this.complete();
                    }
                },

                complete: function () {
                    clearTimeout(loadingIconTimeout);
                    statusIcon.removeClass("k-loading");
                },

                success: function (data, textStatus) {
                    contentElement.html(data);
                    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] });
        }
    });

    // client-side rendering
    extend(PanelBar, {
        renderItem: function (options) {
            options = extend({ panelBar: {}, group: {} }, options);

            var empty = templates.empty,
                item = options.item,
                panelBar = options.panelBar;

            return templates.item(extend(options, {
                image: item.imageUrl ? templates.image : empty,
                sprite: item.spriteCssClass ? templates.sprite : empty,
                itemWrapper: templates.itemWrapper,
                arrow: item.items ? 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);

})(jQuery);
/**
 * @fileOverview Provides a TabStrip implementation which can be used to display a collection of tabs with associated
 * content
 */

(function ($, undefined) {
    /**
     *
     * @name kendo.ui.TabStrip.Description
     *
     * @section
     * <p>
     *  A <strong>TabStrip</strong> displays a collection of tabs with associated content. It is composed of an
     *  unordered list of items - representing tabs - and a collection of div elements, which contain the content for
     *  each tab.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Create an unordered list for tabs with associated div elements for content
     * @example
     * <div id="tabStrip">
     *     <ul>
     *         <li>First tab</li>
     *         <li>Second tab</li>
     *     </ul>
     *     <div>First tab content</div>
     *     <div>Second tab content</div>
     * </div>
     *
     * @section
     * <p>
     *  Initialization of a <strong>TabStrip</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>TabStrip</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a TabStrip using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#tabStrip").kendoTabStrip();
     * });
     *
     * @exampleTitle Initialize the TabStrip using JSON data object
     * @example
     * $(document).ready(function() {
     *     $("#tabstrip").kendoTabStrip({
     *         dataTextField: "text",
     *         dataContentField: "content",
     *         dataSource:
     *         [
     *             { text: "Tab 1", content: "Tab 1 content" },
     *             { text: "Tab 2", content: "Tab 2 content" }
     *         ]
     *     });
     * });
     *
     * @section
     * <p>
     *  The tabs of a <strong>TabStrip</strong> are not required to have content. Should a tab have no content, it is
     *  safe to omit its associated div.
     * </p>
     * <h3>Loading TabStrip content with AJAX</h3>
     * <p>
     *  While any valid technique for loading AJAX content can be used, a <strong>TabStrip</strong> supports loading
     *  content from URLs in an asynchronous manner. These URLs should return HTML fragments that can be loaded in a
     *  TabStrip content area.
     * </p>
     *
     * @exampleTitle Loading Tab content asynchronously
     * @example
     * <div id="tabstrip">
     *     <ul>
     *         <li>First Tab</li>
     *         <li>Second Tab</li>
     *     </ul>
     *     <div></div>
     *     <div></div>
     *  </div>
     *
     * @exampleTitle Initialize TabStrip and configure one tab with async content loading
     * @example
     * $(document).ready(function(){
     *     $("#tabstrip").kendoTabStrip({
     *         contentUrls: [null, "html-content-snippet.html"]
     *     });
     *  });
     *
     * @section
     * <h3>Dynamically Configure TabStrip Tabs</h3>
     * <p>
     *  The <strong>TabStrip</strong> API provides several methods for dynamically adding or removing tabs. To add
     *  tabs, provide the new item as a JSON object along with a reference item that will be used to determine the
     *  placement in the <strong>TabStrip</strong>. Note: append() does not require a reference item.
     * <p>
     * <p>
     *  A reference item is simply a target DOM element of a tab that already exists in the TabStrip. Any valid
     *  selector may be used to obtain a reference to the target item.
     * </p>
     * <p>Removing an item requires a reference to the target element.</p>
     *
     * @exampleTitle Dynamically add a new tab
     * @example
     * var tabStrip = $("#tabStrip").data("kendoTabStrip");
     * tabStrip.insertAfter(
     *     { text: "New Tab" },
     *     tabstrip.tabGroup.children("li:last")
     * );
     *
     * @section
     * <h3>Selecting a Tab on Initial Load</h3>
     * <p>
     *  It is possible to select a tab and display its associated content upon its initial load. There are two (2) ways
     *  to accomplish this task:
     * </p>
     * <ol>
     *  <li>Add a "k-state-active" class to the DOM element of the tab</li>
     *  <li>Use select() to target and select a tab either by selector or index</li>
     * </ol>
     * <p>Both approaches produce the same result.</p>
     *
     * @exampleTitle Selecting a default tab manually using HTML
     * @example
     * <div id="tabstrip">
     *     <ul>
     *         <li class="k-state-active">First Tab</li>
     *         <li>Second Tab</li>
     *     </ul>
     *     <div></div>
     *     <div></div>
     * </div>
     *
     * @exampleTitle Initialize a TabStrip and select first tab via select(element)
     * @example
     * $(document).ready(function(){
     *     var tabstrip = $("#tabstrip").kendoTabStrip().data("kendoTabStrip");
     *     tabstrip.select(tabstrip.tabGroup.children("li:first"));
     * });
     *
     * @exampleTitle Initialize a TabStrip and select first tab via select(index)
     * @example
     * $(document).ready(function(){
     *     var tabstrip = $("#tabstrip").kendoTabStrip().data("kendoTabStrip");
     *     tabstrip.select(1);
     * });
     *
     * @section
     * <h3>Accessing an Existing TabStrip</h3>
     * <p>
     *  You can reference an existing <b>TabStrip</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing TabStrip instance
     * @example
     * var tabStrip = $("#tabStrip").data("kendoTabStrip");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        map = $.map,
        each = $.each,
        trim = $.trim,
        extend = $.extend,
        template = kendo.template,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(a|div)$/i,
        IMG = "img",
        HREF = "href",
        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",
        CLICKABLEITEMS = ".k-tabstrip-items > .k-item:not(.k-state-disabled)",
        HOVERABLEITEMS = ".k-tabstrip-items > .k-item:not(.k-state-disabled):not(.k-state-active)",
        DISABLEDLINKS = ".k-tabstrip-items > .k-state-disabled .k-link",
        DISABLEDSTATE = "k-state-disabled",
        DEFAULTSTATE = "k-state-default",
        ACTIVESTATE = "k-state-active",
        HOVERSTATE = "k-state-hover",
        TABONTOP = "k-tab-on-top",

        templates = {
            content: template(
                "<div class='k-content'#= contentAttributes(data) #>#= 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) #'>" +
                    "#= 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'" : "";
            },
            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:focus")
            .parent()
            .addClass(ACTIVESTATE + " " + TABONTOP);

        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({/** @lends kendo.ui.TabStrip.prototype */
        /**
         *
         * Creates a TabStrip instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         * @class TabStrip UI widget
         *
         * @param {Selector} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Object} [animation]
         * A collection of visual animations used when <strong>TabStrip</strong> tab are selected through
         * user interactions. Setting this option to <strong>false</strong> will disable all animations.
         *
         * _exampleTitle Defining custom animations when selecting tabs
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     animation: {
         *         // fade-out current tab over 1000 milliseconds
         *         close: {
         *             duration: 1000,
         *             effects: "fadeOut"
         *         },
         *        // fade-in new tab over 500 milliseconds
         *        open: {
         *            duration: 500,
         *            effects: "fadeIn"
         *        }
         *    }
         * });
         *
         * @option {Object} [animation.open]
         * The visual animation(s) that will be used when the new tab is shown.
         *
         * _exampleTitle Defining a custom animation when new tab is shown that executes over 200 milliseconds
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     animation: {
         *         open: {
         *             duration: 200,
         *             effects: "expand:vertical"
         *         }
         *     }
         * });
         *
         * @option {Number} [animation.open.duration] <200>
         * The number of milliseconds used for the visual animation when a new tab is shown.
         *
         * _exampleTitle Defining animation when a new tab is shown that executes over 1000 milliseconds
         * _example
         * $("#tabstrip").kendoTabStrip({
         *  animation: {
         *       open: {
         *           duration: 1000
         *       }
         *    }
         * });
         *
         * @option {String} [animation.open.effects] <"expand:vertical fadeIn">
         * A whitespace-separated string of animation effects that are used when a new tab is shown. Options include
         * <strong>"expand:vertical"</strong> and <strong>"fadeIn"</strong>.
         *
         * @option {Boolean} [animation.open.show] <true>
         *
         * @option {Object} [animation.close]
         * The visual animation(s) that will be used when the current tab is closed.
         *
         * _exampleTitle Defining a custom animation for the current tab that
         * executes over 200 milliseconds
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     animation: {
         *         close: {
         *             duration: 200,
         *             effects: "fadeOut"
         *         }
         *     }
         * });
         *
         * @option {Number} [animation.close.duration] <200>
         * The number of milliseconds used for the visual animation when the current tab is closed.
         *
         * _exampleTitle Animating the current tab for 1000 milliseconds
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     animation: {
         *         close: {
                       duration: 1000
                   }
         *   }
         * });
         *
         * @option {String} [animation.close.effects]
         * A whitespace-delimited string of animation effects that are utilized when the current tab
         * is closed. By default not specified - uses the opening animation with reverse.
         *
         * _exampleTitle Fading-out the current tab for 1000 milliseconds
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     animation: {
         *         close: {
         *             duration: 1000,
         *             effects: "fadeOut"
         *         }
         *     }
         * });
         *
         * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text name of the tab.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataSource: data
         * });
         *
         * @option {String} [dataContentField] <""> Sets the field of the data item that provides the text content of
         * the tab content element.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataContentField: "Content",
         *     dataSource: data
         * });
         *
         * @option {String} [dataImageUrlField] <""> Sets the field of the data item that provides the image URL of
         * the tab.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataImageUrlField: "ImageUrl",
         *     dataSource: data
         * });
         *
         * @option {String} [dataUrlField] <""> Sets the field of the data item that provides the link URL for the
         * tab.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataUrlField: "Url",
         *     dataSource: data
         * });
         *
         * @option {String} [dataSpriteCssClass] <""> Sets the field of the data item that provides the CSS class of
         * the tab.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataSpriteCssClass: "CssClass",
         *     dataSource: data
         * });
         *
         * @option {String} [dataContentUrlField] <""> Sets the field of the data item that provides the URL for
         * the ajax loaded tab content.
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     dataTextField: "Name",
         *     dataContentUrlField: "ContentUrl",
         *     dataSource: data
         * });
         *
         * @option {Boolean} [collapsible] <false>
         * Specifies whether the TabStrip should be able to collapse completely when clicking an expanded tab.
         *
         * _exampleTitle Turning on tab collapsing
         * _example
         * $("#tabstrip").kendoTabStrip({
         *     collapsible: true
         * });
         *
         */
        init: function(element, options) {
            var that = this;

            that._animations(options);

            Widget.fn.init.call(that, element, options);

            if (that.element.is("ul")) {
                that.wrapper = that.element.wrapAll("<div />").parent();
            } else {
                that.wrapper = that.element;
            }

            options = that.options;

            that.wrapper
                .delegate(CLICKABLEITEMS, CLICK, $.proxy(that._click, that))
                .delegate(HOVERABLEITEMS, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover)
                .delegate(DISABLEDLINKS, CLICK, false);

            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]);
                    });
            }

            var selectedItems = that.wrapper.find("li." + ACTIVESTATE),
                content = $(that.contentElement(selectedItems.parent().children().index(selectedItems)));

            if (content.length > 0 && content[0].childNodes.length == 0) {
                that.activateTab(selectedItems.eq(0));
            }

            kendo.notify(that);
        },

        _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: {}, show: true }, close: { effects: {} } }; // No animation
            }
        },

        refresh: function(e) {
            var that = this,
                html = "",
                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.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 animation = this.options.animation;

            this._animations(options);

            options.animation = extend(true, animation, options.animation);

            Widget.fn.setOptions.call(this, options);
        },

        events: [
            /**
             *
             * Triggered before a tab is selected.
             *
             * @name kendo.ui.TabStrip#select
             * @event
             *
             * @param {Event} e
             *
             * @param {HTMLElement} e.item
             * The selected item chosen by a user.
             *
             * @param {Element} e.contentElement
             * The content element of the tab going to be selected.
             *
             * @exampleTitle Attach select event handler during initialization; detach via unbind()
             * @example
             * // event handler for select
             * var onSelect = function(e) {
             *     // access the selected item via e.item (HTMLElement)
             * };
             *
             * // attach select event handler during initialization
             * var tabStrip = $("#tabStrip").kendoTabStrip({
             *     select: onSelect
             * });
             *
             * // detach select event handler via unbind()
             * tabStrip.data("kendoTabStrip").unbind("select", onSelect);
             *
             * @exampleTitle Attach select event handler via bind(); detach via unbind()
             * @example
             * // event handler for select
             * var onSelect = function(e) {
             *     // access the selected item via e.item (HTMLElement)
             * };
             *
             * // attach select event handler via bind()
             * $("#tabStrip").data("kendoTabStrip").bind("select", onSelect);
             *
             * // detach select event handler via unbind()
             * $("#tabStrip").data("kendoTabStrip").unbind("select", onSelect);
             *
             */
            SELECT,
            /**
             * Triggered just after a tab is being made visible, but before the end of the animation
             *
             * @name kendo.ui.TabStrip#activate
             * @event
             *
             * @param {Event} e
             *
             * @param {HTMLElement} e.item
             * The activated tab.
             *
             * @param {Element} e.contentElement
             * The content element of the activated tab.
             *
             * @exampleTitle Attach activate event handler during initialization; detach via unbind()
             * @example
             * // event handler for activate
             * var onActivate = function(e) {
             *     // access the activated item via e.item (HTMLElement)
             * };
             *
             * // attach activate event handler during initialization
             * var tabStrip = $("#tabStrip").kendoTabStrip({
             *     activate: onActivate
             * });
             *
             * // detach activate event handler via unbind()
             * tabStrip.data("kendoTabStrip").unbind("activate", onActivate);
             *
             * @exampleTitle Attach activate event handler via bind(); detach via unbind()
             * @example
             * // event handler for activate
             * var onActivate = function(e) {
             *     // access the activated item via e.item (HTMLElement)
             * };
             *
             * // attach activate event handler via bind()
             * $("#tabStrip").data("kendoTabStrip").bind("activate", onActivate);
             *
             * // detach activate event handler via unbind()
             * $("#tabStrip").data("kendoTabStrip").unbind("activate", onActivate);
             *
             */
            ACTIVATE,
            /**
             *
             * Triggered when an AJAX request results in an error.
             *
             * @name kendo.ui.TabStrip#error
             * @event
             *
             * @param {Event} e
             *
             * @param {jqXHR} e.xhr
             * The jqXHR object used to load the content
             *
             * @param {String} e.status
             * The returned status.
             *
             */
            ERROR,
            /**
             *
             * Triggered when content is fetched from an AJAX request.
             *
             * @name kendo.ui.TabStrip#contentLoad
             * @event
             *
             * @param {Event} e
             *
             * @param {Element} e.item
             * The selected item
             *
             * @param {Element} e.contentElement
             * The loaded content element that is retrieved via AJAX.
             *
             */
            CONTENTLOAD,
            "change",
            "dataBinding",
            "dataBound"
        ],

        options: {
            name: "TabStrip",
            dataTextField: "",
            dataContentField: "",
            dataImageUrlField: "",
            dataUrlField: "",
            dataSpriteCssClass: "",
            dataContentUrlField: "",
            animation: {
                open: {
                    effects: "expand:vertical fadeIn",
                    duration: 200,
                    show: true
                },
                close: { // if close animation effects are defined, they will be used instead of open.reverse
                    duration: 200
                }
            },
            collapsible: false
        },

        /**
         *
         * Selects the specified tab(s) within a <strong>TabStrip</strong>. If called without arguments, it returns the
         * currently selected tab.
         *
         * @param {Selector/Index} element or index
         * The target tab(s), specified as a selector or index in the tab group.
         *
         * @example
         * tabStrip.select("#tab1");
         *
         * @example
         * tabStrip.select(1);
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         */
        select: function (element) {
            var that = this;

            if (arguments.length == 0) {
                return that.wrapper.find("li." + ACTIVESTATE);
            }

            if (!isNaN(element)) {
                element = that.tabGroup.children().get(element);
            }

            element = that.element.find(element);
            $(element).each(function (index, item) {
                item = $(item);
                if (!item.hasClass(ACTIVESTATE) && !that.trigger(SELECT, { item: item[0], contentElement: that.contentElement(item.index()) })) {
                    that.activateTab(item);
                }
            });

            return that;
        },

        /**
         *
         * Disables (<strong>false</strong>) or enables (<strong>true</strong>) a tab(s) of a <strong>TabStrip</strong>.
         *
         * @param {Selector} element
         * The target tab(s), specified as a selector, to be enabled (<strong>true</strong>) or disabled
         * (<strong>false</strong>).
         *
         * @param {Boolean} enable
         * Desired state of the tab(s) specified by the selector; enabled (<strong>true</strong>) or disabled
         * (<strong>false</strong>).
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         */
        enable: function (element, state) {
            this._toggleDisabled(element, state !== false);

            return this;
        },

        /**
         *
         * Disables a tab(s) of a <strong>TabStrip</strong>.
         *
         * @param {Selector} element
         * The target tab(s), specified as a selector, to be disabled.
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         */
        disable: function (element) {
            this._toggleDisabled(element, false);

            return this;
        },

        /**
         *
         * Reloads TabStrip tab(s) via AJAX.
         *
         * @param {Selector} element
         * The target tab(s), specified as a selector, to be reloaded via AJAX.
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         */
        reload: function (element) {
            element = this.tabGroup.find(element);
            var that = this;

            element.each(function () {
                var item = $(this),
                    contentUrl = item.find("." + LINK).data(CONTENTURL);

                if (contentUrl) {
                    that.ajaxRequest(item, $(that.contentElement(item.index())), null, contentUrl);
                }
            });

            return that;
        },

        /**
         *
         * Appends a tab to the collection of tabs in a <strong>TabStrip</strong>.
         *
         * @param {Selector} tab
         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
         * HTML string or array of such strings or JSON.
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         * @example
         * tabStrip.append(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         content: "text"                             // Content for the content element
         *     },
         *     {
         *         text: "Item 3",
         *         contentUrl: "partialContent.html"           // From where to load the item content
         *     },
         *     {
         *         text: "Item 4",
         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
         *     },
         *     {
         *         text: "Item 5",
         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
         *     }]
         * );
         *
         */
        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;
        },

        /**
         *
         * Inserts a newly-created tab before a specified tab.
         *
         * @param {Selector} item
         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
         * HTML string or array of such strings or JSON.
         *
         * @param {Item} referenceTab
         * A reference tab to insert the new item before
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         * @example
         * tabStrip.insertBefore(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         content: "text"                             // Content for the content element
         *     },
         *     {
         *         text: "Item 3",
         *         contentUrl: "partialContent.html"           // From where to load the item content
         *     },
         *     {
         *         text: "Item 4",
         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
         *     },
         *     {
         *         text: "Item 5",
         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         */
        insertBefore: function (tab, 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;
        },

        /**
         *
         * Inserts a newly-created tab after a specified tab.
         *
         * @param {Selector} item
         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
         * HTML string or array of such strings or JSON.
         *
         * @param {Item} referenceTab
         * A reference tab to insert the new item after.
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         * @example
         * tabStrip.insertAfter(
         *     [{
         *         text: "Item 1",
         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
         *     },
         *     {
         *         text: "Item 2",
         *         content: "text"                             // Content for the content element
         *     },
         *     {
         *         text: "Item 3",
         *         contentUrl: "partialContent.html"           // From where to load the item content
         *     },
         *     {
         *         text: "Item 4",
         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
         *     },
         *     {
         *         text: "Item 5",
         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
         *     }],
         *     referenceItem
         * );
         *
         */
        insertAfter: function (tab, 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;
        },

        /**
         *
         * Removes a specified tab from a TabStrip.
         *
         * @param {Selector} element
         * The target tab(s), specified as a selector, to be removed.
         *
         * @returns {TabStrip}
         * Returns the TabStrip object to support chaining.
         *
         * @exampleTitle Remove a tab with ID, tab1 from a TabStrip
         * @example
         * tabStrip.remove("#tab1");
         *
         */
        remove: function (element) {
            var that = this,
                type = typeof element,
                content;

            if (type === "string") {
                element = that.tabGroup.find(element);
            } else if (type === "number") {
                element = that.tabGroup.children().eq(element);
            }

            content = $(that.contentElement(element.index()));

            content.remove();
            element.remove();

            that._updateContentElements();

            return that;
        },

        _create: function (tab) {
            var plain = $.isPlainObject(tab),
                that = this, tabs, contents;

            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 (value.content || value.contentUrl) {
                                return $(TabStrip.renderContent({
                                    item: extend(value, { index: idx })
                                }));
                            }
                        });
            } else {
                tabs = $(tab);
                contents = $("<div class='" + 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();
            }

            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 || [],
                tabStripID = that.element.attr("id"),
                contentElements = that.wrapper.children("div");

            that.tabGroup.find(".k-item").each(function(idx) {
                var currentContent = contentElements.eq(idx),
                    id = tabStripID + "-" + (idx+1),
                    href = $(this).children("." + LINK).attr(HREF);

                if (!currentContent.length && contentUrls[idx]) {
                    $("<div id='"+ id +"' class='" + CONTENT + "'/>").appendTo(that.wrapper);
                } else {
                    currentContent.attr("id", id);
                }
            });

            that.contentElements = that.contentAnimators = that.wrapper.children("div"); // refresh the contents

            if (kendo.support.touch) {
                kendo.touchScroller(that.contentElements);
                that.contentElements = that.contentElements.children(".km-scroll-container");
            }
        },

        _toggleHover: function(e) {
            $(e.currentTarget).toggleClass(HOVERSTATE, e.type == MOUSEENTER);
        },

        _click: function (e) {
            var that = this,
                item = $(e.currentTarget),
                link = item.find("." + LINK),
                href = link.attr(HREF),
                collapse = that.options.collapsible,
                content = $(that.contentElement(item.index()));

            if (item.closest(".k-widget")[0] != that.wrapper[0]) {
                return;
            }

            if (item.is("." + DISABLEDSTATE + (!collapse ? ",." + ACTIVESTATE : ""))) {
                e.preventDefault();
                return;
            }

            if (that.tabGroup.children("[data-animating], [data-in-request]").length) {
                return;
            }

            if (that.trigger(SELECT, { item: item[0], contentElement: content[0] })) {
                e.preventDefault();
            } else {
                var isAnchor = link.data(CONTENTURL) || (href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1));

                if (!href || isAnchor) {
                    e.preventDefault();
                } else {
                    return;
                }

                if (collapse && item.is("." + ACTIVESTATE)) {
                    that.deactivateTab(item);
                    e.preventDefault();

                    return;
                }

                if (that.activateTab(item)) {
                    e.preventDefault();
                }
            }
        },

        /**
         *
         * Deactivates a tab specified as a selector. Note: Invoking this method will not trigger any events.
         *
         * @param {Selector} item
         * The target tab, specified as a selector, to be deactivated.
         *
         * @example
         * var tabToDeactivate = $("#tab1");
         * $("#tabStrip").data("kendoTabStrip").deactivateTab(tabToActivate);
         *
         */
        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), { show: false, 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);
            }

            that.contentAnimators
                    .filter("." + ACTIVESTATE)
                    .kendoStop(true, true)
                    .kendoAnimate( close )
                    .removeClass(ACTIVESTATE);
        },

        /**
         *
         * Activates a tab specified as a selector. Note: Invoking this method will not trigger any events.
         *
         * @param {Selector} item
         * The target tab, specified as a selector, to be activated.
         *
         * @returns {Boolean}
         * Returns <strong>true</strong> if successful; otherwise, <strong>false</strong>.
         *
         * @exampleTitle Activate a tab with ID, tab1 in a TabStrip
         * @example
         * var tabToActivate = $("#tab1");
         * $("#tabStrip").data("kendoTabStrip").activateTab(tabToActivate);
         *
         */
        activateTab: function (item) {
            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), { show: false, 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 (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.trigger("change");

                return false;
            }

            var visibleContents = contentAnimators.filter("." + ACTIVESTATE);

            // find associated content element
            var content = $(that.contentElement(itemIndex));

            if (content.length == 0) {
                visibleContents
                    .removeClass( ACTIVESTATE )
                    .kendoStop(true, true)
                    .kendoAnimate( close );
                return false;
            }

            item.attr("data-animating", true);

            var isAjaxContent = (item.children("." + LINK).data(CONTENTURL) || false) && content.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);
                    }

                    content
                        .closest(".k-content")
                        .addClass(ACTIVESTATE)
                        .kendoStop(true, true)
                        .kendoAnimate( extend({ init: function () {
                            that.trigger(ACTIVATE, { item: item[0], contentElement: content[0] });
                        } }, animation, { complete: function () {
                                                        item.removeAttr("data-animating");
                                                    } } ) );
                },
                showContent = function() {
                    if (!isAjaxContent) {
                        showContentElement();
                        that.trigger("change");
                    } else
                        that.ajaxRequest(item, content, function () {
                            showContentElement();
                            that.trigger("change");
                        });
                };

            visibleContents
                    .removeClass(ACTIVESTATE);

            if (visibleContents.length) {
                visibleContents
                    .kendoStop(true, true)
                    .kendoAnimate(extend( {
                        complete: showContent
                   }, close ));
            } else {
                showContent();
            }

            return true;
        },

        /**
         *
         * Obtains the DOM element representing a tab by its index in the <strong>TabStrip</strong>.
         *
         * @param {int} itemIndex
         * The index of the tab in the TabStrip.
         *
         * @returns {HTMLElement}
         * The DOM element representing a tab by its index in the <strong>TabStrip</strong>.
         *
         * @exampleTitle Obtain the DOM element representing the first tab in a TabStrip
         * @example
         * var tabContent = $("#tabStrip").data("kendoTabStrip").contentElement(0);
         *
         */
        contentElement: function (itemIndex) {
            if (isNaN(itemIndex - 0)) return;

            var contentElements = this.contentElements,
                idTest = new RegExp("-" + (itemIndex + 1) + "$");

            for (var i = 0, len = contentElements.length; i < len; i++) {
                if (idTest.test(contentElements.closest(".k-content")[i].id)) {
                    return contentElements[i];
                }
            }
        },

        ajaxRequest: function (element, content, complete, url) {
            element = this.tabGroup.find(element);
            if (element.find(".k-loading").length)
                return;

            var that = this,
                link = element.find("." + LINK),
                data = {},
                statusIcon = null,
                loadingIconTimeout = setTimeout(function () {
                    statusIcon = $("<span class='k-icon k-loading'/>").prependTo(link)
                }, 100);

            element.attr("data-in-request", true);

            $.ajax({
                type: "GET",
                cache: false,
                url: url || link.data(CONTENTURL) || link.attr(HREF),
                dataType: "html",
                data: data,

                error: function (xhr, status) {
                    element.removeAttr("data-animating");
                    if (that.trigger("error", { xhr: xhr, status: status })) {
                        this.complete();
                    }
                },

                complete: function () {
                    element.removeAttr("data-in-request");

                    clearTimeout(loadingIconTimeout);
                    if (statusIcon !== null) {
                        statusIcon.remove();
                    }
                },

                success: function (data, textStatus) {
                    content.html(data);

                    if (complete) {
                        complete.call(that, content);
                    }

                    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,
                tabStrip = options.tabStrip;

            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);

})(jQuery);
/**
 * @fileOverview Provides a TimePicker implementation which allows the end user to select a time value from a list of
 * predefined values or to type a new value.
 */

(function($, undefined) {
    /**
     * @name kendo.ui.TimePicker.Description
     *
     * @section
     * <p>
     *  The <strong>TimePicker</strong> allows the end user to select a time value from a list of predefined values or
     *  to type a new value. It supports configurable options for the format, minimum and maximum time, and the
     *  interval between predefined values in the list.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Creating a TimePicker from existing input element
     * @example
     * <input id="timePicker" />
     *
     * @exampleTitle Initialize the PanelBar via an ID selector
     * @example
     * $(document).ready(function(){
     *     $("#timePicker").kendoTimePicker();
     * });
     *
     * @section
     * <p>
     *  When a <strong>TimePicker</strong> is initialized, it will automatically be displayed near the location of the
     *  used HTML element.
     * </p>
     * <h3>Configuring TimePicker Behaviors</h3>
     * <p>
     *  A <strong>TimePicker</strong> provides configuration options that can be easily set during initialization.
     *  Among the properties that can be controlled:
     * </p>
     * <ul>
     *  <li>Selected time</li>
     *  <li>Minimum/Maximum time</li>
     *  <li>Define format</li>
     *  <li>Define interval between predefined values in the list</li>
     * </ul>
     *
     * @exampleTitle Create TimePicker with selected time and defined min and max time
     * @example
     * $("#timePicker").kendoTimePicker({
     *     value: new Date(2000, 10, 10, 10, 0, 0),
     *     min: new Date(1950, 0, 1, 8, 0, 0),
     *     max: new Date(2049, 11, 31, 18, 0, 0)
     * });
     *
     * @section
     * <p>
     *  A <strong>TimePicker</strong> will set the value only if the entered time is valid and if it is in the defined
     *  range.
     * </p>
     *
     * @exampleTitle Define time format
     * @example
     * $("#timePicker").kendoTimePicker({
     *     format: "hh:mm:ss tt"
     * });
     *
     * @exampleTitle Define the interval (in minutes) between values in the list
     * @example
     * $("#timePicker").kendoTimePicker({
     *     interval: 15
     * });
     *
     * @section
     * <h3>Accessing an Existing TimePicker</h3>
     * <p>
     *  You can reference an existing <strong>TimePicker</strong> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing TimePicker instance
     * @example
     * var timePicker = $("#timePicker").data("kendoTimePicker");
     *
     */
    var kendo = window.kendo,
        touch = kendo.support.touch,
        keys = kendo.keys,
        ui = kendo.ui,
        Widget = ui.Widget,
        keys = kendo.keys,
        OPEN = "open",
        CLOSE = "close",
        CHANGE = "change",
        CLICK = (touch ? "touchend" : "click"),
        DEFAULT = "k-state-default",
        DISABLED = "disabled",
        LI = "li",
        DIV = "<div/>",
        SPAN = "<span/>",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter mouseleave",
        MOUSEDOWN = "mousedown",
        MS_PER_MINUTE = 60000,
        MS_PER_DAY = 86400000,
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        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, list;

        that.options = options;

        that.ul = $('<ul unselectable="on" class="k-list k-reset"/>')
                    .css({ overflow: kendo.support.touch ? "": "auto" })
                    .delegate(LI, CLICK, proxy(that._click, that))
                    .delegate(LI, "mouseenter", function() { $(this).addClass(HOVER); })
                    .delegate(LI, "mouseleave", function() { $(this).removeClass(HOVER); });

        that.list = $("<div class='k-list-container'/>")
                    .append(that.ul)
                    .mousedown(function(e) {
                        e.preventDefault();
                    });

        that._popup();

        that.template = kendo.template('<li class="k-item" unselectable="on">#=data#</li>', { useWithBlock: false });
    };

    TimeView.prototype = {
        current: function(candidate) {
            var that = this;

            if (candidate !== undefined) {
                if (that._current) {
                    that._current.removeClass(SELECTED);
                }

                if (candidate) {
                    candidate = $(candidate);
                    candidate.addClass(SELECTED);
                    that.scroll(candidate[0]);
                }

                that._current = candidate;
            } else {
                return that._current;
            }
        },

        close: function() {
            this.popup.close();
        },

        open: function() {
            var that = this;

            if (!that.ul[0].firstChild) {
                that.refresh();
            }

            that.popup.open();
            if (that._current) {
                that.scroll(that._current[0]);
            }
        },

        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),
                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;
            }

            for (; idx < length; idx++) {
                if (idx) {
                    setTime(start, msInterval, ignoreDST);
                }

                if (msMax && getMilliseconds(start) > msMax) {
                    start = new DATE(max);
                }

                html += template(toString(start, format));
            }

            that.ul[0].innerHTML = html;

            that._height(length);

            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;

            ul.scrollTop = ulScrollTop > itemOffsetTop
                        ? itemOffsetTop
                        : bottomDistance > (ulScrollTop + ulOffsetHeight)
                        ? bottomDistance - ulOffsetHeight
                        : ulScrollTop;
        },

        select: function(li) {
            var that = this,
                current = that._current;

            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);
        },

        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(length) {
            if (length) {
                var that = this,
                    list = that.list,
                    parent = list.parent(".k-animation-container"),
                    height = that.options.height;

                list.add(parent)
                    .show()
                    .height(that.ul[0].scrollHeight > height ? height : "auto")
                    .hide();
            }
        },

        _popup: function() {
            var that = this,
                list = that.list,
                options = that.options,
                anchor = options.anchor,
                width;

            that.popup = new ui.Popup(list, extend(options.popup, {
                anchor: anchor,
                open: options.open,
                close: options.close,
                animation: options.animation
            }));

            width = anchor.outerWidth() - (list.outerWidth() - list.width());

            list.css({
                fontFamily: anchor.css("font-family"),
                width: width
            });

            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();
                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
    }

    kendo.TimeView = TimeView;

    var TimePicker = Widget.extend(/** @lends kendo.ui.TimePicker.prototype */{
        /**
         *
         * Creates a TimePicker instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Date} [value] <null>
         * Specifies the selected time.
         *
         * @option {Date} [min] <00:00>
         * Specifies the start value in the popup list.
         *
         * @option {Date} [max] <00:00>
         * Specifies the end value in the popup list.
         *
         * @option {String} [format] <h:mm tt>
         * Specifies the format, which is used to parse value set with value() method.
         *
         * @option {Number} [interval] <30>
         * Specifies the interval, between values in the popup list, in minutes.
         *
         * @option {Object} [animation] <>
         * Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
         *
         * @option {Object} [animation.open] <>
         * Animation to be used for opening of the popup.
         *
         * _exampleTitle Intialize a TimePicker that fades-in the time drop-down list over 300 milliseconds
         * _example
         * $("#timePicker").kendoTimePicker({
         *     animation: {
         *         open: {
         *             effects: "fadeIn",
         *             duration: 300,
         *             show: true
         *         }
         *     }
         * });
         *
         * @option {Object} [animation.close] <>
         * Animation to be used for closing of the popup.
         *
         * _exampleTitle Initialize a TimePicker that fades-out the time drop-down list over 300 milliseconds
         * _example
         * $("#timepicker").kendoTimePicker({
         *     animation: {
         *         close: {
         *             effects: "fadeOut",
         *             duration: 300,
         *             hide: true
         *             show: false
         *         }
         *     }
         * });
         *
         */
        init: function(element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            element = that.element;
            options = that.options;

            options.format = options.format || kendo.culture().calendar.patterns.t;

            that._wrapper();

            that.timeView = new TimeView(extend({}, options, {
                anchor: that.wrapper,
                format: options.format,
                change: function(value, trigger) {
                    if (trigger) {
                        that._change(value);
                    } else {
                        element.val(value);
                    }
                },
                open: function(e) {
                    if (that.trigger(OPEN)) {
                        e.preventDefault();
                    }
                },
                close: function(e) {
                    if (that.trigger(CLOSE)) {
                        e.preventDefault();
                    }
                }
            }));

            that._icon();

            element.addClass("k-input")
                .bind({
                    keydown: proxy(that._keydown, that),
                    focus: function(e) {
                        that._inputWrapper.addClass(FOCUSED);
                    },
                    blur: proxy(that._blur, that)
                })
                .closest("form")
                .bind("reset", function() {
                    that.value(element[0].defaultValue);
                });

            that.enable(!element.is('[disabled]'));
            that.value(options.value || element.val());

            kendo.notify(that);
        },

        options: {
            name: "TimePicker",
            min: TODAY,
            max: TODAY,
            format: "",
            value: null,
            interval: 30,
            height: 200,
            animation: {}
        },

        events: [
        /**
         *
         * Triggered when the underlying value of a TimePicker is changed.
         *
         * @name kendo.ui.TimePicker#change
         * @event
         *
         * @param {Event} e
         *
         * @exampleTitle Attach change event handler during initialization; detach via unbind()
         * @example
         * // event change for expand
         * var onChange = function(e) {
         *     // ...
         * };
         *
         * // attach change event handler during initialization
         * var timePicker = $("#timePicker").kendoTimePicker({
         *     change: onChange
         * });
         *
         * // detach change event handler via unbind()
         * timePicker.data("kendoTimePicker").unbind("change", onChange);
         *
         * @exampleTitle Attach change event handler via bind(); detach via unbind()
         * @example
         * // event change for expand
         * var onChange = function(e) {
         *     // ...
         * };
         *
         * // attach change event handler via bind()
         * $("#timePicker").data("kendoTimePicker").bind("change", onChange);
         *
         * // detach change event handler via unbind()
         * $("#timePicker").data("kendoTimePicker").unbind("change", onChange);
         *
         */
         OPEN,
         CLOSE,
         CHANGE
        ],

        setOptions: function(options) {
            Widget.fn.setOptions.call(this, options);

            extend(this.timeView.options, options);

            this.timeView.refresh();
        },

        /**
         *
         * Enables or disables a TimePicker.
         *
         * @param {Boolean} enable
         * Enables (<strong>true</strong> or undefined) or disables (<strong>false</strong>) a TimePicker.
         *
         * @exampleTitle Enable a TimePicker
         * @example
         * $("timepicker").data("kendoTimePicker").enable();
         *
         * @exampleTitle Enable a TimePicker
         * @example
         * $("timepicker").data("kendoTimePicker").enable(true);
         *
         * @exampleTitle Disable a TimePicker
         * @example
         * $("timepicker").data("kendoTimePicker").enable(false);
         *
         */
        enable: function(enable) {
            var that = this,
                element = that.element,
                arrow = that._arrow.unbind(CLICK + " " + MOUSEDOWN),
                wrapper = that._inputWrapper.unbind(HOVEREVENTS);

            if (enable === false) {
                wrapper
                    .removeClass(DEFAULT)
                    .addClass(STATEDISABLED)

                element.attr(DISABLED, DISABLED);
            } else {
                wrapper
                    .removeClass(STATEDISABLED)
                    .addClass(DEFAULT)
                    .bind(HOVEREVENTS, that._toggleHover);

                element
                    .removeAttr(DISABLED);

                arrow.bind(CLICK, proxy(that._click, that))
                     .bind(MOUSEDOWN, function(e) { e.preventDefault(); });
            }
        },

        /**
         *
         * Closes the drop-down list of a TimePicker.
         *
         * @exampleTitle Close the time drop-down list of a TimePicker.
         * @example
         * $("timepicker").data("kendoTimePicker").close();
         *
         */
        close: function() {
            this.timeView.close();
        },

        /**
         *
         * Opens the drop-down list of a TimePicker.
         *
         * @exampleTitle Open the time drop-down list of a TimePicker.
         * @example
         * $("timepicker").data("kendoTimePicker").open();
         *
         */
        open: function() {
            this.timeView.open();
        },

        /**
         *
         * Gets or sets the minimum value of the TimePicker.
         *
         * @param {Date|String} value
         * The minimum time value to set for a TimePicker, expressed as a Date object or as a string.
         *
         * @returns {Date}
         * The minimum time value of a TimePicker.
         *
         * @exampleTitle Get the minimum value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * var minimum = timePicker.min();
         *
         * @exampleTitle Set the minimum value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * timePicker.min(new Date(1900, 0, 1, 10, 0, 0));
         *
         */
        min: function (value) {
            return this._option("min", value);
        },

        /**
         *
         * Gets or sets the maximum value of the TimePicker.
         *
         * @param {Date|String} value
         * The maximum time value to set for a TimePicker, expressed as a Date object or as a string.
         *
         * @returns {Date}
         * The maximum time value of a TimePicker.
         *
         * @exampleTitle Get the maximum value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * var maximum = timePicker.max();
         *
         * @exampleTitle Set the maximum value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * timePicker.max(new Date(1900, 0, 1, 10, 0, 0));
         *
         */
        max: function (value) {
            return this._option("max", value);
        },

        /**
         *
         * Gets or sets the value of the TimePicker.
         *
         * @param {Date|String} value
         * The time value to set for a TimePicker, expressed as a Date object or as a string.
         *
         * @returns {Date}
         * The time value of a TimePicker.
         *
         * @exampleTitle Get the value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * var timePickerValue = timePicker.value();
         *
         * @exampleTitle Set the value of a TimePicker
         * @example
         * var timePicker = $("#timePicker").data("kendoTimePicker");
         * timePicker.value("10:00 AM");
         *
         */
        value: function(value) {
            var that = this;

            if (value === undefined) {
                return that._value;
            }

            that._old = that._update(value);
        },

        _blur: function() {
            var that = this;

            that.close();
            that._change(that.element.val());
            that._inputWrapper.removeClass(FOCUSED);
        },

        _click: function() {
            var that = this,
                element = that.element;

            if (!touch && element[0] !== document.activeElement) {
                element.focus();
            }

            that.timeView.toggle();
        },

        _change: function(value) {
            var that = this;

            value = that._update(value);

            if (+that._old != +value) {
                that._old = value;
                that.trigger(CHANGE);

                // trigger the DOM change event so any subscriber gets notified
                that.element.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-icon-clock">select</span></span>').insertAfter(element);
            }

            that._arrow = arrow;
        },

        _keydown: function(e) {
            var that = this,
                key = e.keyCode,
                enter = key == keys.ENTER,
                timeView = that.timeView;

            if (timeView.popup.visible() || e.altKey || enter) {
                timeView.move(e);
            }

            if (enter) {
                that._change(that.element.val());
            }
        },

        _option: function(option, value) {
            var that = this,
                options = that.options;

            if (value === undefined) {
                return options[option];
            }

            value = that._parse(value);

            if (!value) {
                return;
            }

            value = new DATE(value);

            options[option] = value;
            that.timeView.options[option] = value;
            that.timeView.refresh();
        },

        _parse: function(value) {
            var that = this,
                current = that._value || TODAY;

            if (value instanceof DATE) {
                return value;
            }

            value = kendo.parseDate(value, that.options.format);

            if (value) {
                value = new DATE(current.getFullYear(),
                                 current.getMonth(),
                                 current.getDate(),
                                 value.getHours(),
                                 value.getMinutes(),
                                 value.getSeconds(),
                                 value.getMilliseconds());
            }

            return value;
        },

        _toggleHover: function(e) {
            if (!touch) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            }
        },

        _update: function(value) {
            var that = this,
                current = that._value,
                options = that.options,
                date = that._parse(value),
                text = kendo.toString(date, options.format);

            if (!isInRange(date, options.min, options.max)) {
                date = null;
            }

            that._value = date;
            that.element.val(date ? text : value);
            that.timeView.value(text);

            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;
            element.css({
                width: "100%",
                height: element[0].style.height
            });

            that.wrapper = wrapper.addClass("k-widget k-timepicker k-header");
            that._inputWrapper = $(wrapper[0].firstChild);
        }
    });

    ui.plugin(TimePicker);

})(jQuery);
/**
 * @fileOverview Provides a TreeView implementation which can be used to display hierarchical data in a traditional
 * tree structure.
 */

(function($, undefined){
    /**
     * @name kendo.ui.TreeView.Description
     *
     * @section
     * <p>
     *  The <strong>TreeView</strong> displays hierarchical data in a traditional tree structure. It supports user
     *  interaction through the mouse or touch to perform re-ordering operations via drag-and-drop.
     * </p>
     * <p>
     *  A <strong>TreeView</strong> can be created by leveraging HTML lists. However, it does not support binding to a
     *  remote data source at this point in time.
     * </p>
     * <h3>Getting Started</h3>
     * <p>A <strong>TreeView</strong> can be created in two ways:</p>
     * <ol>
     *  <li>Define a hierarchical list with static HTML</li>
     *  <li>Use dynamic data binding</li>
     * </ol>
     * <p>
     *  Static HTML definition is appropriate for small hierarchies and for data that does not change frequently.
     *  Databinding should be used for larger data sets and for data that changes frequently.
     * </p>
     * <h3>Creating a TreeView from HTML</h3>
     *
     * @exampleTitle Create a hierarchical list in HTML
     * @example
     * <ul id="treeView">
     *     <li>Item 1
     *         <ul>
     *             <li>Item 1.1</li>
     *             <li>Item 1.2</li>
     *         </ul>
     *     </li>
     *     <li>Item 2</li>
     * </ul>
     *
     * @section
     * <p>
     *  Initialization of a <strong>TreeView</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>TreeView</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a TreeView using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#treeView").kendoTreeView();
     * });
     *
     * @section
     * <h3>Creating a TreeView with Data Binding to a Local Data Source</h3>
     *
     * @exampleTitle Create a hierarchical HTML list
     * @example
     * <div id="treeView"></div>
     *
     * @exampleTitle Initialize and bind the TreeView
     * @example
     * $(document).ready(function() {
     *     $("#treeView").kendoTreeView({
     *         dataSource: [
     *             {
     *                 text: "Item 1",
     *                 items: [
     *                     { text: "Item 1.1" },
     *                     { text: "Item 1.2" }
     *                 ]
     *             },
     *             { text: "Item 2" }
     *         ]
     *     })
     * });
     *
     * @section
     * <p>Currently, the <strong>TreeView</strong> does not support binding to a remote data source.</p>
     * @exampleTitle TreeView item JSON structure
     * @example
     * var item = {
     *     text: "Item text",
     *     imageUrl: "/images/icon.png", // renders a <img class="k-image" src="/images/icon.png" />
     *     spriteCssClass: "icon save",  // renders a <span class="k-sprite icon save" />
     *     items: [
     *         // child items
     *     ]
     * }
     *
     * @section
     * <h3>Configuring TreeView Behavior</h3>
     * <p>
     *  A number of <strong>TreeView</strong> behaviors can be easily controlled by simple configuration properties,
     *  such as animation behaviors and drag-and-drop behaviors.
     * </p>
     *
     * @exampleTitle Enabling drag-and-drop for TreeView nodes
     * @example
     * $("#treeView").kendoTreeView({
     *     dragAndDrop: true
     * });
     *
     * @section
     * <p>
     *  When drag-and-drop is enabled, the nodes of a <strong>TreeView</strong> can be dragged and dropped between all
     *  levels, with useful tooltips helping indicate where the node will be dropped.
     * </p>
     * <h3>Accessing an Existing TreeView</h3>
     * <p>
     *  You can reference an existing <strong>TreeView</strong> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing TreeView instance
     * @example
     * var treeView = $("#treeView").data("kendoTreeView");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        extend = $.extend,
        template = kendo.template,
        Widget = ui.Widget,
        proxy = $.proxy,
        SELECT = "select",
        EXPAND = "expand",
        COLLAPSE = "collapse",
        DRAGSTART = "dragstart",
        DRAG = "drag",
        NODEDRAGCANCELLED = "nodeDragCancelled",
        DROP = "drop",
        DRAGEND = "dragend",
        CLICK = "click",
        VISIBILITY = "visibility",
        TSTATEHOVER = "k-state-hover",
        TTREEVIEW = "k-treeview",
        TITEM = "k-item",
        VISIBLE = ":visible",
        NODE = ".k-item",
        SUBGROUP = ">.k-group,>.k-animation-container>.k-group",
        NODECONTENTS = SUBGROUP + ",>.k-content,>.k-animation-container>.k-content",
        templates, rendering, TreeView;

    function updateNodeHtml(node) {
        var wrapper = node.find(">div"),
            subGroup = node.find(">ul"),
            toggleButton = wrapper.find(">.k-icon"),
            innerWrapper = wrapper.find(">.k-in");

        if (node.hasClass("k-treeview")) {
            return;
        }

        if (!wrapper.length) {
            wrapper = $("<div />").prependTo(node);
        }

        if (!toggleButton.length && subGroup.length) {
            toggleButton = $("<span class='k-icon' />").prependTo(wrapper);
        } else if (!subGroup.length || !subGroup.children().length) {
            toggleButton.remove();
            subGroup.remove();
        }

        if (!innerWrapper.length) {
            innerWrapper = $("<span class='k-in' />").appendTo(wrapper)[0];

            // move all non-group content in the k-in container
            currentNode = wrapper[0].nextSibling;
            innerWrapper = wrapper.find(".k-in")[0];

            while (currentNode && currentNode.nodeName.toLowerCase() != "ul") {
                tmp = currentNode;
                currentNode = currentNode.nextSibling;

                if (tmp.nodeType == 3) {
                    tmp.nodeValue = $.trim(tmp.nodeValue);
                }

                innerWrapper.appendChild(tmp);
            }
        }
    }

    function updateNodeClasses(node, groupData, nodeData) {
        var wrapper = node.find(">div"),
            subGroup = node.find(">ul")

        if (node.hasClass("k-treeview")) {
            return;
        }

        if (!nodeData) {
            nodeData = {
                expanded: !(subGroup.css("display") == "none"),
                index: node.index(),
                enabled: !wrapper.find(">.k-in").hasClass("k-state-disabled")
            };
        }

        if (!groupData) {
            groupData = {
                firstLevel: node.parent().parent().hasClass(TTREEVIEW),
                length: node.parent().children().length
            };
        }

        // li
        node.removeClass("k-first k-last")
            .addClass(rendering.wrapperCssClass(groupData, nodeData));

        // div
        wrapper.removeClass("k-top k-mid k-bot")
               .addClass(rendering.cssClass(groupData, nodeData));

        // toggle button
        if (subGroup.length) {
            wrapper.find(">.k-icon").removeClass("k-plus k-minus k-plus-disabled k-minus-disabled")
                .addClass(rendering.toggleButtonClass(nodeData));

            subGroup.addClass("k-group");
        }
    }


    templates = {
        dragClue: template("<div class='k-header k-drag-clue'><span class='k-icon k-drag-status'></span>#= text #</div>"),
        group: template(
            "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
                "#= renderItems(data) #" +
            "</ul>"
        ),
        itemWrapper: template(
            "<div class='#= cssClass(group, item) #'>" +
                "#= toggleButton(data) #" +
                "#= checkbox(data) #" +
                "<#= tag(item) # class='#= textClass(item) #'#= textAttributes(item) #>" +
                    "#= image(item) ##= sprite(item) #" +
                    "#= treeview.template ? template(treeview, item) : text(item) #" +
                "</#= tag(item) #>" +
            "</div>"
        ),
        item: template(
            "<li class='#= wrapperCssClass(group, item) #'>" +
                "#= itemWrapper(data) #" +
                "# if (item.items) { #" +
                "#= subGroup(treeview, group, item) #" +
                "# } #" +
            "</li>"
        ),
        checkbox: template(
            "# if (treeview.checkboxTemplate) { #" +
                "<span class='k-checkbox'>" +
                    "#= checkboxTemplate(treeview, group, item) #" +
                "</span>" +
            "# } #"
        ),
        image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
        toggleButton: template("<span class='#= toggleButtonClass(item) #'></span>"),
        sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
        empty: template("")
    };

    TreeView = Widget.extend(/** @lends kendo.ui.TreeView.prototype */ {
        /**
         *
         * Creates a TreeView instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Array} [dataSource]
         * The data that the <strong>TreeView</strong> will be bound to.
         *
         * @option {Object} [animation]
         * A collection of visual animations used when items are expanded or collapsed through user interaction.
         * Setting this option to <strong>false</strong> will disable all animations.
         *
         * _example
         * $("#treeView").kendoTreeView({
         *     animation: {
         *         expand: {
         *             duration: 200,
         *             hide: true,
         *             show: false
         *         }
         *         collapse: {
         *             duration: 200,
         *             effects: "expandVertical",
         *             show: true
         *         }
         *     }
         * });
         *
         * @option {Animation} [animation.expand]
         * The animation that will be used when expanding items.
         *
         * @option {Number} [animation.expand.duration] <200> The number of milliseconds used for the animation when a
         * node is expanded.
         *
         * _example
         * $("#treeView").kendoTreeView({
         *     animation: {
         *         expand: {
         *             duration: 1000
         *         }
         *     }
         * });
         *
         * @option {String} [animation.expand.effects] <"expandVertical">
         * A whitespace-delimited string of animation effects that are utilized when a <strong>TreeView</strong> node
         * is expanded. Options include <strong>"expandVertical"</strong> and <strong>"fadeIn"</strong>.
         *
         * _exampleTitle Initialize a TreeView to expand and fade-in nodes over 5000 milliseconds
         * _example
         * $("#treeView").kendoTreeView({
         *     animation: {
         *         expand: {
         *             duration: 5000,
         *             effects: "expandVertical fadeIn"
         *         }
         *     }
         * });
         *
         * @option {Boolean} [animation.expand.show] <true>
         *
         * @option {Boolean} [dragAndDrop] <false>
         * Disables (<strong>false</strong>) or enables (<b>true</b>) drag-and-drop on the nodes of a
         * <strong>TreeView</strong>.
         *
         * @option {Animation} [animation.collapse]
         * The animation that will be used when collapsing items.
         *
         * @option {Number} [animation.collapse.duration] <200>
         * The number of milliseconds used for the animation when a node is expanded.
         *
         * _exampleTitle Initialize a TreeView to collapse nodes over 1000 milliseconds
         * _example
         * $("#treeView").kendoTreeView({
         *     animation: {
         *         collapse: {
         *             duration: 1000
         *         }
         *     }
         * });
         *
         * @option {String} [animation.collapse.effects]
         * A whitespace-delimited string of animation effects that are utilized when a <strong>TreeView</strong> node
         * is collapsed. Options include <strong>"fadeOut"</strong>.
         *
         * _exampleTitle Initialize a TreeView to collapse and fade-out nodes over 5000 milliseconds
         * _example
         * $("#treeView").kendoTreeView({
         *     animation: {
         *         collapse: {
         *             duration: 5000,
         *             effects: "fadeOut"
         *         }
         *     }
         * });
         *
         * @option {String|Function} [template] Template for rendering of the nodes of the treeview.
         * _example
         * $("#treeview").kendoTreeView({
         *     template: "#= item.text # <a href='\\#'>Delete</a>"
         * });
         *
         * @option {String|Function} [checkboxTemplate] Template for rendering of the treeview checkboxes.
         * _example
         * $("#treeview").kendoTreeView({
         *     template: kendo.template(
         *         "<input type='checkbox' name='checkedFiles[" +
         *             item.id +
         *         "]' value='true' />"
         *     )
         * });
         */
        init: function (element, options) {
            var that = this,
                clickableItems = ".k-in:not(.k-state-selected,.k-state-disabled)",
                MOUSEENTER = "mouseenter",
                dataInit;

            options = $.isArray(options) ? (dataInit = true, { dataSource: options }) : options;

            Widget.prototype.init.call(that, element, options);

            element = that.element;
            options = that.options;

            that._animation();

            if (options.template && typeof options.template == "string") {
                options.template = template(options.template);
            }

            // render treeview if it's not already rendered
            if (!element.hasClass(TTREEVIEW)) {
                that._wrapper();

                if (!that.root.length) { // treeview initialized from empty element
                    that.root = that.wrapper.html(TreeView.renderGroup({
                        items: options.dataSource,
                        group: {
                            firstLevel: true,
                            expanded: true
                        },
                        treeview: options
                    })).children("ul");
                } else {
                    that._group(that.wrapper);
                }
            } else {
                // otherwise just initialize properties
                that.wrapper = element;
                that.root = element.children("ul").eq(0);
            }

            that.wrapper
                .on(MOUSEENTER, ".k-in.k-state-selected", function(e) { e.preventDefault(); })
                .on(MOUSEENTER, clickableItems, function () { $(this).addClass(TSTATEHOVER); })
                .on("mouseleave", clickableItems, function () { $(this).removeClass(TSTATEHOVER); })
                .on(CLICK, clickableItems, proxy(that._nodeClick, that))
                .on("dblclick", "div:not(.k-state-disabled) .k-in", proxy(that._toggleButtonClick, that))
                .on(CLICK, ".k-plus,.k-minus", proxy(that._toggleButtonClick, that));

            if (options.dragAndDrop) {
                that.dragging = new TreeViewDragAndDrop(that);
            }
        },

        _animation: function() {
            var options = this.options;

            if (options.animation === false) {
                options.animation = {
                    expand: { show: true, effects: {} },
                    collapse: { hide: true, effects: {} }
                };
            }
        },

        events: [
            /**
            *
            * Triggered before the dragging of a node starts.
            *
            * @name kendo.ui.TreeView#dragstart
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.sourceNode
            * The node that will be dragged.
            *
            * @exampleTitle Disable dragging of root nodes
            * @example
            * treeview.data("kendoTreeView").bind("dragstart", function(e) {
            *     if ($(e.sourceNode).parentsUntil(".k-treeview", ".k-item").length == 0) {
            *         e.preventDefault();
            *     }
            * });
            *
            */
            DRAGSTART,

            /**
            *
            * Triggered while a node is being dragged.
            *
            * @name kendo.ui.TreeView#drag
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.sourceNode
            * The node that is being dragged.
            *
            * @param {DomElement} e.dropTarget
            * The element that the node is placed over.
            *
            * @param {Integer} e.pageX
            * The x coordinate of the mouse.
            *
            * @param {Integer} e.pageY
            * The y coordinate of the mouse.
            *
            * @param {String} e.statusClass
            * The status that the drag clue shows.
            *
            * @param {Function} e.setStatusClass
            * Allows a custom drag clue status to be set.
            * <p>Pre-defined status classes are:</p>
            * <ul>
            *     <li><strong>k-insert-top</strong>
            *         - Indicates that the item will be inserted on top.
            *     </li>
            *     <li><strong>k-insert-middle</strong>
            *         - Indicates that the item will be inserted in the middle.
            *     </li>
            *     <li><strong>k-insert-bottom</strong>
            *         - Indicates that the item will be inserted at the bottom.
            *     </li>
            *     <li><strong>k-add</strong>
            *         - Indicates that the item will be added/appended.
            *     </li>
            *     <li><strong>k-denied</strong>
            *         - Indicates an invalid operation. Using this class will automatically
            *           make the drop operation invalid, so there will be no need to call
            *           <code>setValid(false)</code> in the <code>drop</code> event.
            *     </li>
            * </ul>
            *
            * @exampleTitle Show the user that is not permitted to drop nodes outside of the #drop-area element
            * @example
            * treeview.data("kendoTreeView").bind("drag", function(e) {
            *     if ($(e.dropTarget).parents("#drop-area").length ) {
            *         e.setStatusClass("k-denied");
            *     }
            * });
            *
            */
            DRAG,

            /**
            *
            * Triggered when a node is being dropped.
            *
            * @name kendo.ui.TreeView#drop
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.sourceNode
            * The node that is being dropped.
            *
            * @param {Node} e.destinationNode
            * The node that the sourceNode is being dropped upon.
            *
            * @param {Boolean} e.valid
            * Whether this drop operation is permitted.
            *
            * @param {Function} e.setValid
            * Allows the drop to be prevented.
            *
            * @param {DomElement} e.dropTarget
            * The element that the node is placed over.
            *
            * @param {String} e.dropPosition
            * Shows where the new sourceLocation would be.
            *
            */
            DROP,

            /**
            *
            * Triggered after a node is has been dropped.
            *
            * @name kendo.ui.TreeView#dragend
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.sourceNode
            * The node that is being dropped.
            *
            * @param {Node} e.destinationNode
            * The node that the sourceNode is being dropped upon.
            *
            * @param {String} e.dropPosition
            * Shows where the new sourceLocation would be.
            *
            */
            DRAGEND,
            /**
            *
            * Triggered before a subgroup gets expanded.
            *
            * @name kendo.ui.TreeView#expand
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.node
            * The expanded node
            *
            */
            EXPAND,

            /**
            *
            * Triggered before a subgroup gets collapsed.
            *
            * @name kendo.ui.TreeView#collapse
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.node
            * The collapsed node
            *
            */
            COLLAPSE,

            /**
            *
            * Triggered when a node gets selected.
            *
            * @name kendo.ui.TreeView#select
            * @event
            *
            * @param {Event} e
            *
            * @param {Node} e.node
            * The selected node
            *
            */
            SELECT
        ],

        options: {
            name: "TreeView",
            dataSource: {},
            animation: {
                expand: {
                    effects: "expand:vertical",
                    duration: 200,
                    show: true
                },
                collapse: {
                    duration: 100
                }
            },
            dragAndDrop: false
        },

        setOptions: function(options) {
            var that = this;

            if (("dragAndDrop" in options) && options.dragAndDrop && !that.options.dragAndDrop) {
                that.dragging = new TreeViewDragAndDrop(that);
            }

            Widget.fn.setOptions.call(that, options);

            that._animation();
        },

        _trigger: function (eventName, node) {
            return this.trigger(eventName, {
                node: node.closest(NODE)[0]
            });
        },

        _toggleButtonClick: function (e) {
            this.toggle($(e.target).closest(NODE));
        },

        _nodeClick: function (e) {
            var that = this,
                node = $(e.target),
                contents = node.closest(NODE).find(NODECONTENTS),
                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 k-reset";

            if (element.is("div")) {
                wrapper = element;
                root = wrapper.children("ul").eq(0);
            } else { // element is ul
                wrapper = element.wrap('<div />').parent();
                root = element;
            }

            that.wrapper = wrapper.addClass(wrapperClasses);
            that.root = root;
        },

        _group: function(item) {
            var that = this,
                firstLevel = item.hasClass(TTREEVIEW),
                group = {
                    firstLevel: firstLevel,
                    expanded: firstLevel || item.attr(kendo.attr("expanded")) === "true"
                },
                groupElement = item.find("> ul");

            groupElement
                .addClass(rendering.groupCssClass(group))
                .css("display", group.expanded ? "" : "none");

            that._nodes(groupElement, group);
        },

        _nodes: function(groupElement, groupData) {
            var that = this,
                nodes = groupElement.find("> li"),
                nodeData;

            groupData = extend({ length: nodes.length }, groupData);

            nodes.each(function(i, node) {
                node = $(node);

                nodeData = { index: i, expanded: node.attr(kendo.attr("expanded")) === "true" };

                updateNodeHtml(node);

                updateNodeClasses(node, groupData, nodeData);

                // iterate over child nodes
                that._group(node);
            });
        },

        _processNodes: function(nodes, callback) {
            var that = this;
            that.element.find(nodes).each(function(index, item) {
                callback.call(that, index, $(item).closest(NODE));
            });
        },

        /**
         *
         * Expands nodes.
         *
         * @param {Selector} nodes
         * The nodes that are to be expanded.
         *
         * @example
         * var treeview = $("#treeview").data("kendoTreeView");
         *
         * // expands the node with id="firstItem"
         * treeview.expand(document.getElementById("firstItem"));
         *
         * // expands all nodes
         * treeview.expand(".k-item");
         *
         */
        expand: function (nodes) {
            this._processNodes(nodes, function (index, item) {
                var contents = item.find(NODECONTENTS);

                if (contents.length > 0 && !contents.is(VISIBLE)) {
                    this.toggle(item);
                }
            });
        },

        /**
         *
         * Collapses nodes.
         *
         * @param {Selector} nodes
         * The nodes that are to be collapsed.
         *
         * @example
         * var treeview = $("#treeview").data("kendoTreeView");
         *
         * // collapse the node with id="firstItem"
         * treeview.collapse(document.getElementById("firstItem"));
         *
         * // collapse all nodes
         * treeview.collapse(".k-item");
         *
         */
        collapse: function (nodes) {
            this._processNodes(nodes, function (index, item) {
                var contents = item.find(NODECONTENTS);

                if (contents.length > 0 && contents.is(VISIBLE)) {
                    this.toggle(item);
                }
            });
        },

        /**
         *
         * Enables or disables nodes.
         *
         * @param {Selector} nodes
         * The nodes that are to be enabled/disabled.
         *
         * @param {Boolean} [enable=true]
         * Whether the nodes should be enabled or disabled.
         *
         * @example
         * var treeview = $("#treeview").data("kendoTreeView");
         *
         * // disable the node with id="firstItem"
         * treeview.enable(document.getElementById("firstItem"), false);
         *
         * // enable all nodes
         * treeview.enable(".k-item");
         *
         */
        enable: function (nodes, enable) {
            enable = arguments.length == 2 ? !!enable : true;

            this._processNodes(nodes, function (index, item) {
                var isCollapsed = !item.find(NODECONTENTS).is(VISIBLE);

                if (!enable) {
                    this.collapse(item);
                    isCollapsed = true;
                }

                item.find(">div")
                        .find(">.k-in")
                            .toggleClass("k-state-default", enable)
                            .toggleClass("k-state-disabled", !enable)
                        .end()
                        .find(">.k-icon")
                            .toggleClass("k-plus", isCollapsed && enable)
                            .toggleClass("k-plus-disabled", isCollapsed && !enable)
                            .toggleClass("k-minus", !isCollapsed && enable)
                            .toggleClass("k-minus-disabled", !isCollapsed && !enable);
            });
        },

        /**
         *
         * Gets or sets the selected node of a TreeView.
         *
         * @param {Selector} [node]
         * If provided, the node of a TreeView that should be selected.
         *
         * @returns {Node}
         * The selected node of a TreeView.
         *
         * @exampleTitle Select the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.select($("#firstItem"));
         *
         * @exampleTitle Get the currently selected node
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * var selectedNode = treeView.select();
         *
         */
        select: function (node) {
            var element = this.element;

            if (arguments.length == 0) {
                return element.find(".k-state-selected").closest(NODE);
            }

            node = $(node, element).closest(NODE);

            if (node.length) {
                element.find(".k-in").removeClass("k-state-hover k-state-selected");

                node.find(".k-in:first").addClass("k-state-selected");
            }
        },

        /**
         *
         * Toggles the node of a TreeView between its expanded and collapsed states.
         *
         * @param {Selector} node
         * The node that should be toggled.
         *
         * @exampleTitle Toggle the state of a node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.toggle($("#firstItem"));
         *
         */
        toggle: function (node) {
            node = $(node);

            if (node.find(".k-minus,.k-plus").length == 0) {
                return;
            }

            if (node.find("> div > .k-state-disabled").length) {
                return;
            }

            var that = this,
                contents = node.find(NODECONTENTS),
                isExpanding = !contents.is(VISIBLE),
                animationSettings = that.options.animation || {},
                animation = animationSettings.expand,
                collapse = extend({}, animationSettings.collapse),
                hasCollapseAnimation = collapse && "effects" in collapse;

            if (contents.data("animating"))
                return;

            if (!isExpanding) {
                animation = extend( hasCollapseAnimation ? collapse
                                    : extend({ reverse: true }, animation), { show: false, hide: true });
            }

            if (contents.children().length > 0) {
                if (!that._trigger(isExpanding ? "expand" : "collapse", node)) {
                    node.find("> div > .k-icon")
                        .toggleClass("k-minus", isExpanding)
                        .toggleClass("k-plus", !isExpanding);

                    if (!isExpanding) {
                        contents.css("height", contents.height()).css("height");
                    }

                    contents.kendoStop(true, true).kendoAnimate(extend(animation, {
                        complete: function() {
                            if (isExpanding) {
                                contents.css("height", "");
                            }
                        }
                    }));
                }
            }
        },

        /**
         *
         * Gets the text of a node in a TreeView.
         *
         * @param {Selector} node
         * The node of which the text is being retrieved.
         *
         * @returns {String}
         * The text of a node.
         *
         * @exampleTitle Get the text of the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * var nodeText = treeView.text($("#firstItem"));
         *
         */
        text: function (node) {
            return $(node).closest(NODE).find(">div>.k-in").text();
        },

        _insertNode: function(nodeData, index, parentNode, group, insertCallback) {
            var that = this,
                updatedGroupLength = group.children().length + 1,
                isArrayData = $.isArray(nodeData),
                fromNodeData = isArrayData || $.isPlainObject(nodeData),
                groupData = {
                    firstLevel: parentNode.hasClass(TTREEVIEW),
                    expanded: true,
                    length: updatedGroupLength
                }, node, i, nodeHtml = "";

            if (fromNodeData) {
                if (isArrayData) {
                    for (i = 0; i < nodeData.length; i++) {
                        nodeHtml += TreeView.renderItem({
                            group: groupData,
                            item: extend(nodeData[i], { index: index + i })
                        });
                    }

                } else {
                    nodeHtml = TreeView.renderItem({
                        group: groupData,
                        item: extend(nodeData, { index: index })
                    });
                }

                node = $(nodeHtml);
            } else {
                node = $(nodeData);

                if (group.children()[index - 1] == node[0]) {
                    return node;
                }

                if (node.closest(".k-treeview")[0] == that.wrapper[0]) {
                    that.detach(node);
                }
            }

            if (!group.length) {
                group = $(TreeView.renderGroup({
                    group: groupData
                })).appendTo(parentNode);
            }

            insertCallback(node, group);

            if (parentNode.hasClass("k-item")) {
                updateNodeHtml(parentNode);
                updateNodeClasses(parentNode);
            }

            if (!fromNodeData) {
                updateNodeClasses(node);
            }

            updateNodeClasses(node.prev());
            updateNodeClasses(node.next());

            return node;
        },

        /**
         *
         * Inserts a node after a specified node in a TreeView. This method may also be used to reorder the nodes of a
         * TreeView.
         *
         * @param {String|Selector} nodeData
         * A JSON-formatted string or selector that specifies the node to be inserted.
         *
         * @param {Node} referenceNode
         * The node that will be preceed the newly-appended node.
         *
         * @exampleTitle Insert a node with the text, "Y U NO insert node?" after the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.insertAfter({ text: "Y U NO insert node?" }, $("#firstItem"));
         *
         * @exampleTitle Moves a node with ID, secondNode after a node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.insertAfter($("#secondNode"), $("#firstItem"));
         *
         */
        insertAfter: function (nodeData, referenceNode) {
            var group = referenceNode.parent();

            return this._insertNode(nodeData, referenceNode.index() + 1, group.parent(), group, function(item, group) {
                item.insertAfter(referenceNode);
            });
        },

        /**
         *
         * Inserts a node before another node. This method may also be used to reorder the nodes of a
         * TreeView.
         *
         * @param {String|Selector} nodeData
         * A JSON-formatted string or selector that specifies the node to be inserted.
         *
         * @param {Node} referenceNode
         * The node that follows the inserted node.
         *
         * @exampleTitle Inserts a new node with the text, "It's over 9000!" before the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.insertBefore({ text: "It's over 9000!" }, $("#firstItem"));
         *
         * @exampleTitle Moves the node with ID, secondNode before the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.insertBefore($("#secondNode"), $("#firstItem"));
         *
         */
        insertBefore: function (nodeData, referenceNode) {
            var group = referenceNode.parent();

            return this._insertNode(nodeData, referenceNode.index(), group.parent(), group, function(item, group) {
                item.insertBefore(referenceNode);
            });
        },

        /**
         *
         * Appends a node to a group of a TreeView. This method may also be used to reorder the nodes of a
         * TreeView.
         *
         * @param {String|Selector} nodeData
         * A JSON-formatted string or selector that specifies the node to be appended.
         *
         * @param {Node} [parentNode]
         * The node that will contain the newly appended node. If not specified, the new node will be appended to the
         * root group of the TreeView.
         *
         * @exampleTitle Append a new node with the text, "Meanwhile, in HTML5..." to the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.append({ text: "Meanwhile, in HTML5..." }, $("#firstItem"));
         *
         * @exampleTitle Moves the node with ID, secondNode as a last child of the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.append($("#secondNode"), $("#firstItem"));
         *
         */
        append: function (nodeData, parentNode) {
            parentNode = parentNode || this.element;

            var group = parentNode.find(SUBGROUP);

            return this._insertNode(nodeData, group.children().length, parentNode, group, function(item, group) {
                item.appendTo(group);
            });
        },

        _remove: function (node, keepData) {
            var parentNode,
                prevSibling, nextSibling;

            node = $(node, this.element);

            parentNode = node.parent().parent();
            prevSibling = node.prev();
            nextSibling = node.next();

            node[keepData ? "detach" : "remove"]();

            if (parentNode.hasClass("k-item")) {
                updateNodeHtml(parentNode);
                updateNodeClasses(parentNode);
            }

            updateNodeClasses(prevSibling);
            updateNodeClasses(nextSibling);

            return node;
        },

        /**
         *
         * Removes a node from a TreeView.
         *
         * @param {Selector} node
         * The node that is to be removed.
         *
         * @exampleTitle Remove the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * treeView.remove($("#firstItem"));
         *
         */
        remove: function (node) {
            return this._remove(node, false);
        },

        /**
         *
         * Removes a node from a TreeView, but keeps its jQuery.data() objects.
         *
         * @param {Selector} node
         * The node that is to be detached.
         *
         * @returns {jQueryObject}
         * The node that has been detached.
         *
         * @exampleTitle Remove the node with ID, firstItem
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * var firstItem = $("#firstItem");
         * firstItem.data("id", 1);
         * treeview.detach(firstItem);
         * firstItem.data("id") == 1;
         *
         */
        detach: function (node) {
            return this._remove(node, true);
        },

        /**
         *
         * Searches a TreeView for a node that has specific text.
         *
         * @param {String} text
         * The text that is being searched for.
         *
         * @returns {jQueryObject}
         * All nodes that have the text.
         *
         * @exampleTitle Search a TreeView for the item that has the text, "CSS3 is da bomb!"
         * @example
         * var treeView = $("#treeView").data("kendoTreeView");
         * var foundNode = treeView.findByText("CSS3 is da bomb!");
         *
         */
        findByText: function(text) {
            return $(this.element).find(".k-in").filter(function(i, element) {
                return $(element).text() == text;
            }).closest(NODE);
        }
    });

    function TreeViewDragAndDrop(treeview) {
        var that = this;

        that.treeview = treeview;

        that._draggable = new ui.Draggable(treeview.element, {
           filter: "div:not(.k-state-disabled) .k-in",
           hint: function(node) {
               return templates.dragClue({ text: node.text() });
           },
           cursorOffset: {
               left: 10,
               top: kendo.support.touch ? -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)
        });
    }

    TreeViewDragAndDrop.prototype = /** @ignore */{
        _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,
                hoveredItem, hoveredItemPos, itemHeight, itemTop, itemContent, delta,
                insertOnTop, insertOnBottom, addChild;

            if (!dropTarget.closest(".k-treeview").length) {
                // dragging node outside of treeview
                statusClass = "k-denied";
            } else if ($.contains(sourceNode[0], dropTarget[0])) {
                // dragging node within itself
                statusClass = "k-denied";
            } else {
                // moving or reordering node
                statusClass = "k-insert-middle";

                that.dropHint.css(VISIBILITY, "visible");

                hoveredItem = dropTarget.closest(".k-top,.k-mid,.k-bot");

                if (hoveredItem.length > 0) {
                    itemHeight = hoveredItem.outerHeight();
                    itemTop = hoveredItem.offset().top;
                    itemContent = dropTarget.closest(".k-in");
                    delta = itemHeight / (itemContent.length > 0 ? 4 : 2);

                    insertOnTop = e.pageY < (itemTop + delta);
                    insertOnBottom = (itemTop + itemHeight - delta) < e.pageY;
                    addChild = itemContent.length > 0 && !insertOnTop && !insertOnBottom;

                    itemContent.toggleClass(TSTATEHOVER, addChild);
                    that.dropHint.css(VISIBILITY, addChild ? "hidden" : "visible");

                    if (addChild) {
                        statusClass = "k-add";
                    } else {
                        hoveredItemPos = hoveredItem.position();
                        hoveredItemPos.top += insertOnTop ? 0 : itemHeight;

                        that.dropHint
                            .css(hoveredItemPos)
                            [insertOnTop ? "prependTo" : "appendTo"](dropTarget.closest(NODE).find("> 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]) {
                    statusClass = "k-denied";
                }
            }

            treeview.trigger(DRAG, {
                sourceNode: sourceNode[0],
                dropTarget: dropTarget[0],
                pageY: e.pageY,
                pageX: e.pageX,
                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(e) {
            this.dropHint.remove();
            //treeview.trigger("nodeDragCancelled", { item: sourceNode[0] });
        },

        dragend: function (e) {
            var that = this,
            treeview = that.treeview,
            dropPosition = "over",
            sourceNode = that.sourceNode,
            destinationNode,
            dropHint = that.dropHint,
            valid, dropPrevented;

            if (dropHint.css(VISIBILITY) == "visible") {
                dropPosition = dropHint.prevAll(".k-in").length > 0 ? "after" : "before";
                destinationNode = dropHint.closest(NODE);
            } else if (that.dropTarget) {
                destinationNode = that.dropTarget.closest(NODE);
            }

            valid = that._hintStatus() != "k-denied";

            dropPrevented = treeview.trigger(DROP, {
                sourceNode: sourceNode[0],
                destinationNode: destinationNode[0],
                valid: valid,
                setValid: function(newValid) { valid = newValid; },
                dropTarget: e.target,
                dropPosition: dropPosition
            });

            dropHint.remove();

            if (!valid || dropPrevented) {
                that._draggable.dropped = valid;
                return;
            }

            that._draggable.dropped = true;

            // perform reorder / move
            if (dropPosition == "over") {
                treeview.append(sourceNode, destinationNode);
                treeview.expand(destinationNode);
            } else if (dropPosition == "before") {
                treeview.insertBefore(sourceNode, destinationNode);
            } else if (dropPosition == "after") {
                treeview.insertAfter(sourceNode, destinationNode);
            }

            treeview.trigger(DRAGEND, {
                sourceNode: sourceNode[0],
                destinationNode: destinationNode[0],
                dropPosition: dropPosition
            });
        }
    };

    // client-side rendering

    extend(TreeView, {
        renderItem: function (options) {
            options = extend({ treeview: {}, group: {} }, options);

            var empty = templates.empty,
                item = options.item,
                treeview = options.treeview;

            return templates.item(extend(options, {
                image: item.imageUrl ? templates.image : empty,
                sprite: item.spriteCssClass ? templates.sprite : empty,
                itemWrapper: templates.itemWrapper,
                toggleButton: item.items ? templates.toggleButton : empty,
                checkbox: treeview.checkboxTemplate ? templates.checkbox : empty,
                subGroup: function(treeview, group, item) {
                    return TreeView.renderGroup({
                        items: item.items,
                        treeview: treeview,
                        group: {
                           expanded: item.expanded
                        }
                    });
                }
            }, 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 += TreeView.renderItem(extend(options, {
                            group: group,
                            item: extend({ index: i }, items[i])
                        }));
                    }

                    return html;
                }
            }, options, rendering));
        }
    });

    rendering = /** @ignore */{
        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;
        },
        textAttributes: function(item) {
            return item.url ? " href='" + item.url + "'" : "";
        },
        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;
        },
        text: function(item) {
            return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
        },
        template: function(treeview, item) {
            return treeview.template(extend({ item: item }, rendering));
        },
        checkboxTemplate: function(treeview, group, item) {
            return treeview.checkboxTemplate(extend({
                treeview: treeview,
                group: group,
                item: item
            }, rendering));
        },
        tag: function(item) {
            return item.url ? "a" : "span";
        },
        groupAttributes: function(group) {
            return group.expanded !== true ? " style='display:none'" : "";
        },
        groupCssClass: function(group) {
            var cssClass = "k-group";

            if (group.firstLevel) {
                cssClass += " k-treeview-lines";
            }

            return cssClass;
        }
    };

    ui.plugin(TreeView);
})(jQuery);
/**
 * @fileOverview Provides a Slider and RangeSlider implementation which can be used to display a rich input for
 * selecting values or ranges of values.
 */

(function($, undefined) {
    /**
     *
     * @name kendo.ui.Slider.Description
     *
     * @section
     * <p>
     *  The <strong>Slider</strong> provides a rich input for selecting values or ranges of values. Unlike the HTML5
     *  range input, the <strong>Slider</strong> presents a consistent experience across browsers and features a rich
     *  API and event model.
     * </p>
     * <h3>Getting Started</h3>
     * <p>There are two types of <strong>Slider</strong>:</p>
     * <ol>
     *  <li><strong>Slider</strong>, which presents one thumb and two opposing buttons for selecting a single value</li>
     *  <li><strong>RangeSlider</strong>, which present two thumbs for defining a range of values</li>
     * </ol>
     * <h4>Slider</h4>
     *
     * @exampleTitle Create an input element
     * @example
     * <input id="slider" />
     *
     * @section
     * <p>
     *  Initialization of a <strong>Slider</strong> should occur after the DOM is fully loaded. It is recommended that
     *  initialization the <strong>Slider</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a Slider using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#slider").kendoSlider();
     * });
     *
     * @section
     * <h4>RangeSlider</h4>
     * @exampleTitle Create two HTML input elements in a div
     * @example
     * <div id="rangeSlider">
     *     <input />
     *     <input />
     * </div>
     *
     * @section
     * <p>
     *  Initialization of a <strong>RangeSlider</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>RangeSlider</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize a RangeSlider using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#rangeSlider").kendoRangeSlider();
     * });
     *
     * @section
     * <p>
     *  The <strong>RangeSlider</strong> requires two inputs to capture both ends of the value range. This benefits
     *  scenarios where JavaScript is disabled, in which case users will be presented with two inputs, still allowing
     *  them to input a valid range.
     * </p>
     * <h3>Customizing Slider Behaviors</h3>
     * <p>
     *  Many facets of the <strong>Slider</strong> and <strong>RangeSlider</strong> behavior can be configured through
     *  properties, including:
     * </p>
     * <ul>
     *  <li>Minimum and/or maximum values</li>
     *  <li>Orientation (horizontal or vertical)</li>
     *  <li>Small or large step</li>
     *  <li>Tooltip format/placement</li>
     * </ul>
     *
     * @exampleTitle Initialize a Slider and its properties
     * @example
     * $("#slider").kendoSlider({
     *     min: 10,
     *     max: 50,
     *     orientation: "vertical",
     *     smallStep: 1,
     *     largeStep: 10
     * });
     *
     * @section
     * <h3>Accessing an Existing Slider</h3>
     * <p>
     *  You can reference an existing <strong>Slider</strong> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Slider instance
     * @example
     * var slider = $("#slider").data("kendoSlider");
     *
     */
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        Draggable = kendo.ui.Draggable,
        keys = kendo.keys,
        extend = $.extend,
        format = kendo.format,
        parse = kendo.parseFloat,
        proxy = $.proxy,
        math = Math,
        touch = kendo.support.touch,
        CHANGE = "change",
        SLIDE = "slide",
        MOUSE_DOWN = touch ? "touchstart" : "mousedown",
        MOUSE_UP = touch ? "touchend" : "mouseup",
        MOVE_SELECTION = "moveSelection",
        KEY_DOWN = "keydown",
        MOUSE_OVER = "mouseover",
        DRAG_HANDLE = ".k-draghandle",
        TRACK_SELECTOR = ".k-slider-track",
        TICK_SELECTOR = ".k-tick",
        STATE_SELECTED = "k-state-selected",
        STATE_DEFAULT = "k-state-default",
        STATE_DISABLED = "k-state-disabled",
        PRECISION = 3,
        DISABLED = "disabled";

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

            Widget.fn.init.call(that, element, options);

            options = that.options;

            that._distance = options.max - options.min;
            that._isHorizontal = options.orientation == "horizontal";
            that._position = that._isHorizontal ? "left" : "bottom";
            that._size = 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._size]();

            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._setItemsLargeTick();
            }

            that._calculateSteps(pixelWidths);

            that[options.enabled ? "enable" : "disable"]();

            that._keyMap = {
                37: step(-options.smallStep), // left arrow
                40: step(-options.smallStep), // down arrow
                39: step(+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: [
            /**
            * Fires when the slider value changes as a result of selecting a new value with the drag handle, buttons or keyboard.
            * @name kendo.ui.Slider#change
            * @event
            * @param {Event} e
            * @param {Number} e.value Represents the updated value of the slider.
            **/

            /**
            * Fires when the rangeSlider value changes as a result of selecting a new value with one of the drag handles or the keyboard.
            * @name kendo.ui.RangeSlider#change
            * @event
            * @param {Event} e
            * @param {Number} e.values Represents the updated array of values of the first and second drag handle.
            **/
            CHANGE,

            /**
            * Fires when the user drags the drag handle to a new position.
            * @name kendo.ui.Slider#slide
            * @event
            * @param {Event} e
            * @param {Number} e.value Represents the value from the current position of the drag handle.
            **/

            /**
            * Fires when the user drags the drag handle to a new position.
            * @name kendo.ui.RangeSlider#slide
            * @event
            * @param {Event} e
            * @param {Number} e.values Represents an array of values of the current positions of the first and second drag handle.
            **/
            SLIDE
        ],

        options: {
            enabled: true,
            min: 0,
            max: 10,
            smallStep: 1,
            largeStep: 5,
            orientation: "horizontal",
            tickPlacement: "both",
            tooltip: { enabled: true, format: "{0}" }
        },

        _setTrackDivWidth: function() {
            var that = this,
                trackDivPosition = parseFloat(that._trackDiv.css(that._position), 10) * 2;

            that._trackDiv[that._size]((that.wrapper[that._size]() - 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._size](pixelWidths[i]);
            }

            if (that._isHorizontal) {
                $(items[first]).addClass("k-first")[that._size](pixelWidths[last - 1]);
                $(items[last]).addClass("k-last")[that._size](pixelWidths[last]);
            } else {
                $(items[last]).addClass("k-first")[that._size](pixelWidths[last]);
                $(items[first]).addClass("k-last")[that._size](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,
                i = that._isHorizontal ? 0 : items.length - 1,
                limit = that._isHorizontal ? items.length : -1,
                increment = that._isHorizontal ? 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,
                i,
                items = that.wrapper.find(TICK_SELECTOR),
                item = {},
                step = round(options.largeStep / options.smallStep);

            if ((1000 * options.largeStep) % (1000 * options.smallStep) == 0) {
                if (that._isHorizontal) {
                    for (i = 0; i < items.length; i = round(i + step)) {
                        item = $(items[i]);

                        item.addClass("k-tick-large")
                            .html("<span class='k-label'>" + item.attr("title") + "</span>");
                    }
                } else {
                    for (i = items.length - 1; i >= 0; i = round(i - step)) {
                        item = $(items[i]);

                        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._size]() + "px");
                        }
                    }
                }
            }
        },

        _calculateItemsWidth: function(itemsCount) {
            var that = this,
                options = that.options,
                trackDivSize = parseFloat(that._trackDiv.css(that._size)) + 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;

            for (i = 0; i < pixelWidthsArray.length; 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))] += 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;
                that._values[i] = val += options.smallStep;

                i++;
            }

            lastItem = that._distance % options.smallStep == 0 ? itemsCount - 1 : itemsCount;

            that._pixelSteps[lastItem] = that._maxSelection;
            that._values[lastItem] = options.max;
        },

        _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),
                val = 0,
                i;

            if (that._isHorizontal) {
                position = mousePosition - dragableArea.startPoint;
            } else {
                position = dragableArea.startPoint - mousePosition;
            }

            if (that._maxSelection - ((parseInt(that._maxSelection % step) - 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]);
                }
            }
        },

        _getDragableArea: function() {
            var that = this,
                offsetLeft = that._trackDiv.offset().left,
                offsetTop = that._trackDiv.offset().top;

            return {
                startPoint: that._isHorizontal ? offsetLeft : offsetTop + that._maxSelection,
                endPoint: that._isHorizontal ? offsetLeft + that._maxSelection : offsetTop
            };
        },

        _createHtml: function() {
            var that = this,
                element = that.element,
                options = that.options,
                inputs = element.find("input");

            if (inputs.length == 2) {
                inputs.eq(0).val(options.selectionStart);
                inputs.eq(1).val(options.selectionEnd);
            } else {
                element.val(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(element));
        }
    });

    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-arrow-next" : "k-arrow-up";
        } else {
            buttonCssClass = isHorizontal ? "k-arrow-prev" : "k-arrow-down";
        }

        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;

        for(i = 0; i < count; i++) {
            result += "<li class='k-tick'>&nbsp;</li>";
        }

        result += "</ul>";

        return result;
    }

    function createTrack (element) {
        var dragHandleCount = element.is("input") ? 1 : 2;

        return "<div class='k-slider-track'><div class='k-slider-selection'><!-- --></div>" +
               "<a href='javascript:void(0)' class='k-draghandle' title='Drag'>Drag</a>" +
               (dragHandleCount > 1 ? "<a href='javascript:void(0)' class='k-draghandle' title='Drag'>Drag</a>" : "") +
               "</div>";
    }

    function step(step) {
        return function (value) {
            return value + step;
        }
    }

    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) {
        return parse(element.getAttribute(name)) || undefined;
    }

    var Slider = SliderBase.extend(/** @lends kendo.ui.Slider.prototype */{
        /**
         *
         * Creates a Slider instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Boolean} [enabled] <true>
         * Disables (<strong>false</strong>) or enables (<strong>true</strong>) the <strong>Slider</strong>. This
         * option can be specified during initialization.
         *
         * @option {Number} [min] <0>
         * The minimum value of the <strong>Slider</strong>.
         *
         * @option {Number} [max] <10>
         * The maximum value of the <strong>Slider</strong>.
         *
         * @option {Boolean} [showButtons] <true>
         * Can be used to show (<b>true</b>) or hide (<b>false</b>) the
         * increase and decrease buttons of a <strong>Slider</strong>.
         *
         * @option {Object} [tooltip]
         * Configuration of the <strong>Slider</strong> tooltip.
         *
         * @option {Boolean} [tooltip.enabled] <true>
         * Disables (<b>false</b>) or enables (<b>true</b>) the tooltip of
         * the <strong>Slider</strong>.
         *
         * @option {String} [tooltip.format] <"{0}">
         * Format string for the text of the tooltip. Note: The applied
         * format will also influence the appearance of the <strong>Slider</strong>
         * tick labels.
         *
         * @option {Number} [value] <0>
         * The underlying value of the <strong>Slider</strong>.
         *
         * @option {String} [orientation] <"horizontal">
         * The orientation of a <strong>Slider</strong>; <strong>"horizontal"</strong> or <strong>"vertical"</strong>.
         *
         * @option {String} [tickPlacement] <"both">
         * Denotes the location of the tick marks in the <strong>Slider</strong>. The available options are:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <code>"topLeft"</code>
         *         </dt>
         *         <dd>
         *              Tick marks are located on the top of the horizontal widget or on the left of
         *   the vertical widget.
         *         </dd>
         *         <dt>
         *              <code>"bottomRight"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are located on the bottom of the horizontal widget or on the
         *   right side of the vertical widget.
         *         </dd>
         *         <dt>
         *              <code>"both"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are located on both sides of the widget.
         *         </dd>
         *         <dt>
         *              <code>"none"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are not visible.
         *         </dd>
         *    </dl>
         * </div>
         * @option {Number} [smallStep] <1>
         * The small step value of the <strong>Slider</strong>. The underlying value will be changed when the end user
         * (1) clicks on the increase or decrease buttons of the <strong>Slider</strong>, (2) presses the arrow keys
         * (the drag handle must be focused), or (3) drags the drag handle.
         *
         * @option {Number} [largeStep] <5>
         * The delta with which the value will change when the user presses the Page Up or Page Down key (the drag
         * handle must be focused). Note: The allied largeStep will also set large tick for every large step.
         *
         * @option {String} [increaseButtonTitle] <"Increase">
         * The title of the increase button of the <strong>Slider</strong>.
         *
         * @option {String} [decreaseButtonTitle] <"Decrease">
         * The title of the decrease button of the <strong>Slider</strong>.
         *
         */
        init: function(element, options) {
            var that = this,
                dragHandle;

            element.type = "text";

            element = $(element);

            options = extend({}, {
                value: parseAttr(element[0], "value"),
                min: parseAttr(element[0], "min"),
                max: parseAttr(element[0], "max"),
                smallStep: parseAttr(element[0], "step")
            }, options);

            SliderBase.fn.init.call(that, element, options);
            options = that.options;

            that._setValueInRange(options.value);
            dragHandle = that.wrapper.find(DRAG_HANDLE);

            new Slider.Selection(dragHandle, that, options);
            that._drag = new Slider.Drag(dragHandle, "", that, options);
        },

        options: {
            name: "Slider",
            value: 0,
            showButtons: true,
            increaseButtonTitle: "Increase",
            decreaseButtonTitle: "Decrease",
            tooltip: { template: "#= value #" }
        },

        /**
         *
         * Enables a <strong>Slider</strong> for handling user interactions (i.e. mouse click).
         *
         * @example
         * var slider = $("#slider").data("kendoSlider");
         * slider.enable();
         *
         */
        enable: function (enable) {
            var that = this,
                options = that.options,
                clickHandler,
                move;

            if (enable === false) {
                that.disable();
                return;
            }

            that.wrapper
                .removeClass(STATE_DISABLED)
                .addClass(STATE_DEFAULT);

            that.wrapper.find("input").removeAttr(DISABLED);

            clickHandler = function (e) {
                if ($(e.target).hasClass("k-draghandle")) {
                    $(e.target).addClass(STATE_SELECTED);
                    return;
                }

                var location = kendo.touchLocation(e),
                    mousePosition = that._isHorizontal ? location.x : location.y,
                    dragableArea = that._getDragableArea();

                that._update(that._getValueFromPosition(mousePosition, dragableArea));

                that._drag.dragstart(e);
            };

            that.wrapper
                .find(TICK_SELECTOR).bind(MOUSE_DOWN, clickHandler)
                .end()
                .find(TRACK_SELECTOR).bind(MOUSE_DOWN, clickHandler);

            that.wrapper.find(DRAG_HANDLE).bind(MOUSE_UP, function (e) {
                $(e.target).removeClass(STATE_SELECTED);
            });

            move = proxy(function (sign) {
                that._setValueInRange(that._nextValueByIndex(that._valueIndex + (sign * 1)));
            }, that);

            if (options.showButtons) {
                var mouseDownHandler = proxy(function(e, sign) {
                    if (e.which == 1 || (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")
                    .bind(MOUSE_UP, proxy(function (e) {
                        this._clearTimer();
                    }, that))
                    .bind(MOUSE_OVER, function (e) {
                        $(e.currentTarget).addClass("k-state-hover");
                    })
                    .bind("mouseout", proxy(function (e) {
                        $(e.currentTarget).removeClass("k-state-hover");
                        this._clearTimer();
                    }, that))
                    .eq(0)
                    .bind(MOUSE_DOWN, proxy(function (e) {
                        mouseDownHandler(e, 1);
                    }, that))
                    .click(false)
                    .end()
                    .eq(1)
                    .bind(MOUSE_DOWN, proxy(function (e) {
                        mouseDownHandler(e, -1);
                    }, that))
                    .click(false);
            }

            that.wrapper
                .find(DRAG_HANDLE).bind(KEY_DOWN, proxy(this._keydown, that));

            options.enabled = true;
        },

        /**
         *
         * Disables a <strong>Slider</strong> from handling user interactions (i.e. mouse click).
         *
         * @example
         * var slider = $("#slider").data("kendoSlider");
         * slider.disable();
         *
         */
        disable: function () {
            var that = this;

            that.wrapper
                .removeClass(STATE_DEFAULT)
                .addClass(STATE_DISABLED);

            $(that.element).attr(DISABLED, DISABLED);

            that.wrapper
                .find(".k-button")
                .unbind(MOUSE_DOWN)
                .bind(MOUSE_DOWN, false)
                .unbind(MOUSE_UP)
                .bind(MOUSE_UP, false)
                .unbind("mouseleave")
                .bind("mouseleave", false)
                .unbind(MOUSE_OVER)
                .bind(MOUSE_OVER, false);

            that.wrapper
                .find(TICK_SELECTOR).unbind(MOUSE_DOWN)
                .end()
                .find(TRACK_SELECTOR).unbind(MOUSE_DOWN);

            that.wrapper
                .find(DRAG_HANDLE)
                .unbind(MOUSE_UP)
                .unbind(KEY_DOWN)
                .bind(KEY_DOWN, false);

            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 });
            }
        },

        /**
         *
         * Gets or sets the value of a <strong>Slider</strong>. It accepts a string or number as parameters and returns
         * a number representing the underlying value.
         *
         * @param {String} [value]
         * The value to be set for a Slider.
         *
         * @example
         * var slider = $("#slider").data("kendoSlider");
         * var sliderValue = slider.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.attr("value", formatValue(value));
                    options.value = value;
                    that.refresh();
                }
            }
        },

        refresh: function () {
            this.trigger(MOVE_SELECTION, { value: this.options.value });
        },

        _clearTimer: function (e) {
            clearTimeout(this.timeout);
            clearInterval(this.timer);
        },

        _keydown: function (e) {
            var that = this;

            if (e.keyCode in that._keyMap) {
                that._setValueInRange(that._keyMap[e.keyCode](that.options.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;
            return this._values[math.max(0, math.min(index, count - 1))];
        }
    });

    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]),
                selectionDiv = that._trackDiv.find(".k-slider-selection"),
                halfDragHanndle = parseInt(dragHandle[that._outerSize]() / 2, 10);

            selectionDiv[that._size](selection);
            dragHandle.css(that._position, selection - halfDragHanndle);
        }

        moveSelection(options.value);

        that.bind([CHANGE, SLIDE, MOVE_SELECTION], function (e) {
            moveSelection(parseFloat(e.value, 10));
        });
    };

    Slider.Drag = function (dragHandle, type, owner, options) {
        var that = this;
        that.owner = owner;
        that.options = options;
        that.dragHandle = dragHandle;
        that.dragHandleSize = dragHandle[owner._outerSize]();
        that.type = type;

        that.draggable = new Draggable(dragHandle, {
            threshold: 0,
            dragstart: proxy(that._dragstart, that),
            drag: proxy(that.drag, that),
            dragend: proxy(that.dragend, that),
            dragcancel: proxy(that.dragcancel, that)
        });

        dragHandle.click(false);
    };

    Slider.Drag.prototype = {
        dragstart: function(e) {
            // HACK to initiate click on the line
            this.draggable.drag._start(e);
        },

        _dragstart: function(e) {
            var that = this,
                owner = that.owner,
                options = that.options,
                tooltip = options.tooltip,
                html = '',
                tooltipTemplate,
                formattedSelectionStart,
                formattedSelectionEnd;

            if (!options.enabled) {
                return false;
            }

            owner.element.unbind(MOUSE_OVER);
            that.dragHandle.addClass(STATE_SELECTED);

            that.dragableArea = owner._getDragableArea();
            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;
            }

            if (tooltip.enabled) {
                if (tooltip.template) {
                    tooltipTemplate = that.tooltipTemplate = kendo.template(tooltip.template);
                }

                that.tooltipDiv = $("<div class='k-widget k-tooltip'><!-- --></div>").appendTo(document.body);

                if (that.type) {
                    if (that.tooltipTemplate) {
                        html = tooltipTemplate({
                            selectionStart: that.selectionStart,
                            selectionEnd: that.selectionEnd
                        });
                    } else {
                        formattedSelectionStart = format(tooltip.format, that.selectionStart);
                        formattedSelectionEnd = format(tooltip.format, that.selectionEnd);

                        html = formattedSelectionStart + ' - ' + formattedSelectionEnd;
                    }
                } else {
                    that.tooltipInnerDiv = "<div class='k-callout k-callout-" + (owner._isHorizontal ? 's' : 'e') + "'><!-- --></div>";
                    if (that.tooltipTemplate) {
                        html = tooltipTemplate({
                            value: that.val
                        });
                    } else {
                        html = format(tooltip.format, that.val);
                    }
                    html += that.tooltipInnerDiv;
                }

                that.tooltipDiv.html(html);

                that.moveTooltip();
            }
        },

        drag: function (e) {
            var that = this,
                owner = that.owner,
                options = that.options,
                x = e.x.location,
                y = e.y.location,
                startPoint = that.dragableArea.startPoint,
                endPoint = that.dragableArea.endPoint,
                tooltip = options.tooltip,
                html = "",
                tooltipTemplate = that.tooltipTemplate,
                slideParams,
                formattedSelectionStart,
                formattedSelectionEnd;

            e.preventDefault();

            if (owner._isHorizontal) {
                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] };
                } else {
                    slideParams = { value: that.val };
                }

                owner.trigger(SLIDE, slideParams);

                if (tooltip.enabled) {
                    if (that.type) {
                        if (that.tooltipTemplate) {
                            html = tooltipTemplate({
                                selectionStart: that.selectionStart,
                                selectionEnd: that.selectionEnd
                        });
                        } else {
                            formattedSelectionStart = format(tooltip.format, that.selectionStart);
                            formattedSelectionEnd = format(tooltip.format, that.selectionEnd);
                            html = formattedSelectionStart + " - " + formattedSelectionEnd;
                        }
                    } else {
                        if (that.tooltipTemplate) {
                            html = tooltipTemplate({
                                value: that.val
                            });
                        } else {
                            html = format(tooltip.format, that.val);
                        }

                        html += that.tooltipInnerDiv;
                    }
                    that.tooltipDiv.html(html);
                    that.moveTooltip();
                }
            }
        },

        dragcancel: function(e) {
            this.owner.refresh();
            return this._end();
        },

        dragend: function(e) {
            var that = this,
                owner = that.owner;

            if (that.type) {
                owner._update(that.selectionStart, that.selectionEnd);
            } else {
                owner._update(that.val);
            }

            return that._end();
        },

        _end: function() {
            var that = this,
                owner = that.owner;

            if (owner.options.tooltip.enabled) {
                that.tooltipDiv.remove();
            }

            that.dragHandle.removeClass(STATE_SELECTED);
            owner.element.bind(MOUSE_OVER);

            return false;
        },

        moveTooltip: function () {
            var that = this,
                owner = that.owner,
                positionTop = 0,
                positionLeft = 0,
                dragHandleOffset = that.dragHandle.offset(),
                margin = 4,
                callout = that.tooltipDiv.find(".k-callout"),
                padding;

            if (that.type) {
                var dragHandles = owner.wrapper.find(DRAG_HANDLE),
                    firstDragHandleOffset = dragHandles.eq(0).offset(),
                    secondDragHandleOffset = dragHandles.eq(1).offset();

                if (owner._isHorizontal) {
                    positionTop = secondDragHandleOffset.top;
                    positionLeft = firstDragHandleOffset.left + ((secondDragHandleOffset.left - firstDragHandleOffset.left) / 2);
                } else {
                    positionTop = firstDragHandleOffset.top + ((secondDragHandleOffset.top - firstDragHandleOffset.top) / 2);
                    positionLeft = secondDragHandleOffset.left;
                }
            } else {
                positionTop = dragHandleOffset.top;
                positionLeft = dragHandleOffset.left;
            }
            if (owner._isHorizontal) {
                positionLeft -= parseInt((that.tooltipDiv.outerWidth() - that.dragHandle[owner._outerSize]()) / 2);
                positionTop -= that.tooltipDiv.outerHeight() + callout.height() + margin;
            } else {
                positionTop -= parseInt((that.tooltipDiv.outerHeight() - that.dragHandle[owner._outerSize]()) / 2);
                positionLeft -= that.tooltipDiv.outerWidth() + callout.width() + margin;
            }

            that.tooltipDiv.css({ top: positionTop, left: positionLeft });
        },

        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(/** @lends kendo.ui.RangeSlider.prototype */{
        /**
         *
         * Creates a RangeSlider instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Boolean} [enabled] <true>
         * Disables (<strong>false</strong>) or enables (<strong>true</strong>) the <strong>RangeSlider</strong>. This
         * option can be specified during initialization.
         *
         * @option {Number} [min] <0>
         * The minimum value of the <strong>RangeSlider</strong>.
         *
         * @option {Number} [max] <10>
         * The maximum value of the <strong>RangeSlider</strong>.
         *
         * @option {Object} [tooltip]
         * Configuration of the <strong>RangeSlider</strong> tooltip.
         *
         * @option {Boolean} [tooltip.enabled] <true>
         * Disables (<b>false</b>) or enables (<b>true</b>) the tooltip of the <strong>RangeSlider</strong>.
         *
         * @option {String} [tooltip.format] <"{0}">
         * Format string for the text of the tooltip. Note: The applied format will also influence the appearance of
         * the <strong>RangeSlider</strong> tick labels.
         *
         * @option {String} [orientation] <"horizontal">
         * The orientation of a <strong>RangeSlider</strong>; <strong>"horizontal"</strong> or
         * <strong>"vertical"</strong>.
         *
         * @option {String} [tickPlacement] <"both">
         * Denotes the location of the tick marks in the <strong>RangeSlider</strong>. The available options are:
         * <div class="details-list">
         *    <dl>
         *         <dt>
         *              <code>"topLeft"</code>
         *         </dt>
         *         <dd>
         *              Tick marks are located on the top of the horizontal widget or on the left of
         *   the vertical widget.
         *         </dd>
         *         <dt>
         *              <code>"bottomRight"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are located on the bottom of the horizontal widget or on the
         *   right side of the vertical widget.
         *         </dd>
         *         <dt>
         *              <code>"both"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are located on both sides of the widget.
         *         </dd>
         *         <dt>
         *              <code>"none"</code>
         *         </dt>
         *         <dd>
         *             Tick marks are not visible.
         *         </dd>
         *    </dl>
         * </div>
         *
         * @option {Number} [smallStep] <1>
         * The small step value of the <strong>RangeSlider</strong>. The underlying value will be changed when the end
         * user (1) clicks on the increase or decrease buttons of the <strong>RangeSlider</strong>, (2) presses the
         * arrow keys (the drag handle must be focused), or (3) drags the drag handle.
         *
         * @option {Number} [largeStep] <5>
         * The delta with which the value will change when the user presses the Page Up or Page Down key (the drag
         * handle must be focused). Note: The allied largeStep will also set large tick for every large step.
         *
         * @option {Number} [selectionStart] <0>
         * The selection start value of the <strong>RangeSlider</strong>.
         *
         * @option {Number} [selectionEnd] <10>
         * The selection end value of the <strong>RangeSlider</strong>.
         *
         */
        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);

            SliderBase.fn.init.call(that, element, options);
            options = that.options;
            that._setValueInRange(options.selectionStart, options.selectionEnd);

            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",
            selectionStart: 0,
            selectionEnd: 10,
            tooltip: { template: "#= selectionStart # - #= selectionEnd #" }
        },

        /**
         *
         * Enables a <strong>RangeSlider</strong> for handling user interactions (i.e. mouse click).
         *
         * @example
         * var rangeSlider = $("#rangeSlider").data("kendoRangeSlider");
         * rangeSlider.enable();
         *
         */
        enable: function (enable) {
            var that = this,
                options = that.options,
                clickHandler;

            if (enable === false) {
                that.disable();
                return;
            }

            that.wrapper
                .removeClass(STATE_DISABLED)
                .addClass(STATE_DEFAULT);

            that.wrapper.find("input").removeAttr(DISABLED);

            clickHandler = function (e) {
                if ($(e.target).hasClass("k-draghandle")) {
                    $(e.target).addClass(STATE_SELECTED);
                    return;
                }

                var location = kendo.touchLocation(e),
                    mousePosition = that._isHorizontal ? location.x : location.y,
                    dragableArea = that._getDragableArea(),
                    val = that._getValueFromPosition(mousePosition, dragableArea);

                if (val < options.selectionStart) {
                    that._setValueInRange(val, options.selectionEnd);
                    that._firstHandleDrag.dragstart(e);
                } else if (val > that.selectionEnd) {
                    that._setValueInRange(options.selectionStart, val);
                    that._lastHandleDrag.dragstart(e);
                } else {
                    if (val - options.selectionStart <= options.selectionEnd - val) {
                        that._setValueInRange(val, options.selectionEnd);
                        that._firstHandleDrag.dragstart(e);
                    } else {
                        that._setValueInRange(options.selectionStart, val);
                        that._lastHandleDrag.dragstart(e);
                    }
                }
            };

            that.wrapper
                .find(TICK_SELECTOR).bind(MOUSE_DOWN, clickHandler)
                .end()
                .find(TRACK_SELECTOR).bind(MOUSE_DOWN, clickHandler);

            that.wrapper.find(DRAG_HANDLE).bind(MOUSE_UP, function (e) {
                $(e.target).removeClass(STATE_SELECTED);
            });

            that.wrapper.find(DRAG_HANDLE)
                .eq(0).bind(KEY_DOWN,
                    proxy(function(e) {
                        this._keydown(e, "firstHandle");
                    }, that)
                )
                .end()
                .eq(1).bind(KEY_DOWN,
                    proxy(function(e) {
                        this._keydown(e, "lastHandle");
                    }, that)
                );

            that.options.enabled = true;
        },

        /**
         *
         * Disables a <strong>RangeSlider</strong> from handling user interactions (i.e. mouse click).
         *
         * @example
         * var rangeSlider = $("#rangeSlider").data("kendoRangeSlider");
         * rangeSlider.disable();
         *
         */
        disable: function () {
            var that = this,
                options = that.options;

            that.wrapper
                .removeClass(STATE_DEFAULT)
                .addClass(STATE_DISABLED);

            that.wrapper.find("input").attr(DISABLED, DISABLED);

            that.wrapper
                .find(TICK_SELECTOR).unbind(MOUSE_DOWN)
                .end()
                .find(TRACK_SELECTOR).unbind(MOUSE_DOWN);

            that.wrapper
                .find(DRAG_HANDLE)
                .unbind(MOUSE_UP)
                .unbind(KEY_DOWN)
                .bind(KEY_DOWN, false);

            that.options.enabled = false;
        },

        _keydown: function (e, handle) {
            var that = this,
                selectionStartValue = that.options.selectionStart,
                selectionEndValue = that.options.selectionEnd;

            if (e.keyCode in that._keyMap) {
                if (handle == "firstHandle") {
                    selectionStartValue = that._keyMap[e.keyCode](selectionStartValue);

                    if (selectionStartValue > selectionEndValue) {
                        selectionEndValue = selectionStartValue;
                    }
                } else {
                    selectionEndValue = that._keyMap[e.keyCode](selectionEndValue);

                    if (selectionStartValue > selectionEndValue) {
                        selectionStartValue = selectionEndValue;
                    }
                }

                that._setValueInRange(selectionStartValue, selectionEndValue);
                e.preventDefault();
            }
        },

        _update: function (selectionStart, selectionEnd) {
            var that = this,
                values = that.values();

            var change = values[0] != selectionStart || values[1] != selectionEnd;

            that.values(selectionStart, selectionEnd);

            if (change) {
                that.trigger(CHANGE, { values: [selectionStart, selectionEnd] });
            }
        },

        /**
         *
         * The value method gets or sets the start and end selection values of the <strong>RangeSlider</strong>. It
         * accepts a string, a number, or an array as parameters, and returns an object array with the start and end
         * selection values.
         *
         * @example
         * var rangeSider = $("#rangeSlider").data("kendoRangeSlider");
         * rangeSlider.values();
         *
         */
        values: function () {
            var that = this,
                options = that.options,
                selectionStart = 0,
                selectionEnd = 0;

            if (arguments.length == 0) {
                return [options.selectionStart, options.selectionEnd];
            } else if (arguments.length == 1 && $.isArray(arguments[0])) {
                selectionStart = arguments[0][0];
                selectionEnd = arguments[0][1];
            } else {
                selectionStart = round(arguments[0]);
                selectionEnd = round(arguments[1]);
            }

            if (selectionStart >= options.min && selectionStart <= options.max
            && selectionEnd >= options.min && selectionEnd <= options.max && selectionStart <= selectionEnd) {
                if (options.selectionStart != selectionStart || options.selectionEnd != selectionEnd) {
                    that.element.find("input")
                                .eq(0).attr("value", formatValue(selectionStart))
                                .end()
                                .eq(1).attr("value", formatValue(selectionEnd));

                    options.selectionStart = selectionStart;
                    options.selectionEnd = selectionEnd;
                    that.refresh();
                }
            }
        },

        refresh: function() {
            var that = this,
                options = that.options;

            that.trigger(MOVE_SELECTION, { values: [options.selectionStart, options.selectionEnd] });

            if (options.selectionStart == options.max && options.selectionEnd == options.max) {
                that._setZIndex("firstHandle");
            }
        },

        _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);
            });
        }
    });

    RangeSlider.Selection = function (dragHandles, that, options) {
        function moveSelection(values) {
            var selectionStartValue = values[0] - options.min,
                selectionEndValue = values[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);

            dragHandles.eq(0).css(that._position, selectionStart - halfHandle)
                       .end()
                       .eq(1).css(that._position, selectionEnd - halfHandle);

            makeSelection(selectionStart, selectionEnd);
        }

        function makeSelection(selectionStart, selectionEnd) {
            var selection = 0,
                selectionPosition = 0,
                selectionDiv = that._trackDiv.find(".k-slider-selection");

            selection = math.abs(selectionStart - selectionEnd);
            selectionPosition = selectionStart < selectionEnd ? selectionStart : selectionEnd;

            selectionDiv[that._size](selection);
            selectionDiv.css(that._position, selectionPosition - 1);
        }

        moveSelection(that.values());

        that.bind([ CHANGE, SLIDE, MOVE_SELECTION ], function (e) {
            moveSelection(e.values);
        });
    };

    kendo.ui.plugin(RangeSlider);

})(jQuery);
/**
 * @fileOverview Provides a Splitter implementation which can be used to display a dynamic layout of resizable and
 * collapsible panes.
 */

(function ($, undefined) {
    /**
     * @name kendo.ui.Splitter.Description
     *
     * @section
     * <p>
     *  The <strong>Splitter</strong> provides a dynamic layout of resizable and collapsible panes. It converts the
     *  children of an HTML element in to the interactive layout, adding resize and collapse handles based on
     *  configuration. A <strong>Splitter</strong> can be mixed in a vertical or horizontal orientation to build
     *  complex layouts.
     * </p>
     * <h3>Getting Started</h3>
     * <p>
     *  The layout and structure of a <strong>Splitter</strong> is defined within the DOM as a div with child elements.
     * </p>
     *
     * @exampleTitle Create a div with children that will become panes
     * @example
     * <div id="splitter">
     *     <div>Area 1</div>
     *     <div>Area 2</div>
     * </div>
     *
     * @section
     * <p>
     *  Initialization of a <strong>Splitter</strong> should occur after the DOM is fully loaded. It is recommended
     *  that initialization the <strong>Splitter</strong> occur within a handler is provided to $(document).ready().
     * </p>
     *
     * @exampleTitle Initialize the Splitter using a selector within $(document).ready()
     * @example
     * $(document).ready(function() {
     *     $("#splitter").kendoSplitter();
     * });
     *
     * @section
     * <p>
     *  When the <strong>Splitter</strong> is initialized, a vertical split bar will be placed between the two div
     *  elements. This bar can be moved by a user left and right to adjust the size on the panes.
     * </p>
     * <h3>Configuring Splitter Behaviors</h3>
     * <p>
     *  The <strong>Splitter</strong> has a default configuration specified during initialization. However, these
     *  options may be overriden to control the following properties:
     * </p>
     * <ul>
     *  <li>Maximum and/or minimum pane sizes</li>
     *  <li>Resizable and collapsible/expandable pane behaviors</li>
     *  <li>Orientation (horizontal or vertical)</li>
     * </ul>
     * <p>
     *  The properties of a pane must be set during initialization and set for each individual pane in a
     *  <strong>Splitter</strong>.
     * </p>
     *
     * @exampleTitle Initialize a Splitter and the properties of its panes
     * @example
     * $("#splitter").kendoSplitter({
     *     panes: [
     *         { collapsible: true, min: "100px", max: "300px" },
     *         { collapsible: true }
     *     ],
     *     orientation: "vertical"
     * });
     *
     * @section
     * <h3>Nested Splitter Layouts</h3>
     * <p>To achieve complex layouts, the <strong>Splitter</strong> supports nested layouts.</p>
     *
     * @exampleTitle Creating nested Splitter layout
     * @example
     * <div id="horizontalSplitter">
     *     <div><p>Left Side Pane Content</p></div>
     *     <div>
     *         <div id="verticalSplitter">
     *             <div><p>Right Side, Top Pane Content</p></div>
     *             <div><p>Right Side, Bottom Pane Content</p></div>
     *         </div>
     *     </div>
     * </div>
     *
     * @exampleTitle Initialize two Splitters with differing orientations
     * @example
     * $("horizontalSplitter").kendoSplitter();
     * $("verticalSplitter").kendoSplitter({ orientation: "vertical" });
     *
     * @section
     * <h3>Loading Content with AJAX</h3>
     * <p>
     *  While any valid technique for loading content via AJAX may be used, <strong>Splitter</strong> provides built-in
     *  support for asynchronously loading content from URLs. These URLs should return HTML fragments that can be
     *  loaded in the pane of a <strong>Splitter</strong>. If you want to load a whole page in an IFRAME, you may do so
     *  by specifying the complete URL (i.e. http://kendoui.com/).
     * </p>
     *
     * @exampleTitle Loading Splitter content asynchronously
     * @example
     * <div id="splitter">
     *     <div>Area 1 with Static Content</div>
     *     <div></div>
     *     <div></div>
     * </div>
     *
     * @exampleTitle Initialize Splitter; configure async loading for one pane; and an iframe for a third pane
     * @example
     * $(document).ready(function() {
     *     $("#splitter").kendoSplitter({
     *         panes: [
     *             {},
     *             { contentUrl: "html-content-snippet.html" },
     *             { contentUrl: "http://kendoui.com/" }
     *         ]
     *     });
     * });
     *
     * @section
     * <h3>Accessing an Existing Splitter</h3>
     * <p>
     *  You can reference an existing <strong>Splitter</strong> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Splitter instance
     * @example
     * var splitter = $("#splitter").data("kendoSplitter");
     *
     */
    var kendo = window.kendo,
        ui = kendo.ui,
        extend = $.extend,
        proxy = $.proxy,
        Widget = ui.Widget,
        pxUnitsRegex = /^\d+(\.\d+)?px$/i,
        percentageUnitsRegex = /^\d+(\.\d+)?%$/i,
        EXPAND = "expand",
        COLLAPSE = "collapse",
        CONTENTLOAD = "contentLoad",
        RESIZE = "resize",
        LAYOUTCHANGE = "layoutChange",
        HORIZONTAL = "horizontal",
        VERTICAL = "vertical",
        MOUSEENTER = "mouseenter",
        CLICK = "click",
        PANE = "pane",
        MOUSELEAVE = "mouseleave",
        KPANE = "k-" + PANE,
        PANECLASS = "." + KPANE;

    function isPercentageSize(size) {
        return percentageUnitsRegex.test(size);
    }

    function isPixelSize(size) {
        return pxUnitsRegex.test(size);
    }

    function isFluid(size) {
        return !isPercentageSize(size) && !isPixelSize(size);
    }

    function panePropertyAccessor(propertyName, triggersResize) {
        return function(pane, value) {
            var paneConfig = $(pane).data(PANE);

            if (arguments.length == 1) {
                return paneConfig[propertyName];
            }

            paneConfig[propertyName] = value;

            if (triggersResize) {
                var splitter = this.element.data("kendoSplitter");
                splitter.trigger(RESIZE);
            }
        };
    }

    var Splitter = Widget.extend(/** @lends kendo.ui.Splitter.prototype */ {
        /**
         * Creates a Splitter instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {String} [orientation] <horizontal>
         * Specifies the orientation of the <strong>Splitter</strong>.
         * <div class="details-list">
         *  <dl>
         *   <dt>"horizontal"</dt>
         *   <dd>Define horizontal orientation of the splitter.</dd>
         *   <dt>"vertical"</dt>
         *   <dd>Define vertical orientation of the splitter.</dd>
         *  </dl>
         * </div>
         *
         * @option {Array} [panes]
         * An array of pane definitions.
         *
         * _example
         * $("#splitter").kendoSplitter({
         *     panes: [
         *         { size: "200px", min: "100px", max: "300px" },
         *         { size: "20%", resizable: false },
         *         { collapsed: true, collapsible: true }
         *     ]
         * });
         *
         * @option {String} [panes.size]
         * Specifies the size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). Note: This
         * value must not exceed <strong>panes.max</strong> or be less then <strong>panes.min</strong>.
         *
         * @option {String} [panes.min]
         * Specifies the minimum size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). The
         * size of a resized pane cannot be less than the defined minimum size.
         *
         * @option {String} [panes.max]
         * Specifies the maximum size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). The
         * size of a resized pane cannot exceed the defined maximum size.
         *
         * @option {Boolean} [panes.collapsed] <false>
         * Specifies whether a pane is initially collapsed (<strong>true</strong>) or expanded (<strong>true</strong>).
         *
         * @option {Boolean} [panes.collapsible] <false>
         * Specifies whether a pane is collapsible (<strong>true</strong>) or not collapsible (<strong>false</strong>).
         *
         * @option {Boolean} [panes.scrollable] <true>
         * Specifies whether a pane is scrollable (<strong>true</strong>) or not scrollable (<strong>false</strong>).
         *
         * @option {Boolean} [panes.resizable] <true>
         * Specifies whether a pane is resizable (<strong>true</strong>) or not resizable (<strong>false</strong>).
         *
         * @option {Boolean} [panes.contentUrl] <true>
         * Specifies the URL from which to load the content of a pane.
         *
         */
        init: function(element, options) {
            var that = this,
                panesConfig,
                triggerResize = function() {
                    that.trigger(RESIZE);
                };

            Widget.fn.init.call(that, element, options);

            that.wrapper = that.element;

            that.orientation = that.options.orientation.toLowerCase() != VERTICAL ? HORIZONTAL : VERTICAL;
            splitbarSelector = ".k-splitbar-draggable-" + that.orientation;

            that.bind(RESIZE, proxy(that._resize, that));

            that._initPanes();

            that._attachEvents();

            $(window).resize(triggerResize);

            that.resizing = new PaneResizing(that);
        },
        events: [
            /**
            * Triggered when a pane of a Splitter is expanded.
            *
            * @name kendo.ui.Splitter#expand
            * @event
            *
            * @param {Event} e
            *
            * @param {Element} e.pane
            * The expanding pane of the Splitter.
            *
            * @exampleTitle Attach expand event handler during initialization; detach via unbind()
            * @example
            * // event handler for expand
            * var onExpand = function(e) {
            *     // access the expanded item via e.pane (HTMLElement)
            * };
            *
            * // attach expand event handler during initialization
            * var splitter = $("#splitter").kendoSplitter({
            *     expand: onExpand
            * });
            *
            * // detach expand event handler via unbind()
            * splitter.data("kendoSplitter").unbind("expand", onExpand);
            *
            * @exampleTitle Attach expand event handler via bind(); detach via unbind()
            * @example
            * // event handler for expand
            * var onExpand = function(e) {
            *     // access the expanded item via e.pane (HTMLElement)
            * };
            *
            * // attach expand event handler via bind()
            * $("#splitter").data("kendoSplitter").bind("expand", onExpand);
            *
            * // detach expand event handler via unbind()
            * $("#splitter").data("kendoSplitter").unbind("expand", onExpand);
            *
            */
            EXPAND,

            /**
            * Triggered when a pane of a Splitter is collapsed.
            *
            * @name kendo.ui.Splitter#collapse
            * @event
            *
            * @param {Event} e
            * @param {Element} e.pane
            * The collapsing pane of the Splitter.
            *
            * @exampleTitle Attach expand event handler during initialization; detach via unbind()
            * @example
            * // event handler for expand
            * var onCollapse = function(e) {
            *     // access the collapsed item via e.pane (HTMLElement)
            * };
            *
            * // attach collapse event handler during initialization
            * var splitter = $("#splitter").kendoSplitter({
            *     collapse: onCollapse
            * });
            *
            * // detach collapse event handler via unbind()
            * splitter.data("kendoSplitter").unbind("collapse", onCollapse);
            *
            * @exampleTitle Attach collapse event handler via bind(); detach via unbind()
            * @example
            * // event handler for collapse
            * var onExpand = function(e) {
            *     // access the collapsed item via e.pane (HTMLElement)
            * };
            *
            * // attach collapse event handler via bind()
            * $("#splitter").data("kendoSplitter").bind("collapse", onCollapse);
            *
            * // detach collapse event handler via unbind()
            * $("#splitter").data("kendoSplitter").unbind("collapse", onCollapse);
            *
            */
            COLLAPSE,

            /**
            * Triggered when the content for a pane has finished loading.
            *
            * @name kendo.ui.Splitter#contentLoad
            * @event
            *
            * @param {Event} e
            *
            * @param {HTMLElement} e.pane
            * The pane whose content has been loaded.
            *
            * @exampleTitle Attach contentLoad event handler during initialization; detach via unbind()
            * @example
            * // event handler for contentLoad
            * var onContentLoad = function(e) {
            *     // access the loaded pane via e.pane (HTMLElement)
            * };
            *
            * // attach contentLoad event handler during initialization
            * var splitter = $("#splitter").kendoSplitter({
            *     contentLoad: onContentLoad
            * });
            *
            * // detach contentLoad event handler via unbind()
            * splitter.data("kendoSplitter").unbind("contentLoad", onContentLoad);
            *
            * @exampleTitle Attach contentLoad event handler via bind(); detach via unbind()
            * @example
            * // event handler for contentLoad
            * var onContentLoad = function(e) {
            *     // access the loaded pane via e.pane (HTMLElement)
            * };
            *
            * // attach contentLoad event handler via bind()
            * $("#splitter").data("kendoSplitter").bind("contentLoad", onContentLoad);
            *
            * // detach contentLoad event handler via unbind()
            * $("#splitter").data("kendoSplitter").unbind("contentLoad", onContentLoad);
            *
            */
            CONTENTLOAD,

            /**
            * Triggered when a pane is resized.
            *
            * @name kendo.ui.Splitter#resize
            * @event
            * @param {Event} e
            *
            * @exampleTitle Attach resize event handler during initialization; detach via unbind()
            * @example
            * // event handler for resize
            * var onResize = function(e) {
            *     // ...
            * };
            *
            * // attach resize event handler during initialization
            * var splitter = $("#splitter").kendoSplitter({
            *     resize: onResize
            * });
            *
            * // detach resize event handler via unbind()
            * splitter.data("kendoSplitter").unbind("resize", onResize);

            * @exampleTitle Attach resize event handler via bind(); detach via unbind()
            * @example
            * // event handler for resize
            * var onResize = function(e) {
            *     // ...
            * };
            *
            * // attach resize event handler via bind()
            * $("#splitter").data("kendoSplitter").bind("resize", onResize);
            *
            * // detach resize event handler via unbind()
            * $("#splitter").data("kendoSplitter").unbind("resize", onResize);
            *
            */
            RESIZE,

            /**
            * Fires when the splitter layout has changed
            *
            * @name kendo.ui.Splitter#layoutChange
            * @event
            *
            * @exampleTitle Attach layoutChange event handler during initialization; detach via unbind()
            * @example
            * // event handler for resize
            * var onLayoutChange = function(e) {
            *     // ...
            * };
            *
            * $("#splitter").kendoSplitter({
            *     layoutChange: onLayoutChange
            * });
            *
            * @exampleTitle Attach layoutChange event handler via bind(); detach via unbind()
            * @example
            * // event handler for layoutChange
            * var onLayoutChange = function(e) {
            *     // ...
            * };
            *
            * // attach layoutChange event handler via bind()
            * $("#splitter").bind("layoutChange", onLayoutChange);
            *
            * // detach layoutChange event handler via unbind()
            * $("#splitter").unbind("layoutChange", onLayoutChange);
            *
            */
            LAYOUTCHANGE
        ],

        _attachEvents: function() {
            var that = this,
                orientation = that.options.orientation,
                splitbarSelector = ".k-splitbar-draggable-" + orientation,
                expandCollapseSelector = ".k-splitbar .k-icon:not(.k-resize-handle)",
                triggerResize = function() {
                    that.trigger(RESIZE);
                };

            that.element
                .delegate(splitbarSelector, MOUSEENTER, function() { $(this).addClass("k-splitbar-" + that.orientation + "-hover"); })
                .delegate(splitbarSelector, MOUSELEAVE, function() { $(this).removeClass("k-splitbar-" + that.orientation + "-hover"); })
                .delegate(splitbarSelector, "mousedown", function() { that._contentFrames(this).after("<div class='k-overlay' />"); })
                .delegate(splitbarSelector, "mouseup", function() { that._contentFrames(this).next(".k-overlay").remove(); })
                .delegate(expandCollapseSelector, MOUSEENTER, function() { $(this).addClass("k-state-hover")})
                .delegate(expandCollapseSelector, MOUSELEAVE, function() { $(this).removeClass('k-state-hover')})
                .delegate(".k-splitbar .k-collapse-next, .k-splitbar .k-collapse-prev", CLICK, that._arrowClick(COLLAPSE))
                .delegate(".k-splitbar .k-expand-next, .k-splitbar .k-expand-prev", CLICK, that._arrowClick(EXPAND))
                .delegate(".k-splitbar", "dblclick", proxy(that._dbclick, that))
                .parent().closest(".k-splitter").each(function() {
                    $(this).data("kendoSplitter").bind(RESIZE,  triggerResize);
                });
        },

        options: {
            name: "Splitter",
            orientation: HORIZONTAL
        },

        _initPanes: function() {
            var that = this,
                panesConfig = that.options.panes || [];

            that.element
                .addClass("k-widget").addClass("k-splitter")
                .children()
                    .addClass(KPANE)
                    .each(function (index, pane) {
                        var config = panesConfig && panesConfig[index];

                        pane = $(pane);

                        pane.data(PANE, config ? config : {})
                            .toggleClass("k-scrollable", config ? config.scrollable !== false : true);
                        that.ajaxRequest(pane);
                    })
                .end();
            that.trigger(RESIZE);
        },

        /**
         * Loads the content of a pane from a local or remote URL.
         *
         * @param {Selector | DOM Element} pane
         * The targetted pane whose content is to be loaded via a URL.
         *
         * @param {String} url
         * A local or remote URL from which the content of the pane is to be loaded.
         *
         * @param {Object | String} data
         * Any data that is necessary to be sent to the server.
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // load content into the pane with ID, pane1
         * splitter.ajaxRequest("#pane1", "/customer/profile", { id: 42 });
         *
         */
        ajaxRequest: function(pane, url, data) {
            pane = $(pane);

            var that = this,
                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)) {
                    $.ajax({
                        url: url,
                        data: data || {},
                        type: "GET",
                        dataType: "html",
                        success: function (data) {
                            pane.html(data);

                            that.trigger(CONTENTLOAD, { pane: pane[0] });
                        }
                    });
                } 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]);
            }
        },
        _dbclick: 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)
                .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)
                );
        },
        _updateSplitBars: function() {
            var that = this;

            this.element.children(".k-splitbar").each(function() {
                var splitbar = $(this),
                    previousPane = splitbar.prev(PANECLASS).data(PANE),
                    nextPane = splitbar.next(PANECLASS).data(PANE);

                if (!nextPane) {
                    return;
                }

                that._updateSplitBar(splitbar, previousPane, nextPane);
            });
        },
        _contentFrames: function(splitbar) {
            return $(splitbar).siblings(PANECLASS).find("> .k-content-frame");
        },
        _resize: function() {
            var that = this,
                element = that.element,
                panes = element.children(":not(.k-splitbar)"),
                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 class='k-splitbar' />");
                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 config = $(this).data(PANE) || {}, size;

                    if (config.collapsed) {
                        size = 0;
                    } else if (isFluid(config.size)) {
                        freeSizedPanes = freeSizedPanes.add(this);
                        return;
                    } else { // sized in px/%, not collapsed
                        size = parseInt(config.size, 10);

                        if (isPercentageSize(config.size)) {
                            size = Math.floor(size * totalSize / 100);
                        }
                    }

                    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) {
                    child.style[positioningProperty] = Math.floor(sum) + "px";
                    sum += child[sizingDomProperty];
                });

            that.trigger(LAYOUTCHANGE);
        },

        /**
         * Toggles the state of a specified pane (i.e. collapsed or expanded). Invoking this method will force the
         * <strong>Splitter</strong> to redraw and it will trigger layoutChange and resize events. Note: Invoking the
         * method will not trigger collapse or expand events.
         *
         * @param {Selector | DOM Element} pane
         * The pane to be collapsed.
         *
         * @param {Boolean} expand (Optional)
         * Represents the desired state of the specified pane; to be expanded (<strong>true</strong>) or collapsed
         * (<strong>false</strong>). If undefined, toggle() will collapse the pane if it is expanded or will expand the
         * pane if it is collapsed.
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // toggle the state of the pane with ID, pane1
         * splitter.toggle("#pane1");
         * // toggle the state of the pane with ID, pane1 to be expanded
         * splitter.toggle("#pane1", true);
         * // toggle the state of the pane with ID, pane1 to be collapsed
         * splitter.toggle("#pane1", false);
         *
         */
        toggle: function(pane, expand) {
            var pane = $(pane),
                paneConfig = pane.data(PANE);

            if (arguments.length == 1) {
                expand = paneConfig.collapsed === undefined ? false : paneConfig.collapsed;
            }

            paneConfig.collapsed = !expand;

            this.trigger(RESIZE);
        },

        /**
         * Collapses a specified pane. Invoking this method will force the <strong>Splitter</strong> to redraw and it
         * will trigger layoutChange and resize events. Note: Invoking the method will not trigger a collapse event.
         *
         * @param {Selector | DOM Element} pane
         * The pane to be collapsed.
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // collapse the pane with ID, pane1
         * splitter.collapse("#pane1");
         *
         */
        collapse: function(pane) {
            this.toggle(pane, false);
        },

        /**
         * Expands a specified pane. Invoking this method will force the <strong>Splitter</strong> to redraw and it
         * will trigger layoutChange and resize events. Note: Invoking the method will not trigger an expand event.
         *
         * @param {Selector | DOM Element} pane
         * The pane to be expanded.
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // expand the pane with ID, pane1
         * splitter.expand("#pane1");
         *
         */
        expand: function(pane) {
            this.toggle(pane, true);
        },

        /**
         * Set the size of the pane. Setting this value will cause the <strong>Splitter</strong> to redraw and it will
         * trigger layoutChange and resize events.
         *
         * @name kendo.ui.Splitter#size
         * @function
         *
         * @param {Selector | DOM Element} pane
         * The pane to be resized.
         *
         * @param {String} value
         * The new size of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). Note: This value
         * must not exceed <strong>panes.max</strong> or be less then <strong>panes.min</strong>.
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // set the size of the pane with ID, pane1
         * splitter.size("#pane1", "200px");
         *
         */
        size: panePropertyAccessor("size", true),

        /**
         * Sets the minimum size of a pane. Setting this value will not cause the <strong>Splitter</strong> to
         * redraw, nor will it trigger any events.
         *
         * @name kendo.ui.Splitter#min
         * @function
         *
         * @param {Selector | DOM Element} pane
         * The pane being targetted for a new minimum size configuration value.
         *
         * @param {String} value
         * The minimum size value of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%").
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // set the minimum size of the pane with ID, pane1
         * splitter.min("#pane1", "100px");
         *
         */
        min: panePropertyAccessor("min"),

        /**
         * Sets the maximum size of a pane. Setting this value will not cause the <strong>Splitter</strong> to
         * redraw, nor will it trigger any events.
         *
         * @name kendo.ui.Splitter#max
         * @function
         *
         * @param {Selector | DOM Element} pane
         * The pane being targetted for a new minimum size configuration value.
         *
         * @param {String} value
         * The maximum size value of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%").
         *
         * @example
         * // get a reference to the splitter
         * var splitter = $("#splitter").data("kendoSplitter");
         * // set the maximum size of the pane with ID, pane1
         * splitter.max("#pane1", "300px");
         *
         */
        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,
            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 = {
        _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]),
                nextBoundary = parseInt(nextPane[0].style[that.positioningProperty]) + nextPane[0][that.sizingDomProperty] - splitbar[0][that.sizingDomProperty],
                totalSize = that._element.css(that.sizingProperty),
                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(e) {
              return this._maxPosition;
        },
        _min: function(e) {
            return this._minPosition;
        },
        _stop: function(e) {
            var that = this,
                splitbar = $(e.currentTarget),
                owner = that.owner;

            owner._contentFrames(splitbar).next(".k-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]),
                    nextPaneNewSize = parseInt(nextPane[0].style[that.positioningProperty]) + 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.trigger(RESIZE);
            }

            return false;
        }
    }

})(jQuery);
(function($, undefined) {
    /**
     * @name kendo.ui.Upload.Description
     *
     * @section
     * <p>
     *  An <strong>Upload</strong> uses progressive enhancement to deliver the best possible uploading experience to
     *  users without requiring extra developer effort. Features highlights:
     * </p>
     * <ul>
     *  <li>Asynchronous and synchronous (on form submit) file upload</li>
     *  <li>Multiple file selection</li>
     *  <li>Removing uploaded files</li>
     *  <li>Progress tracking *</li>
     *  <li>File drag-and-drop *</li>
     *  <li>Cancelling upload in-progress *</li>
     * </ul>
     * <p>(*) These features are automatically enabled if supported by the browser.</p>
     * <p>An <strong>Upload</strong> is a standards-based widget; no plug-ins required.</p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle 1. Create a simple HTML form and input element of type "file"
     * @example
     * <!-- Kendo will automatically set the form enctype attribute to "multi-part/form-data" -->
     * <form method="post" action="handler.php">
     *     <div>
     *         <input name="photos[]" id="photos" type="file" />
     *     </div>
     * </form>
     *
     * @exampleTitle 2. Initialize Upload with a jQuery selector
     * @example
     * $(document).ready(function() {
     *     $("#photos").kendoUpload();
     * });
     *
     * @section
     * <p>
     *  Note the array syntax in the input name; it is used to hint the upload handler to treat photos as an array.
     * </p>
     * <p>
     *  Please consult the documentation of your specific server technology regarding the handling of uploaded files.
     * </p>
     * <h3>See Also</h3>
     * <p><a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx">Upload Modes</a></p>
     * <h3>Accessing an Existing Upload</h3>
     * <p>
     *  You can reference an existing <b>Upload</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Upload instance
     * @example
     * var upload = $("#upload").data("kendoUpload");
     *
     */
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        logToConsole = kendo.logToConsole,
        rFileExtension = /\.([^\.]+)$/,
        SELECT = "select",
        UPLOAD = "upload",
        SUCCESS = "success",
        ERROR = "error",
        COMPLETE = "complete",
        CANCEL = "cancel",
        LOAD = "load",
        REMOVE = "remove";

    var Upload = Widget.extend(/** @lends kendo.ui.Upload.prototype */{
        /**
         *
         * Creates an Upload instance.
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element DOM element
         * @param {Object} options Configuration options.
         *
         * @option {Boolean} [enabled] <true>
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) an <strong>Upload</strong>. A disabled
         * <strong>Upload</strong> may be re-enabled via enable().
         *
         * @option {Boolean} [multiple] <true>
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability to select multiple files.
         * If <strong>false</strong>, users will be able to select only one file at a time. Note: This option does not
         * limit the total number of uploaded files in an asynchronous configuration.
         *
         * @option {Boolean} [showFileList] <true>
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability to display a file listing
         * for uploading a file(s). Disabling a file listing may be useful you wish to customize the UI; use the
         * client-side events to build your own UI.
         *
         * @option {Object} [async]
         * Configures the ability to upload a file(s) in an asynchronous manner. Please refer to the
         * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode help topic</a>
         * for more details.
         *
         * @option {String} [async.saveUrl]
         * The URL of the handler that will receive the submitted files. The handler must accept POST requests
         * containing one or more fields with the same name as the original input name.
         *
         * @option {String} [async.saveField]
         * The name of the form field submitted to the save URL. The default value is the input name.
         *
         * @option {String} [async.removeUrl]
         * The URL of the handler responsible for removing uploaded files (if any). The handler must accept POST
         * requests containing one or more "fileNames" fields specifying the files to be deleted.
         *
         * @option {String} [async.removeVerb] <"DELETE">
         * The HTTP verb to be used by the remove action.
         *
         * @option {String} [async.removeField] <"fileNames">
         * The name of the form field submitted to the Remove URL.
         *
         * @option {Boolean} [async.autoUpload] <"fileNames">
         * The selected files will be uploaded immediately by default. You can change this behavior by setting
         * autoUpload to false.
         *
         * @option {Object} [localization]
         * Sets the strings rendered by the Upload.
         *
         * @option {String} [localization.select]
         * Sets the "Select..." button text.
         *
         * @option {String} [localization.cancel]
         * Sets the text of the cancel button text.
         *
         * @option {String} [localization.retry]
         * Sets the text of the retry button text.
         *
         * @option {String} [localization.remove]
         * Sets the text of the remove button text.
         *
         * @option {String} [localization.uploadSelectedFiles]
         * Sets the text of the "Upload files" button.
         *
         * @option {String} [localization.dropFilesHere] <"drop files here to upload">
         * Sets the drop zone hint.
         *
         * @option {String} [localization.statusUploading]
         * Sets the status message for files that are being uploaded.
         *
         * @option {String} [localization.statusUploaded]
         * Sets the status message for uploaded files.
         *
         * @option {String} [localization.statusFailed]
         * Sets the status message for failed uploads.
         *
         */
        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);

            activeInput.closest("form").bind({
                "submit": $.proxy(that._onParentFormSubmit, that),
                "reset": $.proxy(that._onParentFormReset, that)
            });

            if (that.options.async.saveUrl != undefined) {
                that._module = that._supportsFormData() ?
                new formDataUploadModule(that) :
                new iframeUploadModule(that);
            } else {
                that._module = new syncUploadModule(that);
            }

            if (that._supportsDrop()) {
                that._setupDropZone();
            }

            that.wrapper
            .delegate(".k-upload-action", "click", $.proxy(that._onFileAction, that))
            .delegate(".k-upload-selected", "click", $.proxy(that._onUploadSelected, that))
            .delegate(".k-file", "t:progress", $.proxy(that._onFileProgress, that))
            .delegate(".k-file", "t:upload-success", $.proxy(that._onUploadSuccess, that))
            .delegate(".k-file", "t:upload-error", $.proxy(that._onUploadError, that));
        },

        events: [
            /**
            *
            * Triggered when a file(s) is selected. Note: Cancelling this event will prevent the selection from
            * occurring.
            *
            * @name kendo.ui.Upload#select
            * @event
            *
            * @param {Event} e
            *
            * @param {Array} e.files
            * An array of the selected files.
            *
            * <ul>
            *     <li>name - the name of a selected file, including its extension</li>
            *     <li>extension - the file extension of a selected file, including the leading dot (i.e. ".jpg")</li>
            *     <li>size - the size (in bytes) of a selected file (null, if unavailable)</li>
            *     <li>rawFile - an in-memory representation of a selected file</li>
            * </ul>
            *
            * @exampleTitle Wire-up an event handler that triggered when a user selects a file(s)
            * @example
            * var onSelect = function(e) {
            *     $.each(e.files, function(index, value) {
            *         console.log("Name: " + value.name);
            *         console.log("Size: " + value.size + " bytes");
            *         console.log("Extension: " + value.extension);
            *     });
            * };
            *
            * // initialize and configure an Upload widget with a select event handler
            * $("#photos").kendoUpload({
            *     // ...
            *     select: onSelect
            * });
            *
            */
            SELECT,

            /**
            * Fires when one or more files are about to be uploaded.
            * Cancelling the event will prevent the upload.
            * <p>
            * Note: The upload event fires only when the upload is in
            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
            * </p>
            * @name kendo.ui.Upload#upload
            * @event
            * @param {Event} e
            * @param {Array} e.files
            * List of the files that will be uploaded. Each file has:
            * <ul>
            *     <li>name</li>
            *     <li>
            *         extension - the file extension
            *         inlcuding the leading dot - ".jpg", ".png", etc.
            *      </li>
            *     <li>size - the file size in bytes (null if not available)</li>
            * </ul>
            * @param {Object} data Optional object that will be
            * sent to the save handler in the form of key/value pairs.
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     upload: onUpload
            * });
            *
            * function onUpload(e) {
            *     // Array with information about the uploaded files
            *     var files = e.files;
            *
            *     // Check the extension of each file and abort the upload if it is not .jpg
            *     $.each(files, function() {
            *         if (this.extension != ".jpg") {
            *             alert("Only .jpg files can be uploaded")
            *             e.preventDefault();
            *         }
            *     });
            * }
            */
            UPLOAD,

            /**
            * Fires when an upload / remove operation has been completed successfully.
            * <p>
            * Note: The success event fires only when the upload is in
            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
            * </p>
            * @name kendo.ui.Upload#success
            * @event
            * @param {Event} e
            * @param {Array} e.files
            * List of the files that were uploaded or removed . Each file has:
            * <ul>
            *     <li>name</li>
            *     <li>
            *         extension - the file extension
            *         inlcuding the leading dot - ".jpg", ".png", etc.
            *      </li>
            *     <li>size - the file size in bytes (null if not available)</li>
            * </ul>
            * @param {String} e.operation "upload" or "remove".
            * @param {String} e.response the response object returned by the server.
            * @param {Object} e.XMLHttpRequest
            * This is either the original XHR used for the operation or a stub containing:
            * <ul>
            *     <li>responseText</li>
            *     <li>status</li>
            *     <li>statusText</li>
            * </ul>
            * Verify that this is an actual XHR before accessing any other fields.
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     success: onSuccess
            * });
            *
            * function onSuccess(e) {
            *     // Array with information about the uploaded files
            *     var files = e.files;
            *
            *     if (e.operation == "upload") {
            *         alert("Successfully uploaded " + files.length + " files");
            *     }
            * }
            */
            SUCCESS,

            /**
            * Fires when an upload / remove operation has failed.
            * <p>
            * Note: The error event fires only when the upload is in
            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
            * </p>
            * @name kendo.ui.Upload#error
            * @event
            * @param {Event} e
            * @param {Array} e.files
            * List of the files that were uploaded or removed . Each file has:
            * <ul>
            *     <li>name</li>
            *     <li>
            *         extension - the file extension
            *         inlcuding the leading dot - ".jpg", ".png", etc.
            *      </li>
            *     <li>size - the file size in bytes (null if not available)</li>
            * </ul>
            * @param {String} e.operation - "upload" or "remove".
            * @param {Object} e.XMLHttpRequest
            * This is either the original XHR used for the operation or a stub containing:
            * <ul>
            *     <li>responseText</li>
            *     <li>status</li>
            *     <li>statusText</li>
            * </ul>
            * Verify that this is an actual XHR before accessing any other fields.
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     error: onError
            * });
            *
            * function onError(e) {
            *     // Array with information about the uploaded files
            *     var files = e.files;
            *
            *     if (e.operation == "upload") {
            *         alert("Failed to uploaded " + files.length + " files");
            *     }
            *
            *     // Suppress the default error message
            *     e.preventDefault();
            * }
            */
            ERROR,

            /**
            * Fires when all active uploads have completed either successfully or with errors.
            * <p>
            * Note: The complete event fires only when the upload is in
            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
            * </p>
            * @name kendo.ui.Upload#complete
            * @event
            * @param {Event} e
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     complete: onComplete
            * });
            *
            * function onComplete(e) {
            *     // The upload is now idle
            * }
            */
            COMPLETE,

            /**
            * Fires when the upload has been cancelled while in progress.
            * <p>
            * Note: The cancel event fires only when the upload is in
            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
            * </p>
            * @name kendo.ui.Upload#cancel
            * @event
            * @param {Event} e
            * @param {Array} e.files
            * List of the files that were uploaded or removed . Each file has:
            * <ul>
            *     <li>name</li>
            *     <li>
            *         extension - the file extension
            *         inlcuding the leading dot - ".jpg", ".png", etc.
            *      </li>
            *     <li>size - the file size in bytes (null if not available)</li>
            * </ul>
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     cancel: onCancel
            * });
            *
            * function onCancel(e) {
            *     // Array with information about the uploaded files
            *     var files = e.files;
            *
            *     // Process the Cancel event
            * }
            */
            CANCEL,

            /**
            * Fires when an uploaded file is about to be removed.
            * Cancelling the event will prevent the remove.
            * @name kendo.ui.Upload#remove
            * @event
            * @param {Event} e
            * @param {Array} e.files
            * List of the files that were uploaded or removed . Each file has:
            * <ul>
            *     <li>name</li>
            *     <li>
            *         extension - the file extension
            *         inlcuding the leading dot - ".jpg", ".png", etc.
            *      </li>
            *     <li>size - the file size in bytes (null if not available)</li>
            * </ul>
            * @param {Object} data Optional object that will be
            * sent to the save handler in the form of key/value pairs.
            * @example
            * $("#photos").kendoUpload({
            *     // ...
            *     remove: onRemove
            * });
            *
            * function onRemove(e) {
            *     // Array with information about the removed files
            *     var files = e.files;
            *
            *     // Process the Remove event
            *     // Optionally cancel the remove operation by calling
            *     // e.preventDefault()
            * }
            */
        REMOVE],
        options: {
            name: "Upload",
            enabled: true,
            multiple: true,
            showFileList: true,
            async: {
                removeVerb: "POST",
                autoUpload: true
            },
            localization: {
                "select": "Select...",
                "cancel": "Cancel",
                "retry": "Retry",
                "remove": "Remove",
                "uploadSelectedFiles": "Upload files",
                "dropFilesHere": "drop files here to upload",
                "statusUploading": "uploading",
                "statusUploaded": "uploaded",
                "statusFailed": "failed"
            }
        },

        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);
        },

        /**
         * Enables the upload.
         * @example
         * var upload = $("#upload").data("kendoUpload");
         *
         * // enables the upload
         * upload.enable();
         */
        enable: function(enable) {
            enable = typeof (enable) === "undefined" ? true : enable;
            this.toggle(enable);
        },

        /**
         * Disables the upload.
         * @example
         * var upload = $("#upload").data("kendoUpload");
         *
         * // disables the upload
         * upload.enable();
         */
        disable: function() {
            this.toggle(false);
        },

        /**
         * Toggles the upload enabled state.
         * @param {Boolean} enable (Optional) The new enabled state.
         * @example
         * var upload = $("#upload").data("kendoUpload");
         *
         * // toggles the upload enabled state
         * upload.toggle();
         */
        toggle: function(enable) {
            enable = typeof (enable) === "undefined" ? enable : !enable;
            this.wrapper.toggleClass("k-state-disabled", enable);
        },

        _addInput: function(input) {
            var that = this;

            input
                .insertAfter(that.element)
                .data("kendoUpload", that);

            $(that.element)
                .hide()
                .removeAttr("id");

            that._activeInput(input);
        },

        _activeInput: function(input) {
            var that = this,
                wrapper = that.wrapper;

            that.element = input;

            input
                .attr("multiple", that._supportsMultiple() ? that.multiple : false)
                .attr("autocomplete", "off")
                .click(function(e) {
                    if (wrapper.hasClass("k-state-disabled")) {
                        e.preventDefault();
                    }
                })
                .change($.proxy(that._onInputChange, that));
        },

        _onInputChange: function(e) {
            var upload = this,
                input = $(e.target),
                prevented = upload.trigger(SELECT, { files: inputFiles(input) });

            if (prevented) {
                upload._addInput(input.clone().val(""));
                input.remove();
            } else {
                input.trigger("t:select");
            }
        },

        _onDrop: function (e) {
            var dt = e.originalEvent.dataTransfer,
                that = this,
                droppedFiles = dt.files;

            stopEvent(e);

            if (droppedFiles.length > 0) {
                var prevented = that.trigger(SELECT, { files: getAllFileInfo(droppedFiles) });
                if (!prevented) {
                    $(".k-dropzone", that.wrapper).trigger("t:select", [ droppedFiles ]);
                }
            }
        },

        _enqueueFile: function(name, data) {
            var that = this,
                existingFileEntries,
                fileEntry,
                fileList =  $(".k-upload-files", that.wrapper);

            if (fileList.length == 0) {
                fileList = $("<ul class='k-upload-files k-reset'></ul>").appendTo(that.wrapper);
                if (!that.options.showFileList) {
                    fileList.hide();
                }
            }

            existingFileEntries = $(".k-file", fileList);
            fileEntry =
                $("<li class='k-file'><span class='k-icon'></span><span class='k-filename' title='" + name + "'>" + name + "</span></li>")
                .appendTo(fileList)
                .data(data);

            if (!that.multiple) {
                existingFileEntries.trigger("t:remove");
            }

            return fileEntry;
        },

        _removeFileEntry: function(fileEntry) {
            var fileList = fileEntry.closest(".k-upload-files"),
                allFiles;

            fileEntry.remove();
            allFiles = $(".k-file", fileList);

            if (allFiles.find("> .k-fail").length === allFiles.length) {
                this._hideUploadButton();
            }

            if (allFiles.length == 0) {
                fileList.remove();
            }
        },

        _fileAction: function(fileElement, actionKey) {
            var classDictionary = { remove: "k-delete", cancel: "k-cancel", retry: "k-retry" };
            if (!classDictionary.hasOwnProperty(actionKey)) {
                return;
            }

            this._clearFileAction(fileElement);

            fileElement.append(
                this._renderAction(classDictionary[actionKey], this.localization[actionKey])
                .addClass("k-upload-action")
            );
        },

        _fileState: function(fileEntry, stateKey) {
            var localization = this.localization,
                states = {
                    uploading: {
                        cssClass: "k-loading",
                        text : localization.statusUploading
                    },
                    uploaded: {
                        cssClass: "k-success",
                        text : localization.statusUploaded
                    },
                    failed: {
                        cssClass: "k-fail",
                        text : localization.statusFailed
                    }
                },
                currentState = states[stateKey];

            if (currentState) {
                var icon = fileEntry.children(".k-icon").text(currentState.text);
                icon[0].className = "k-icon " + currentState.cssClass;
            }
        },

        _renderAction: function (actionClass, actionText) {
            if (actionClass != "") {
                return $(
                "<button type='button' class='k-button k-button-icontext'>" +
                    "<span class='k-icon " + actionClass + "'></span>" +
                    actionText +
                "</button>"
                )
            }
            else {
                return $(
                "<button type='button' class='k-button'>" +
                    actionText +
                "</button>"
                )
            }
        },

        _clearFileAction: function(fileElement) {
            fileElement
                .find(".k-upload-action").remove();
        },

        _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)) {
                        fileEntry.trigger("t:remove", eventArgs.data);
                    }
                } else if (icon.hasClass("k-cancel")) {
                    that.trigger(CANCEL, eventArgs);
                    fileEntry.trigger("t:cancel");
                    this._checkAllComplete();
                } else if (icon.hasClass("k-retry")) {
                    fileEntry.trigger("t:retry");
                }
            }

            return false;
        },

        _onUploadSelected: function() {
            this.wrapper.trigger("t:saveSelected");
            return false;
        },

        _onFileProgress: function(e, percentComplete) {
            var progressBar = $(".k-progress-status", e.target);
            if (progressBar.length == 0) {
                progressBar =
                    $("<span class='k-progress'><span class='k-progress-status' style='width: 0;'></span></span>")
                        .appendTo($(".k-filename", e.target))
                        .find(".k-progress-status");
            }

            progressBar.width(percentComplete + "%");
        },

        _onUploadSuccess: function(e, response, xhr) {
            var fileEntry = getFileEntry(e);

            this._fileState(fileEntry, "uploaded");

            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);

            this._fileState(fileEntry, "failed");
            this._fileAction(fileEntry, "retry");

            var prevented = this.trigger(ERROR, {
                operation: "upload",
                files: fileEntry.data("fileNames"),
                XMLHttpRequest: xhr
            });

            logToConsole("Server response: " + xhr.responseText);

            if (!prevented) {
                this._alert("Error! Upload failed. Unexpected server response - see console.");
            }

            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();
        },

        _onParentFormSubmit: function() {
            var upload = this,
                element = upload.element;
            element.trigger("t:abort");

            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() {
            return !$.browser.opera;
        },

        _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 != undefined);
        },

        _userAgent: function() {
            return navigator.userAgent;
        },

        _setupDropZone: function() {
            $(".k-upload-button", this.wrapper)
                .wrap("<div class='k-dropzone'></div>");

            var dropZone = $(".k-dropzone", this.wrapper)
                .append($("<em>" + this.localization["dropFilesHere"] + "</em>"))
                .bind({
                    "dragenter": stopEvent,
                    "dragover": function(e) { e.preventDefault(); },
                    "drop" : $.proxy(this._onDrop, this)
                });

            bindDragEventWrappers(dropZone,
                function() { dropZone.addClass("k-dropzone-hovered"); },
                function() { dropZone.removeClass("k-dropzone-hovered"); });

            bindDragEventWrappers($(document),
                function() { dropZone.addClass("k-dropzone-active"); },
                function() { dropZone.removeClass("k-dropzone-active"); });
        },

        _supportsRemove: function() {
            return this.options.async.removeUrl != undefined;
        },

        _submitRemove: function(fileNames, data, onSuccess, onError) {
            var upload = this,
                removeField = upload.options.async.removeField || "fileNames",
                params = $.extend(data, getAntiForgeryTokens());

            params[removeField] = fileNames;

            $.ajax({
                  type: this.options.async.removeVerb,
                  dataType: "json",
                  url: this.options.async.removeUrl,
                  traditional: true,
                  data: params,
                  success: onSuccess,
                  error: onError
            });
        },

        _alert: function(message) {
            alert(message);
        },

        _wrapInput: function(input) {
            input.wrap("<div class='k-widget k-upload'><div class='k-button k-upload-button'></div></div>");
            input.closest(".k-button")
                .append("<span>" + this.localization.select + "</span>");

            return input.closest(".k-upload");
        },

        _checkAllComplete: function() {
            if ($(".k-file .k-icon.k-loading", this.wrapper).length == 0) {
                this.trigger(COMPLETE);
            }
        }
    });

    // Synchronous upload module
    var syncUploadModule = function(upload) {
        this.name = "syncUploadModule";
        this.element = upload.wrapper;
        this.upload = upload;
        this.element
            .bind("t:select", $.proxy(this.onSelect, this))
            .bind("t:remove", $.proxy(this.onRemove, this))
            .closest("form")
                .attr("enctype", "multipart/form-data")
                .attr("encoding", "multipart/form-data");
    };

    syncUploadModule.prototype = /** @ignore */  {
        onSelect: function(e) {
            var upload = this.upload;
            var sourceInput = $(e.target);
            upload._addInput(sourceInput.clone().val(""));
            var file = upload._enqueueFile(getFileName(sourceInput), {
                "relatedInput" : sourceInput, "fileNames": inputFiles(sourceInput)
            });

            upload._fileAction(file, REMOVE);
        },

        onRemove: function(e) {
            var fileEntry = getFileEntry(e);
            fileEntry.data("relatedInput").remove();

            this.upload._removeFileEntry(fileEntry);
        }
    };

    // Iframe upload module
    var iframeUploadModule = function(upload) {
        this.name = "iframeUploadModule";
        this.element = upload.wrapper;
        this.upload = upload;
        this.iframes = [];
        this.element
            .bind("t:select", $.proxy(this.onSelect, this))
            .bind("t:cancel", $.proxy(this.onCancel, this))
            .bind("t:retry", $.proxy(this.onRetry, this))
            .bind("t:remove", $.proxy(this.onRemove, this))
            .bind("t:saveSelected", $.proxy(this.onSaveSelected, this))
            .bind("t:abort", $.proxy(this.onAbort, this));
    };

    Upload._frameId = 0;

    iframeUploadModule.prototype = /** @ignore */ {
        onSelect: function(e) {
            var upload = this.upload,
                sourceInput = $(e.target);

            var fileEntry = this.prepareUpload(sourceInput);

            if (upload.options.async.autoUpload) {
                this.performUpload(fileEntry);
            } else {
                if (upload._supportsRemove()) {
                    this.upload._fileAction(fileEntry, REMOVE);
                }

                upload._showUploadButton();
            }
        },

        prepareUpload: function(sourceInput) {
            var upload = this.upload;
            var activeInput = $(upload.element);
            var name = upload.options.async.saveField || sourceInput.attr("name");
            upload._addInput(sourceInput.clone().val(""));

            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": inputFiles(sourceInput) });

            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();

                iframe.appendTo(document.body);

                var form = iframe.data("form")
                    .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 })
                            .appendTo(form);
                    }
                    dataInput.val(e.data[key]);
                }

                upload._fileAction(fileEntry, CANCEL);
                upload._fileState(fileEntry, "uploading");

                iframe
                    .one("load", $.proxy(this.onIframeLoad, this));

                form[0].submit();
            } else {
                upload._removeFileEntry(iframe.data("file"));
                this.cleanupFrame(iframe);
                this.unregisterFrame(iframe);
            }
        },

        onSaveSelected: function(e) {
            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);

            try {
                var responseText = iframe.contents().text();
            } catch (e) {
                responseText = "Error trying to get server response: " + e;
            }

            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" });
                    fileEntry.trigger("t:upload-success", [ jsonResult, fakeXHR ]);
                    module.cleanupFrame(iframe);
                    module.unregisterFrame(iframe);
                },
                function() {
                    $.extend(fakeXHR, { statusText: "error", status: "500" });
                    fileEntry.trigger("t:upload-error", [ 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");
                frame.contentWindow.location.href = frame.contentWindow.location.href;
            }
        },

        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;
        this.element
            .bind("t:select", $.proxy(this.onSelect, this))
            .bind("t:cancel", $.proxy(this.onCancel, this))
            .bind("t:remove", $.proxy(this.onRemove, this))
            .bind("t:retry", $.proxy(this.onRetry, this))
            .bind("t:saveSelected", $.proxy(this.onSaveSelected, this))
            .bind("t:abort", $.proxy(this.onAbort, this));
    };

    formDataUploadModule.prototype = /** @ignore */ {
        onSelect: function(e, rawFiles) {
            var upload = this.upload,
                module = this,
                sourceElement = $(e.target),
                files = rawFiles ? getAllFileInfo(rawFiles) : this.inputFiles(sourceElement),
                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.clone().val(""));
            }

            return fileEntries;
        },

        enqueueFiles: function(arrFileInfo) {
            var upload = this.upload
                fileEntries = [];

            for (var i = 0; i < arrFileInfo.length; i++) {
                var currentFile = arrFileInfo[i],
                    name = currentFile.name;

                var fileEntry = upload._enqueueFile(name, { "fileNames": [ currentFile ] });
                fileEntry.data("formData", this.createFormData(arrFileInfo[i]));

                fileEntries.push(fileEntry);
            }

            return fileEntries;
        },

        inputFiles: function(sourceInput) {
            return inputFiles(sourceInput);
        },

        performUpload: function(fileEntry) {
            var upload = this.upload,
                formData = fileEntry.data("formData"),
                e = { files: fileEntry.data("fileNames") };

            if (!upload.trigger(UPLOAD, e)) {
                upload._fileAction(fileEntry, CANCEL);
                upload._hideUploadButton();

                e.data = $.extend({ }, e.data, getAntiForgeryTokens());
                for (var key in e.data) {
                    formData.append(key, e.data[key]);
                }

                upload._fileState(fileEntry, "uploading");

                this.postFormData(this.upload.options.async.saveUrl, formData, fileEntry);
            } else {
                this.removeFileEntry(fileEntry);
            }
        },

        onSaveSelected: function(e) {
            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.children(".k-icon").is(".k-success")) {
                removeUploadedFile(fileEntry, this.upload, data);
            } else {
                this.removeFileEntry(fileEntry);
            }
        },

        postFormData: function(url, data, fileEntry) {
            var xhr = new XMLHttpRequest(),
                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);
            xhr.send(data);
        },

        createFormData: function(fileInfo) {
            var formData = new FormData(),
            upload = this.upload;

            formData.append(upload.options.async.saveField || upload.name, fileInfo.rawFile);

            return formData;
        },

        onRequestSuccess: function(e, fileEntry) {
            var xhr = e.target,
                module = this;
            tryParseJSON(xhr.responseText,
                function(jsonResult) {
                    fileEntry.trigger("t:upload-success", [ jsonResult, xhr ]);
                    fileEntry.trigger("t:progress", [ 100 ]);
                    module.cleanupFileEntry(fileEntry);
                },
                function() {
                    fileEntry.trigger("t:upload-error", [ xhr ]);
                }
            );
        },

        onRequestError: function(e, fileEntry) {
            var xhr = e.target;
            fileEntry.trigger("t:upload-error", [ 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.children(".k-icon").is(".k-success");
                    }
                });

                if (uploadComplete) {
                    relatedInput.remove();
                }
            }

            fileEntry.data("formData", null);
        },

        removeFileEntry: function(fileEntry) {
            this.cleanupFileEntry(fileEntry);
            this.upload._removeFileEntry(fileEntry);
        },

        onRequestProgress: function(e, fileEntry) {
            var percentComplete = Math.round(e.loaded * 100 / e.total);
            fileEntry.trigger("t:progress", [ 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: 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 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, textStatus, textStatus) {
                var prevented = upload.trigger(ERROR, {
                    operation: "remove",
                    files: files,
                    XMLHttpRequest: xhr });

                logToConsole("Server response: " + xhr.responseText);

                if (!prevented) {
                    upload._alert("Error! Remove operation failed. Unexpected response - see console.");
                }
            }
        );
    }

    function tryParseJSON(input, onSuccess, onError) {
        try {
            var json = $.parseJSON(input);
            onSuccess(json);
        } catch (e) {
            onError();
        }
    }

    function stopEvent(e) {
        e.stopPropagation(); e.preventDefault();
    }

    function bindDragEventWrappers(element, onDragEnter, onDragLeave) {
        var hideInterval, lastDrag;

        element
            .bind("dragenter", function(e) {
                onDragEnter();
                lastDrag = new Date();

                if (!hideInterval) {
                    hideInterval = setInterval(function() {
                        var sinceLastDrag = new Date() - lastDrag;
                        if (sinceLastDrag > 100) {
                            onDragLeave();

                            clearInterval(hideInterval);
                            hideInterval = null;
                        }
                    }, 100);
                }
            })
            .bind("dragover", function(e) {
                lastDrag = new Date();
            });
    }

    function isFileUploadStarted(fileEntry) {
        return fileEntry.children(".k-icon").is(".k-loading, .k-success, .k-fail");
    }

    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);
})(jQuery);
/**
 * @fileOverview Provides a Window implementation which can be used to display content in a modal or non-modal HTML
 * window.
 */

(function($, undefined) {
    /**
     * @name kendo.ui.Window.Description
     *
     * @section
     * <p>
     *  A <strong>Window</strong> displays content in a modal or non-modal HTML window. By default, a
     *  <strong>Window</strong> can be moved, resized, and closed. Its content can also be defined with either as
     *  static HTML or loaded dynamically via AJAX.
     * </p>
     * <p>
     *  A <strong>Window</strong> can be initialized from virtually any DOM element. During initialization, the
     *  targeted content will automatically be wrapped in the div element of the <strong>Window</strong>.
     * </p>
     * <h3>Getting Started</h3>
     *
     * @exampleTitle Create a simple HTML element with the Window content
     * @example
     * <div id="window">
     *     Content of the Window
     * </div>
     *
     * @exampleTitle Initialize the Window using a selector
     * @example
     * $(document).ready(function() {
     *     $("#window").kendoWindow();
     * });
     *
     * @section
     * <p>
     *  When a <strong>Window</strong> is initialized, it will automatically be displayed open near the location of the
     *  DOM element that was used to initialize the content.
     * </p>
     * <h3>Configuring Window Behaviors</h3>
     * <p>
     *  A <strong>Window</strong> provides many configuration options that can be easily set during initialization.
     *  Among the properties that can be controlled:
     * </p>
     * <ul>
     *  <li>Minimum height/width</li>
     *  <li>Available user actions (close/refresh/maximize/minimize)</li>
     *  <li>Title</li>
     *  <li>Draggable and resizable behaviors</li>
     * </ul>
     *
     * @exampleTitle Create a modal Window with all user actions enabled
     * @example
     * $("#window").kendoWindow({
     *     actions: ["Refresh", "Maximize", "Minimize", "Close"],
     *     draggable: false,
     *     height: "300px",
     *     modal: true,
     *     resizable: false,
     *     title: "Modal Window",
     *     width: "500px"
     * });
     *
     * @section
     * <p>
     *  The order of the values in the actions array determines the order in which the action buttons will be rendered
     *  in the title of a <strong>Window</strong>. The maximize action serves both as a button for expanding a
     *  <strong>Window</strong> to fill the screen and as a button to restore a <strong>Window</strong> to its previous
     *  size. The minimize action collapses a <strong>Window</strong> to its title.
     * </p>
     * <h3>Positioning and Opening a Window</h3>
     * <p>
     *  In some scenarios, it is preferable to center a <strong>Window</strong> rather than open it near the HTML
     *  element used to define the content. It is also common to open a <strong>Window</strong> as the result of the
     *  action of a user rather than on the load event of a page. The <strong>Window</strong> API provides methods for
     *  handling these scenarios.
     * </p>
     *
     * @exampleTitle Centering a Window and opening on button click
     * @example
     * <p id="window">
     *     Content of the Window
     * </p>
     * <button id="openButton">Open Window</button>
     *
     * @exampleTitle Initialize Window, center, and configure button click action
     * @example
     * $(document).ready(function(){
     *     var window = $("#window").kendoWindow({
     *         height: "200px",
     *         title: "Centered Window",
     *         visible: false,
     *         width: "200px"
     *     }).data("kendoWindow");
     * });
     *
     * $("#openButton").click(function(){
     *     var window = $("#window").data("kendoWindow");
     *     window.center();
     *     window.open();
     * });
     *
     * @section
     * <h3>Loading Window content via AJAX</h3>
     * <p>
     *  A <strong>Window</strong> provides built-in support for asynchronously loading content from a URL. This URL
     *  should return a HTML fragment that can be loaded in a Window content area.
     * </p>
     *
     * @exampleTitle Load Window content asynchronously
     * @example
     * <div id="window"></div>
     *
     * @exampleTitle Initialize window and configure content loading
     * @example
     * $(document).ready(function(){
     *     $("#window").kendoWindow({
     *         content: "html-content-snippet.html",
     *         title: "Async Window Content"
     *     });
     * });
     *
     * @section
     * <h3>Accessing an Existing Window</h3>
     * <p>
     *  You can reference an existing <b>Window</b> instance via
     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
     *  use the API to control its behavior.
     * </p>
     *
     * @exampleTitle Accessing an existing Window instance
     * @example
     * var window = $("#window").data("kendoWindow");
     *
     */
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        Draggable = kendo.ui.Draggable,
        fx = kendo.fx,
        isPlainObject = $.isPlainObject,
        proxy = $.proxy,
        extend = $.extend,
        each = $.each,
        template = kendo.template,
        body,
        templates,
        // classNames
        KWINDOW = ".k-window",
        KWINDOWTITLEBAR = ".k-window-titlebar",
        KWINDOWCONTENT = ".k-window-content",
        KWINDOWRESIZEHANDLES = ".k-resize-handle",
        KOVERLAY = ".k-overlay",
        KCONTENTFRAME = "k-content-frame",
        LOADING = "k-loading",
        KHOVERSTATE = "k-state-hover",
        // constants
        VISIBLE = ":visible",
        CURSOR = "cursor",
        // events
        OPEN = "open",
        ACTIVATE = "activate",
        DEACTIVATE = "deactivate",
        CLOSE = "close",
        REFRESH = "refresh",
        RESIZE = "resize",
        DRAGSTART = "dragstart",
        DRAGEND = "dragend",
        ERROR = "error",
        OVERFLOW = "overflow",
        ZINDEX = "zIndex",
        MINIMIZE_MAXIMIZE = ".k-window-actions .k-minimize,.k-window-actions .k-maximize",
        isLocalUrl = kendo.isLocalUrl;

    function windowObject(element) {
        return element.children(KWINDOWCONTENT).data("kendoWindow");
    }

    function openedModalWindows() {
        return $(KWINDOW).filter(function() {
            var wnd = $(this);
            return wnd.is(VISIBLE) && windowObject(wnd).options.modal;
        });
    }

    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
                .find(KWINDOWRESIZEHANDLES).hide().end()
                .find(MINIMIZE_MAXIMIZE).parent().hide()
                    .eq(0).before(templates.action({ name: "Restore" }));

            callback.call(that);

            return that;
        };
    }

    var Window = Widget.extend(/** @lends kendo.ui.Window.prototype */ {
        /**
         *
         * @constructs
         * @extends kendo.ui.Widget
         *
         * @param {DomElement} element
         * DOM element
         *
         * @param {Object} options
         * Configuration options.
         *
         * @option {Boolean} [modal] <false>
         * Specifies whether the window should block interaction with other page elements.
         *
         * @option {Boolean} [visible] <true>
         * Specifies whether the window will be initially visible.
         *
         * @option {Boolean} [draggable] <true>
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability for users to move/drag a
         * <strong>Window</strong>.
         *
         * @option {Boolean} [resizable] <true>
         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability for users to resize a
         * <strong>Window</strong>.
         *
         * @option {Integer} [minWidth] <50>
         * The minimum width (in pixels) that may be achieved by resizing the window.
         *
         * @option {Integer} [minHeight] <50>
         * The minimum height (in pixels) that may be achieved by resizing the window.
         *
         * @option {Object|String} [content]
         * Specifies a URL or request options that the window should load its content from. For remote URLs, a
         * container iframe element is automatically created.
         *
         * @option {String} [content.template]
         * Template for the content of a <strong>Window</strong>.
         *
         * @option {Boolean} [iframe]
         * Explicitly states whether content iframe should be created.
         *
         * @option {Array} [actions] <["Close"]>
         * The buttons for interacting with the window. Predefined array values are "Close", "Refresh", "Minimize",
         * and "Maximize".
         *
         * @option {String} [title]
         * The text in the window title bar.
         *
         * @option {Object} [animation]
         * A collection of {Animation} objects, used to change default animations. A value of <strong>false</strong>
         * will disable all animations in the widget.
         *
         * @option {Animation} [animation.open]
         * The animation that will be used when a Window opens.
         *
         * @option {Animation} [animation.close]
         * The animation that will be used when a Window closes.
         *
         */
        init: function(element, options) {
            var that = this,
                wrapper,
                titleBar, offset, visibility, display,
                isVisible = false,
                content;

            body = document.body;

            Widget.fn.init.call(that, element, options);
            options = that.options;
            element = that.element;
            content = options.content;

            that._animations();

            if (!isPlainObject(content)) {
                content = options.content = { url: content };
            }

            if (!element.parent().is("body")) {
                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 });
                }
            }

            wrapper = that.wrapper = element.closest(KWINDOW);

            if (!element.is(".k-content") || !wrapper[0]) {
                element.addClass("k-window-content k-content");
                createWindow(element, options);
                wrapper = that.wrapper = element.closest(KWINDOW);

                that._dimensions();
            }

            if (offset) {
                if (isVisible) {
                    wrapper.css({
                        top: offset.top,
                        left: offset.left
                    });
                } else {
                   wrapper.css({
                        top: offset.top,
                        left: offset.left,
                        visibility: "visible",
                        display: "none"
                    });
                }
            }

            if (content) {
                that.refresh(content);
            }

            that.toFront();

            if (options.modal) {
                that._overlay(wrapper.is(VISIBLE)).css({ opacity: 0.5 });
            }

            wrapper.on({
                mouseenter: function () { $(this).addClass(KHOVERSTATE); },
                mouseleave: function () { $(this).removeClass(KHOVERSTATE); },
                click: proxy(that._windowActionHandler, that)
            }, ".k-window-titlebar .k-window-action");

            if (options.resizable) {
                wrapper.on("dblclick", KWINDOWTITLEBAR, proxy(that.toggleMaximization, that));

                each("n e s w se sw ne nw".split(" "), function(index, handler) {
                    wrapper.append(templates.resizeHandle(handler));
                });

                that.resizing = new WindowResizing(that);
            }

            if (options.draggable) {
                that.dragging = new WindowDragging(that);
            }

            wrapper.add(wrapper.find(".k-resize-handle,.k-window-titlebar"))
                .on("mousedown", proxy(that.toFront, that));

            that.touchScroller = kendo.touchScroller(element);

            $(window).resize(proxy(that._onDocumentResize, that));

            if (wrapper.is(VISIBLE)) {
                that.trigger(OPEN);
                that.trigger(ACTIVATE);
            }

            kendo.notify(that);
        },

        _dimensions: function() {
            var that = this,
                wrapper = that.wrapper,
                element = that.element,
                options = that.options;

            that.title(options.title);

            if (options.width) {
                wrapper.width(options.width);
            }

            if (options.height) {
                wrapper.height(options.height);
            }

            each(["minWidth","minHeight","maxWidth","maxHeight"], function(_, prop) {
                var value = options[prop];
                if (value && value != Infinity) {
                    element.css(prop, value);
                }
            });

            if (!options.visible) {
                wrapper.hide();
            }
        },

        _animations: function() {
            var options = this.options;

            if (options.animation === false) {
                options.animation = { open: { show: true, effects: {} }, close: { hide:true, effects: {} } };
            }
        },

        setOptions: function(options) {
            Widget.fn.setOptions.call(this, options);
            this._animations();
            this._dimensions();
        },

        events:[
            /**
             *
             * Triggered when a Window is opened (i.e. the open() method is called).
             *
             * @name kendo.ui.Window#open
             * @event
             * @cancellable
             *
             * @param {Event} e
             *
             * @exampleTitle Attach open event handler during initialization; detach via unbind()
             * @example
             * // event handler for expand
             * var onOpen = function(e) {
             *     // ...
             * };
             *
             * // attach open event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     open: onOpen
             * });
             *
             * // detach expand event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("open", onOpen);
             *
             * @exampleTitle Attach open event handler via bind(); detach via unbind()
             * @example
             * // event handler for open
             * var onOpen = function(e) {
             *     // ...
             * };
             *
             * // attach open event handler via bind()
             * $("#window").data("kendoWindow").bind("open", onOpen);
             *
             * // detach open event handler via unbind()
             * $("#window").data("kendoWindow").unbind("open", onOpen);
             *
             */
            OPEN,
            /**
             *
             * Triggered when a Window has finished its opening animation.
             *
             * @name kendo.ui.Window#activate
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach activate event handler during initialization; detach via unbind()
             * @example
             * // event handler for activate
             * var onActivate = function(e) {
             *     // ...
             * };
             *
             * // attach activate event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     activate: onActivate
             * });
             *
             * // detach activate event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("activate", onActivate);
             *
             * @exampleTitle Attach activate event handler via bind(); detach via unbind()
             * @example
             * // event handler for activate
             * var onActivate = function(e) {
             *     // ...
             * };
             *
             * // attach activate event handler via bind()
             * $("#window").data("kendoWindow").bind("activate", onActivate);
             *
             * // detach activate event handler via unbind()
             * $("#window").data("kendoWindow").unbind("activate", onActivate);
             *
             */
            ACTIVATE,
            /**
             *
             * Triggered when a Window has finished its closing animation.
             *
             * @name kendo.ui.Window#deactivate
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach deactivate event handler during initialization; detach via unbind()
             * @example
             * // event handler for deactivate
             * var onDeactivate = function(e) {
             *     // ...
             * };
             *
             * // attach deactivate event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     deactivate: onDeactivate
             * });
             *
             * // detach deactivate event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("deactivate", onDeactivate);
             *
             * @exampleTitle Attach deactivate event handler via bind(); detach via unbind()
             * @example
             * // event handler for deactivate
             * var onDeactivate = function(e) {
             *     // ...
             * };
             *
             * // attach deactivate event handler via bind()
             * $("#window").data("kendoWindow").bind("deactivate", onDeactivate);
             *
             * // detach deactivate event handler via unbind()
             * $("#window").data("kendoWindow").unbind("deactivate", onDeactivate);
             *
             */
            DEACTIVATE,
            /**
             *
             * Triggered when a Window is closed (by a user or through the close() method).
             *
             * @name kendo.ui.Window#close
             * @event
             * @cancellable
             *
             * @param {Event} e
             *
             * @exampleTitle Attach close event handler during initialization; detach via unbind()
             * @example
             * // event handler for close
             * var onClose = function(e) {
             *     // ...
             * };
             *
             * // attach close event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     close: onClose
             * });
             *
             * // detach close event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("close", onClose);
             *
             * @exampleTitle Attach close event handler via bind(); detach via unbind()
             * @example
             * // event handler for close
             * var onClose = function(e) {
             *     // ...
             * };
             *
             * // attach close event handler via bind()
             * $("#window").data("kendoWindow").bind("close", onClose);
             *
             * // detach close event handler via unbind()
             * $("#window").data("kendoWindow").unbind("close", onClose);
             *
             */
            CLOSE,
            /**
             *
             * Triggered when the content of a Window have been refreshed via AJAX.
             *
             * @name kendo.ui.Window#refresh
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach refresh event handler during initialization; detach via unbind()
             * @example
             * // event handler for refresh
             * var onRefresh = function(e) {
             *     // ...
             * };
             *
             * // attach refresh event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     refresh: onRefresh
             * });
             *
             * // detach refresh event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("refresh", onRefresh);
             *
             * @exampleTitle Attach refresh event handler via bind(); detach via unbind()
             * @example
             * // event handler for refresh
             * var onRefresh = function(e) {
             *     // ...
             * };
             *
             * // attach refresh event handler via bind()
             * $("#window").data("kendoWindow").bind("refresh", onRefresh);
             *
             * // detach refresh event handler via unbind()
             * $("#window").data("kendoWindow").unbind("refresh", onRefresh);
             *
             */
            REFRESH,
            /**
             *
             * Triggered when a Window has been resized by a user.
             *
             * @name kendo.ui.Window#resize
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach resize event handler during initialization; detach via unbind()
             * @example
             * // event handler for resize
             * var onResize = function(e) {
             *     // ...
             * };
             *
             * // attach resize event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     resize: onResize
             * });
             *
             * // detach resize event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("resize", onResize);
             *
             * @exampleTitle Attach resize event handler via bind(); detach via unbind()
             * @example
             * // event handler for resize
             * var onResize = function(e) {
             *     // ...
             * };
             *
             * // attach resize event handler via bind()
             * $("#window").data("kendoWindow").bind("resize", onResize);
             *
             * // detach resize event handler via unbind()
             * $("#window").data("kendoWindow").unbind("resize", onResize);
             *
             */
            RESIZE,

            /**
             * Triggered when the user starts to move the window.
             * @name kendo.ui.Window#dragstart
             * @event
             * @param {Event} e
             */
            DRAGSTART,

            /**
             *
             * Triggered when a Window has been moved by a user.
             *
             * @name kendo.ui.Window#dragend
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach dragEnd event handler during initialization; detach via unbind()
             * @example
             * // event handler for dragEnd
             * var onDragEnd = function(e) {
             *     // ...
             * };
             *
             * // attach dragEnd event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     dragend: onDragEnd
             * });
             *
             * // detach dragEnd event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("dragend", onDragEnd);
             *
             * @exampleTitle Attach dragEnd event handler via bind(); detach via unbind()
             * @example
             * // event handler for dragEnd
             * var onDragEnd = function(e) {
             *     // ...
             * };
             *
             * // attach dragEnd event handler via bind()
             * $("#window").data("kendoWindow").bind("dragend", onDragEnd);
             *
             * // detach dragEnd event handler via unbind()
             * $("#window").data("kendoWindow").unbind("dragend", onDragEnd);
             *
             */
            DRAGEND,

            /**
             *
             * Triggered when an AJAX request for content fails.
             *
             * @name kendo.ui.Window#error
             * @event
             *
             * @param {Event} e
             *
             * @exampleTitle Attach error event handler during initialization; detach via unbind()
             * @example
             * // event handler for error
             * var onError = function(e) {
             *     // ...
             * };
             *
             * // attach dragEnd event handler during initialization
             * var kendoWindow = $("#window").kendoWindow({
             *     error: onError
             * });
             *
             * // detach error event handler via unbind()
             * kendoWindow.data("kendoWindow").unbind("error", onError);
             *
             * @exampleTitle Attach error event handler via bind(); detach via unbind()
             * @example
             * // event handler for error
             * var onError = function(e) {
             *     // ...
             * };
             *
             * // attach error event handler via bind()
             * $("#window").data("kendoWindow").bind("error", onError);
             *
             * // detach error event handler via unbind()
             * $("#window").data("kendoWindow").unbind("error", onError);
             *
             */
            ERROR
        ],

        options: {
            name: "Window",
            animation: {
                open: {
                    effects: { zoom: { direction: "in" }, fade: { direction: "in" } },
                    duration: 350,
                    show: true
                },
                close: {
                    effects: { zoom: { direction: "out", properties: { scale: 0.7 } }, fade: { direction: "out" } },
                    duration: 350,
                    hide: true
                }
            },
            title: "",
            actions: ["Close"],
            modal: false,
            resizable: true,
            draggable: true,
            minWidth: 90,
            minHeight: 50,
            maxWidth: Infinity,
            maxHeight: Infinity,
            visible: true
        },

        _overlay: function (visible) {
            var overlay = $("body > .k-overlay"),
                doc = $(document),
                wrapper = this.wrapper;

            if (overlay.length == 0) {
                overlay = $("<div class='k-overlay' />");
            }

            overlay
                .insertBefore(wrapper[0])
                .toggle(visible)
                .css(ZINDEX, parseInt(wrapper.css(ZINDEX)) - 1);

            return overlay;
        },

        _windowActionHandler: function (e) {
            var target = $(e.target).closest(".k-window-action").find(".k-icon"),
                that = this;

            each({
                "k-close": that.close,
                "k-maximize": that.maximize,
                "k-minimize": that.minimize,
                "k-restore": that.restore,
                "k-refresh": that.refresh
            }, function (commandName, handler) {
                if (target.hasClass(commandName)) {
                    e.preventDefault();
                    handler.call(that);
                    return false;
                }
            });
        },

        /**
         *
         * Centers a <strong>Window</strong> within the viewport.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @example
         * var kendoWindow = $("#window").data("kendoWindow");
         * kendoWindow.center();
         *
         */
        center: function () {
            var wrapper = this.wrapper,
                documentWindow = $(window);

            wrapper.css({
                left: documentWindow.scrollLeft() + Math.max(0, (documentWindow.width() - wrapper.width()) / 2),
                top: documentWindow.scrollTop() + Math.max(0, (documentWindow.height() - wrapper.height()) / 2)
            });

            return this;
        },

        /**
         *
         * Gets or set the title of a <strong>Window</strong>.
         *
         * @param {String} [text]
         * The title of the Window.
         *
         * @returns {Window}
         * If a title is provided, this method will return the (Kendo UI) Window object to support chaining. Otherwise,
         * it will return the current title of the (Kendo UI) Window.
         *
         * @exampleTitle Get the existing title of the Window
         * @example
         * var kendoWindow = $("#window").data("kendoWindow");
         * var windowTitle = kendoWindow.title();
         *
         * @exampleTitle Set the title of a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").title("Do a barrel roll!");
         *
         */
        title: function (text) {
            var that = this,
                wrapper = that.wrapper,
                options = that.options,
                titleBar = wrapper.find(KWINDOWTITLEBAR),
                title = titleBar.children(".k-window-title"),
                titleBarHeight = titleBar.outerHeight();

            if (arguments.length == 0) {
                return title.text();
            }

            if (text === false) {
                wrapper.addClass("k-window-titleless");
                titleBar.remove();
            } else {
                if (titleBar.length == 0) {
                    wrapper.prepend(templates.titlebar(extend(templates, options)));
                }

                wrapper.css("padding-top", titleBarHeight);
                titleBar.css("margin-top", -titleBarHeight);
            }

            title.text(text);

            return that;
        },

        /**
         *
         * Gets or set the content of a <strong>Window</strong>.
         *
         * @param {String} [content]
         * The content of the Window.
         *
         * @returns {Window}
         * If content is provided, this method will return the (Kendo UI) Window object to support chaining. Otherwise,
         * it will return the current content of the (Kendo UI) Window.
         *
         * @exampleTitle Get the existing content of the Window
         * @example
         * var kendoWindow = $("#window").data("kendoWindow");
         * var windowContent = kendoWindow.content();
         *
         * @exampleTitle Set the title of a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").content("Kendo UI for all the things!");
         *
         */
        content: function (html) {
            var content = this.wrapper.children(KWINDOWCONTENT);

            if (!html) {
                return content.html();
            }

            content.html(html);
            return this;
        },

        /**
         *
         * Opens a Window.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Open a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").open();
         *
         */
        open: function () {
            var that = this,
                wrapper = that.wrapper,
                showOptions = that.options.animation.open,
                contentElement = wrapper.children(KWINDOWCONTENT),
                initialOverflow = contentElement.css(OVERFLOW);

            if (!that.trigger(OPEN)) {
                that.toFront();

                if (that.options.modal) {
                    var overlay = that._overlay(false);

                    if (showOptions.duration) {
                        overlay.kendoStop().kendoAnimate({
                            effects: { fade: { direction: "out", properties: { opacity: 0.5 } } },
                            duration: showOptions.duration,
                            show: true
                        });
                    } else {
                        overlay.css("opacity", 0.5).show();
                    }
                }

                if (!wrapper.is(VISIBLE)) {
                    contentElement.css(OVERFLOW, "hidden");
                    wrapper.show().kendoStop().kendoAnimate({
                        effects: showOptions.effects,
                        duration: showOptions.duration,
                        complete: function() {
                            that.trigger(ACTIVATE);
                            contentElement.css(OVERFLOW, initialOverflow);
                        }
                    });
                }
            }

            if (that.options.isMaximized) {
               $("html, body").css(OVERFLOW, "hidden");
            }

            return that;
        },

        /**
         *
         * Closes a Window.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Close a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").close();
         *
         */
        close: function () {
            var that = this,
                wrapper = that.wrapper,
                options = that.options,
                showOptions = options.animation.open,
                hideOptions = options.animation.close,
                modalWindows,
                shouldHideOverlay, overlay;

            if (wrapper.is(VISIBLE) && !that.trigger(CLOSE)) {
                modalWindows = openedModalWindows();

                shouldHideOverlay = options.modal && modalWindows.length == 1;

                overlay = options.modal ? that._overlay(true) : $(undefined);

                if (shouldHideOverlay) {
                    if (hideOptions.duration) {
                        overlay.kendoStop().kendoAnimate({
                             effects: { fadeOut: { properties: { opacity: 0 } } },
                             duration: hideOptions.duration,
                             hide: true
                         });
                    } else {
                        overlay.hide();
                    }
                } else if (modalWindows.length) {
                    windowObject(modalWindows.eq(modalWindows.length - 2))._overlay(true);
                }

                wrapper.kendoStop().kendoAnimate({
                    effects: hideOptions.effects || showOptions.effects,
                    reverse: hideOptions.reverse === true,
                    duration: hideOptions.duration,
                    complete: function() {
                        wrapper.hide();
                        that.trigger(DEACTIVATE);
                    }
                });
            }

            if (that.options.isMaximized) {
                $("html, body").css(OVERFLOW, "");
            }

            return that;
        },

        /**
         *
         * Brings forward a Window to the top of the z-index.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Bring forward a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").toFront();
         *
         */
        toFront: function () {
            var that = this,
                wrapper = that.wrapper,
                currentWindow = wrapper[0],
                zIndex = +wrapper.css(ZINDEX);

            $(KWINDOW).each(function(i, element) {
                var windowObject = $(element),
                    zIndexNew = windowObject.css(ZINDEX),
                    contentElement = windowObject.find(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);
                }
            });

            wrapper.css(ZINDEX, zIndex + 2);
            that.element.find("> .k-overlay").remove();

            return that;
        },

        /**
         *
         * Toggles a Window between a maximized and restored state.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Toggle the state of a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").toggleMaximization();
         *
         */
        toggleMaximization: function () {
            return this[this.options.isMaximized ? "restore" : "maximize"]();
        },

        /**
         *
         * Restores a maximized or minimized Window to its previous state.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Restore the state of a Window; utilize chaining (if necessary)
         * @example
         * var kendoWindow = $("#window").data("kendoWindow").restore();
         *
         */
        restore: function () {
            var that = this,
                options = that.options,
                restoreOptions = that.restoreOptions;

            if (!options.isMaximized && !options.isMinimized) {
                return;
            }

            that.wrapper
                .css({
                    position: "absolute",
                    left: restoreOptions.left,
                    top: restoreOptions.top,
                    width: restoreOptions.width,
                    height: restoreOptions.height
                })
                .find(".k-window-content,.k-resize-handle").show().end()
                .find(".k-window-titlebar .k-restore").parent().remove().end().end()
                .find(MINIMIZE_MAXIMIZE).parent().show();

            $("html, body").css(OVERFLOW, "");

            options.isMaximized = options.isMinimized = false;

            that.trigger(RESIZE);

            return that;
        },

        /**
         *
         * Maximizes a Window to the entire viewing area of the user agent.
         *
         * @function
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Maximize a Window
         * @example
         * $("#window").data("kendoWindow").maximize();
         *
         */
        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"
                });

            $("html, body").css(OVERFLOW, "hidden");

            that.options.isMaximized = true;

            that._onDocumentResize();
        }),

        /**
         *
         * Maximizes a Window to its title bar.
         *
         * @function
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @exampleTitle Minimize a Window
         * @example
         * $("#window").data("kendoWindow").minimize();
         *
         */
        minimize: sizingAction("minimize", function() {
            var that = this;

            that.wrapper.css("height", "");
            that.element.hide();

            that.options.isMinimized = true;
        }),

        _onDocumentResize: function () {
            var that = this,
                wrapper = that.wrapper,
                wnd = $(window);

            if (!that.options.isMaximized) {
                return;
            }

            wrapper.css({
                    width: wnd.width(),
                    height: wnd.height()
                });

            that.trigger(RESIZE);
        },

        /**
         *
         * Refreshes the content of a Window from a remote URL.
         *
         * @param {Object|String} options
         * Options for requesting data from the server.
         * If omitted, the window uses the <code>content</code> property
         * that was supplied when the window was created.
         * Any options specified here are passed to jQuery.ajax().
         *
         * @param {String} options.url
         * The server URL that will be requested.
         *
         * @param {Object} options.data
         * A JSON object containing the data that will be passed to the server.
         *
         * @param {String} options.type
         * The HTTP request method ("GET", "POST").
         *
         * @param {String} options.template
         * A template to be used for displaying the requested data.
         *
         * @returns {Window}
         * Returns the (Kendo UI) Window object to support chaining.
         *
         * @example
         * var windowObject = $("#window").data("kendoWindow");
         * windowObject.refresh("/feedbackForm");
         *
         * windowObject.refresh({
         *     url: "/feedbackForm",
         *     data: { userId: 42 }
         * });
         *
         * windowObject.refresh({
         *     url: "/userInfo",
         *     data: { userId: 42 },
         *     template: "Hello, #= firstName # #= lastName #"
         * });
         *
         */
        refresh: function (options) {
            var that = this,
                initOptions = that.options,
                element = $(that.element),
                iframe,
                url;

            if (!isPlainObject(options)) {
                options = { url: options };
            }

            options = extend({}, initOptions.content, options);

            url = options.url;

            if (url) {
                if (that.options.iframe === false || isLocalUrl(url)) {
                    // 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 })));
                    }
                }
            } else if (options.template) {
                // refresh template
                that.content(template(options.template)({}));
            }

            return that;
        },

        _ajaxRequest: function (options) {
            var that = this,
                contentTemplate = options.template,
                refreshIcon = that.wrapper.find(".k-window-titlebar .k-refresh"),
                loadingIconTimeout = setTimeout(function () {
                    refreshIcon.addClass(LOADING);
                }, 100);

            $.ajax(extend({
                type: "GET",
                dataType: "html",
                cache: false,
                error: proxy(function (xhr, status) {
                    that.trigger(ERROR);
                }, that),
                complete: function () {
                    clearTimeout(loadingIconTimeout);
                    refreshIcon.removeClass(LOADING);
                },
                success: proxy(function (data, textStatus) {
                    if (contentTemplate) {
                        data = template(contentTemplate)(data || {});
                    }

                    that.element.html(data);

                    that.trigger(REFRESH);
                }, that)
            }, options));
        },

        /**
         * Destroys the window and its modal overlay, if necessary. Useful for removing modal windows.
         */
        destroy: function () {
            var that = this,
                modalWindows,
                shouldHideOverlay;

            that.wrapper.remove();

            modalWindows = openedModalWindows();

            shouldHideOverlay = that.options.modal && !modalWindows.length;

            if (shouldHideOverlay) {
                that._overlay(false).remove();
            } else if (modalWindows.length > 0) {
                windowObject(modalWindows.eq(modalWindows.length - 2))._overlay(true);
            }
        }
    });

    templates = {
        wrapper: template("<div class='k-widget k-window' />"),
        action: template(
            "<a href='\\#' class='k-window-action k-link'>" +
                "<span class='k-icon k-#= 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 k-header'>" +
                "# for (var i = 0; i < actions.length; i++) { #" +
                    "#= action({ name: actions[i] }) #" +
                "# } #" +
                "</div>" +
            "</div>"
        ),
        overlay: "<div class='k-overlay' />",
        contentFrame: template(
            "<iframe src='#= content.url #' title='#= title #' frameborder='0'" +
                " class='" + KCONTENTFRAME + "'>" +
                    "This page requires frames in order to show content" +
            "</iframe>"
        ),
        resizeHandle: template("<div class='k-resize-handle k-resize-#= data #'></div>")
    };

    function createWindow(element, options) {
        var contentHtml = element,
            iframeSrcAttributes,
            wrapper;

        if (options.scrollable === false) {
            contentHtml.attr("style", "overflow:hidden;");
        }

        if (options.iframe) {
            contentHtml.html(templates.contentFrame(options));
        }

        wrapper = $(templates.wrapper(options));

        if (options.title !== false) {
            wrapper.append(templates.titlebar(extend(templates, options)))
        }

        wrapper.toggleClass("k-rtl", !!element.closest(".k-rtl").length);

        // 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").map(function(iframe) {
            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
            .appendTo(body)
            .append(contentHtml)
            .find("iframe").each(function(index) {
               // Restore the src attribute of the iframes when they are part of the live DOM tree
               this.src = iframeSrcAttributes[index];
            });
    }

    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)
        });
    }

    WindowResizing.prototype = /** @ignore */ {
        dragstart: function (e) {
            var wnd = this.owner,
                wrapper = wnd.wrapper;

            wnd.elementPadding = parseInt(wnd.wrapper.css("padding-top"));
            wnd.initialCursorPosition = wrapper.offset();

            wnd.resizeDirection = e.currentTarget.prop("className").replace("k-resize-handle k-resize-", "").split("");

            wnd.initialSize = {
                width: wnd.wrapper.width(),
                height: wnd.wrapper.height()
            };

            wrapper
                .append(templates.overlay)
                .find(KWINDOWRESIZEHANDLES).not(e.currentTarget).hide();

            $(body).css(CURSOR, e.currentTarget.css(CURSOR));
        },
        drag: function (e) {
            var wnd = this.owner,
                wrapper = wnd.wrapper,
                options = wnd.options,
                constrain = function(value, low, high) {
                    return Math.max(Math.min(value, high), low);
                },
                resizeHandlers = {
                    "e": function () {
                        var newWidth = e.x.location - wnd.initialCursorPosition.left;

                        wrapper.width(constrain(newWidth, options.minWidth, options.maxWidth));
                    },
                    "s": function () {
                        var newHeight = e.y.location - wnd.initialCursorPosition.top - wnd.elementPadding;

                        wrapper.height(constrain(newHeight, options.minHeight, options.maxHeight));
                    },
                    "w": function () {
                        var windowRight = wnd.initialCursorPosition.left + wnd.initialSize.width,
                            newWidth = constrain(windowRight - e.x.location, options.minWidth, options.maxWidth);

                        wrapper.css({
                            left: windowRight - newWidth,
                            width: newWidth
                        })
                    },
                    "n": function () {
                        var windowBottom = wnd.initialCursorPosition.top + wnd.initialSize.height,
                            newHeight = constrain(windowBottom - e.y.location, options.minHeight, options.maxHeight);

                        wrapper.css({
                            top: windowBottom - newHeight,
                            height: newHeight
                        });
                    }
                };

            each(wnd.resizeDirection, function () {
                resizeHandlers[this]();
            });

            wnd.trigger(RESIZE);
        },
        dragend: function (e) {
            var wnd = this.owner,
                wrapper = wnd.wrapper;

            wrapper
                .find(KOVERLAY).remove().end()
                .find(KWINDOWRESIZEHANDLES).not(e.currentTarget).show();

            $(body).css(CURSOR, "");

            if (wnd.touchScroller) {
               wnd.touchScroller.reset();
            }
            if (e.keyCode == 27) {
                wrapper.css(wnd.initialCursorPosition)
                    .css(wnd.initialSize);
            }

            return false;
        }
    };

    function WindowDragging(wnd) {
        var that = this;

        that.owner = wnd;
        that._draggable = new Draggable(wnd.wrapper, {
            filter: KWINDOWTITLEBAR,
            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)
        });
    }

    WindowDragging.prototype = /** @ignore */{
        dragstart: function (e) {
            var wnd = this.owner,
                element = wnd.element,
                actions = element.find(".k-window-actions");

            wnd.trigger(DRAGSTART);

            wnd.initialWindowPosition = 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.wrapper
                .append(templates.overlay)
                .find(KWINDOWRESIZEHANDLES).hide();

            $(body).css(CURSOR, e.currentTarget.css(CURSOR));
        },

        drag: function (e) {
            var wnd = this.owner,
                coordinates = {
                    left: Math.max(e.x.client - wnd.startPosition.left, wnd.minLeftPosition),
                    top: Math.max(e.y.client - wnd.startPosition.top, 0)
                };

            $(wnd.wrapper).css(coordinates);
        },

        dragcancel: function (e) {
            var wnd = this.owner;

            wnd.wrapper
                .find(KWINDOWRESIZEHANDLES).show().end()
                .find(KOVERLAY).remove();

            $(body).css(CURSOR, "");

            e.currentTarget.closest(KWINDOW).css(wnd.initialWindowPosition);
        },

        dragend: function (e) {
            var wnd = this.owner;

            wnd.wrapper
                .find(KWINDOWRESIZEHANDLES).show().end()
                .find(KOVERLAY).remove();

            $(body).css(CURSOR, "");

            wnd.trigger(DRAGEND);

            return false;
        }
    };

    kendo.ui.plugin(Window);

})(jQuery);
