PageSourceSearch

https://www.flex-solutions.com/Scripts/2012.1.515/kendo.web.js

js flex-solutions.com collected 2026-10-10 00:45:27 UTC 1,389,210 bytes, 39,605 lines download raw bytes

1/*
2* Kendo UI Web v2012.1.515 (http://kendoui.com)
3* Copyright 2012 Telerik AD. All rights reserved.
4*
5* Kendo UI Web commercial licenses may be obtained at http://kendoui.com/web-license
6* If you do not own a commercial license, this file shall be governed by the
7* GNU General Public License (GPL) version 3.
8* For GPL requirements, please review: http://www.gnu.org/copyleft/gpl.html
9*/
10;(function($, undefined) {
11    /**
12     * @name kendo
13     * @namespace This object contains all code introduced by the Kendo project, plus helper functions that are used across all widgets.
14     */
15    var kendo = window.kendo = window.kendo || {},
16        extend = $.extend,
17        each = $.each,
18        proxy = $.proxy,
19        isArray = $.isArray,
20        noop = $.noop,
21        isFunction = $.isFunction,
22        math = Math,
23        Template,
24        JSON = window.JSON || {},
25        support = {},
26        formatRegExp = /\{(\d+)(:[^\}]+)?\}/g,
27        boxShadowRegExp = /(\d+?)px\s*(\d+?)px\s*(\d+?)px\s*(\d+?)?/i,
28        FUNCTION = "function",
29        STRING = "string",
30        NUMBER = "number",
31        OBJECT = "object",
32        NULL = "null",
33        BOOLEAN = "boolean",
34        UNDEFINED = "undefined",
35        getterCache = {},
36        setterCache = {},
37        slice = [].slice,
38        globalize = window.Globalize;
39
40    function Class() {}
41
42    Class.extend = function(proto) {
43        var base = function() {},
44            member,
45            that = this,
46            subclass = proto && proto.init ? proto.init : function () {
47                that.apply(this, arguments);
48            },
49            fn;
50
51        base.prototype = that.prototype;
52        fn = subclass.fn = subclass.prototype = new base();
53
54        for (member in proto) {
55            if (typeof proto[member] === OBJECT && !(proto[member] instanceof Array) && proto[member] !== null) {
56                // Merge object members
57                fn[member] = extend(true, {}, base.prototype[member], proto[member]);
58            } else {
59                fn[member] = proto[member];
60            }
61        }
62
63        fn.constructor = subclass;
64        subclass.extend = that.extend;
65
66        return subclass;
67    };
68
69    var Observable = Class.extend(/** @lends kendo.Observable.prototype */{
70        /**
71         * Creates an observable instance.
72         * @constructs
73         * @class Represents a class that can trigger events, along with methods that subscribe handlers to these events.
74         */
75        init: function() {
76            this._events = {};
77        },
78
79        bind: function(eventName, handlers, one) {
80            var that = this,
81                idx,
82                eventNames = typeof eventName === STRING ? [eventName] : eventName,
83                length,
84                original,
85                handler,
86                handlersIsFunction = typeof handlers === FUNCTION,
87                events;
88
89            for (idx = 0, length = eventNames.length; idx < length; idx++) {
90                eventName = eventNames[idx];
91
92                handler = handlersIsFunction ? handlers : handlers[eventName];
93
94                if (handler) {
95                    if (one) {
96                        original = handler;
97                        handler = function() {
98                            that.unbind(eventName, handler);
99                            original.apply(that, arguments);
100                        };
101                    }
102                    events = that._events[eventName] = that._events[eventName] || [];
103                    events.push(handler);
104                }
105            }
106
107            return that;
108        },
109
110        one: function(eventNames, handlers) {
111            return this.bind(eventNames, handlers, true);
112        },
113
114        first: function(eventName, handlers) {
115            var that = this,
116                idx,
117                eventNames = typeof eventName === STRING ? [eventName] : eventName,
118                length,
119                handler,
120                handlersIsFunction = typeof handlers === FUNCTION,
121                events;
122
123            for (idx = 0, length = eventNames.length; idx < length; idx++) {
124                eventName = eventNames[idx];
125
126                handler = handlersIsFunction ? handlers : handlers[eventName];
127
128                if (handler) {
129                    events = that._events[eventName] = that._events[eventName] || [];
130                    events.unshift(handler);
131                }
132            }
133
134            return that;
135        },
136
137        trigger: function(eventName, e) {
138            var that = this,
139                events = that._events[eventName],
140                idx,
141                isDefaultPrevented = false;
142
143            if (events) {
144                e = e || {};
145
146                e.sender = that;
147
148                e.preventDefault = function () {
149                    isDefaultPrevented = true;
150                };
151
152                e.isDefaultPrevented = function() {
153                    return isDefaultPrevented;
154                };
155
156                //Do not cache the length of the events array as removing events attached through one will fail
157                for (idx = 0; idx < events.length; idx++) {
158                    events[idx].call(that, e);
159                }
160            }
161
162            return isDefaultPrevented;
163        },
164
165        unbind: function(eventName, handler) {
166            var that = this,
167                events = that._events[eventName],
168                idx,
169                length;
170
171            if (events) {
172                if (handler) {
173                    for (idx = 0, length = events.length; idx < length; idx++) {
174                        if (events[idx] === handler) {
175                            events.splice(idx, 1);
176                        }
177                    }
178                } else {
179                    that._events[eventName] = [];
180                }
181            }
182
183            return that;
184        }
185    });
186
187    /**
188     * @name kendo.Template.Description
189     *
190     * @section
191     * <p>
192     *  Templates offer way of creating HTML chunks. Options such as HTML encoding and compilation for optimal
193     *  performance are available.
194     * </p>
195     *
196     * @exampleTitle Basic template
197     * @example
198     * var inlineTemplate = kendo.template("Hello, #= firstName # #= lastName #");
199     * var inlineData = { firstName: "John", lastName: "Doe" };
200     * $("#inline").html(inlineTemplate(inlineData));
201     *
202     * @exampleTitle Output:
203     * @example
204     * Hello, John Doe!
205     *
206     * @exampleTitle Encode HTML
207     * @example
208     * var encodingTemplate = kendo.template("HTML tags are encoded as follows: #:html#");
209     * var encodingData = { html: "<strong>lorem ipsum</strong>" };
210     * $("#encoding").html(encodingTemplate(encodingData));
211     *
212     * @exampleTitle Output:
213     * @example
214     * HTML tags are encoded as follows: <strong>lorem ipsum</strong>
215     *
216     * @exampleTitle Use javascript in templates
217     * @example
218     * var encodingTemplate = kendo.template("#if (foo) {# bar #}#");
219     * var data = { foo: true};
220     * $("#encoding").html(encodingTemplate(data)); // outputs bar
221     *
222     * @exampleTitle Escape sharp symbols in JavaScript strings
223     * @example
224     * var encodingTemplate = kendo.template("<a href='\\#'>Link</a>");
225     *
226     * @exampleTitle Escape sharp symbols in script templates
227     * @example
228     * <script type="text/x-kendo-template" id="template">
229     *  <a href="\#">Link</a>
230     * </script>
231     *
232     * <script>
233     * var encodingTemplate = kendo.template($("#template").text());
234     * </script>
235     */
236
237     function compilePart(part, stringPart) {
238         if (stringPart) {
239             return "'" +
240                 part.split("'").join("\\'")
241                     .replace(/\n/g, "\\n")
242                     .replace(/\r/g, "\\r")
243                     .replace(/\t/g, "\\t") + "'";
244         } else {
245             var first = part.charAt(0),
246                 rest = part.substring(1);
247
248             if (first === "=") {
249                 return "+(" + rest + ")+";
250             } else if (first === ":") {
251                 return "+e(" + rest + ")+";
252             } else {
253                 return ";" + part + ";o+=";
254             }
255         }
256     }
257
258    var argumentNameRegExp = /^\w+/,
259        encodeRegExp = /\$\{([^}]*)\}/g,
260        escapedCurlyRegExp = /\\\}/g,
261        curlyRegExp = /__CURLY__/g,
262        escapedSharpRegExp = /\\#/g,
263        sharpRegExp = /__SHARP__/g;
264
265    /**
266     * @name kendo.Template
267     * @namespace
268     */
269    Template = /** @lends kendo.Template */ {
270        paramName: "data", // name of the parameter of the generated template
271        useWithBlock: true, // whether to wrap the template in a with() block
272        /**
273         * Renders a template for each item of the data.
274         * @ignore
275         * @name kendo.Template.render
276         * @static
277         * @function
278         * @param {String} [template] The template that will be rendered
279         * @param {Array} [data] Data items
280         * @returns {String} The rendered template
281         */
282        render: function(template, data) {
283            var idx,
284                length,
285                html = "";
286
287            for (idx = 0, length = data.length; idx < length; idx++) {
288                html += template(data[idx]);
289            }
290
291            return html;
292        },
293        /**
294         * Compiles a template to a function that builds HTML. Useful when a template will be used several times.
295         * @ignore
296         * @name kendo.Template.compile
297         * @static
298         * @function
299         * @param {String} [template] The template that will be compiled
300         * @param {Object} [options] Compilation options
301         * @returns {Function} The compiled template
302         */
303        compile: function(template, options) {
304            var settings = extend({}, this, options),
305                paramName = settings.paramName,
306                argumentName = paramName.match(argumentNameRegExp)[0],
307                useWithBlock = settings.useWithBlock,
308                functionBody = "var o,e=kendo.htmlEncode;",
309                parts,
310                idx;
311
312            if (isFunction(template)) {
313                if (template.length === 2) {
314                    //looks like jQuery.template
315                    return function(d) {
316                        return template($, { data: d }).join("");
317                    };
318                }
319                return template;
320            }
321
322            functionBody += useWithBlock ? "with(" + paramName + "){" : "";
323
324            functionBody += "o=";
325
326            parts = template
327                .replace(escapedCurlyRegExp, "__CURLY__")
328                .replace(encodeRegExp, "#=e($1)#")
329                .replace(curlyRegExp, "}")
330                .replace(escapedSharpRegExp, "__SHARP__")
331                .split("#");
332
333            for (idx = 0; idx < parts.length; idx ++) {
334                functionBody += compilePart(parts[idx], idx % 2 === 0);
335            }
336
337            functionBody += useWithBlock ? ";}" : ";";
338
339            functionBody += "return o;";
340
341            functionBody = functionBody.replace(sharpRegExp, "#");
342
343            try {
344                return new Function(argumentName, functionBody);
345            } catch(e) {
346                throw new Error(kendo.format("Invalid template:'{0}' Generated code:'{1}'", template, functionBody));
347            }
348        }
349    };
350
351function pad(number) {
352    return number < 10 ? "0" + number : number;
353}
354
355    //JSON stringify
356(function() {
357    var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
358        gap,
359        indent,
360        meta = {
361            "\b": "\\b",
362            "\t": "\\t",
363            "\n": "\\n",
364            "\f": "\\f",
365            "\r": "\\r",
366            "\"" : '\\"',
367            "\\": "\\\\"
368        },
369        rep,
370        toString = {}.toString;
371
372    if (typeof Date.prototype.toJSON !== FUNCTION) {
373
374        /** @ignore */
375        Date.prototype.toJSON = function (key) {
376            var that = this;
377
378            return isFinite(that.valueOf()) ?
379                that.getUTCFullYear()     + "-" +
380                pad(that.getUTCMonth() + 1) + "-" +
381                pad(that.getUTCDate())      + "T" +
382                pad(that.getUTCHours())     + ":" +
383                pad(that.getUTCMinutes())   + ":" +
384                pad(that.getUTCSeconds())   + "Z" : null;
385        };
386
387        String.prototype.toJSON = Number.prototype.toJSON = /** @ignore */ Boolean.prototype.toJSON = function (key) {
388            return this.valueOf();
389        };
390    }
391
392    function quote(string) {
393        escapable.lastIndex = 0;
394        return escapable.test(string) ? "\"" + string.replace(escapable, function (a) {
395            var c = meta[a];
396            return typeof c === STRING ? c :
397                "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
398        }) + "\"" : "\"" + string + "\"";
399    }
400
401    function str(key, holder) {
402        var i,
403            k,
404            v,
405            length,
406            mind = gap,
407            partial,
408            value = holder[key],
409            type;
410
411        if (value && typeof value === OBJECT && typeof value.toJSON === FUNCTION) {
412            value = value.toJSON(key);
413        }
414
415        if (typeof rep === FUNCTION) {
416            value = rep.call(holder, key, value);
417        }
418
419        type = typeof value;
420        if (type === STRING) {
421            return quote(value);
422        } else if (type === NUMBER) {
423            return isFinite(value) ? String(value) : NULL;
424        } else if (type === BOOLEAN || type === NULL) {
425            return String(value);
426        } else if (type === OBJECT) {
427            if (!value) {
428                return NULL;
429            }
430            gap += indent;
431            partial = [];
432            if (toString.apply(value) === "[object Array]") {
433                length = value.length;
434                for (i = 0; i < length; i++) {
435                    partial[i] = str(i, value) || NULL;
436                }
437                v = partial.length === 0 ? "[]" : gap ?
438                    "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" :
439                    "[" + partial.join(",") + "]";
440                gap = mind;
441                return v;
442            }
443            if (rep && typeof rep === OBJECT) {
444                length = rep.length;
445                for (i = 0; i < length; i++) {
446                    if (typeof rep[i] === STRING) {
447                        k = rep[i];
448                        v = str(k, value);
449                        if (v) {
450                            partial.push(quote(k) + (gap ? ": " : ":") + v);
451                        }
452                    }
453                }
454            } else {
455                for (k in value) {
456                    if (Object.hasOwnProperty.call(value, k)) {
457                        v = str(k, value);
458                        if (v) {
459                            partial.push(quote(k) + (gap ? ": " : ":") + v);
460                        }
461                    }
462                }
463            }
464
465            v = partial.length === 0 ? "{}" : gap ?
466                "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" :
467                "{" + partial.join(",") + "}";
468            gap = mind;
469            return v;
470        }
471    }
472
473    if (typeof JSON.stringify !== FUNCTION) {
474        JSON.stringify = function (value, replacer, space) {
475            var i;
476            gap = "";
477            indent = "";
478
479            if (typeof space === NUMBER) {
480                for (i = 0; i < space; i += 1) {
481                    indent += " ";
482                }
483
484            } else if (typeof space === STRING) {
485                indent = space;
486            }
487
488            rep = replacer;
489            if (replacer && typeof replacer !== FUNCTION && (typeof replacer !== OBJECT || typeof replacer.length !== NUMBER)) {
490                throw new Error("JSON.stringify");
491            }
492
493            return str("", {"": value});
494        };
495    }
496})();
497
498// Date and Number formatting
499(function() {
500    var dateFormatRegExp = /dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|HH|H|hh|h|mm|m|fff|ff|f|tt|ss|s|"[^"]*"|'[^']*'/g,
501        standardFormatRegExp =  /^(n|c|p|e)(\d*)$/i,
502        literalRegExp = /["'].*?["']/g,
503        commaRegExp = /\,/g,
504        EMPTY = "",
505        POINT = ".",
506        COMMA = ",",
507        SHARP = "#",
508        ZERO = "0",
509        PLACEHOLDER = "??",
510        EN = "en-US";
511
512    //cultures
513    kendo.cultures = {"en-US" : {
514        name: EN,
515        numberFormat: {
516            pattern: ["-n"],
517            decimals: 2,
518            ",": ",",
519            ".": ".",
520            groupSize: [3],
521            percent: {
522                pattern: ["-n %", "n %"],
523                decimals: 2,
524                ",": ",",
525                ".": ".",
526                groupSize: [3],
527                symbol: "%"
528            },
529            currency: {
530                pattern: ["($n)", "$n"],
531                decimals: 2,
532                ",": ",",
533                ".": ".",
534                groupSize: [3],
535                symbol: "$"
536            }
537        },
538        calendars: {
539            standard: {
540                days: {
541                    names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
542                    namesAbbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
543                    namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ]
544                },
545                months: {
546                    names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
546November", "December"],
547                    namesAbbr: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
548                },
549                AM: [ "AM", "am", "AM" ],
550                PM: [ "PM", "pm", "PM" ],
551                patterns: {
552                    d: "M/d/yyyy",
553                    D: "dddd, MMMM dd, yyyy",
554                    F: "dddd, MMMM dd, yyyy h:mm:ss tt",
555                    g: "M/d/yyyy h:mm tt",
556                    G: "M/d/yyyy h:mm:ss tt",
557                    m: "MMMM dd",
558                    M: "MMMM dd",
559                    s: "yyyy'-'MM'-'ddTHH':'mm':'ss",
560                    t: "h:mm tt",
561                    T: "h:mm:ss tt",
562                    u: "yyyy'-'MM'-'dd HH':'mm':'ss'Z'",
563                    y: "MMMM, yyyy",
564                    Y: "MMMM, yyyy"
565                },
566                "/": "/",
567                ":": ":",
568                firstDay: 0
569            }
570        }
571    }};
572
573    /**
574     * @name kendo.ui.Globalization
575     * @namespace
576     */
577     /**
578     * @name kendo.ui.Globalization.Description
579     *
580     * @section Globalization is the process of designing and developing an
581     * application that works in multiple cultures. The culture defines specific information
582     * for the number formats, week and month names, date and time formats and etc.
583     *
584     * @section Kendo exposes <strong><em>culture(cultureName)</em></strong> method which allows to select the culture
585     * script coresponding to the "culture name". kendo.culture() method uses the passed culture name
586     * to select culture from the culture scripts that you have included and then sets the current culture.
587     * If there is no such culture, the default one is used.
588     *
589     * <h3>Define current culture settings</h3>
590     *
591     * @exampleTitle Include culture scripts and select culture
592     * @example
593     *
594     * <script src="jquery.js" ></script>
595     * <script src="kendo.all.min.js"></script>
596     * <script src="kendo.culture.en-GB.js"></script>
597     * <script type="text/javascript">
598     *    //set current culture to the "en-GB" culture script.
599     *    kendo.culture("en-GB");
600     * </script>
601     *
602     * @exampleTitle Get current culture
603     * @example
604     * var cultureInfo = kendo.culture();
605     *
606     * @section
607     * <h3>Format number or date object</h3>
608     *
609     * Kendo exposes methods which can format number or date object using specific format string and the current specified culture:
610     * @section
611     * <h4><code>kendo.toString(object, format)</code> - returns a string representation of the current object using specific format.</h4>
612     * @exampleTitle Formats number and date objects
613     * @example
614     * //format number using standard number format
615     * kendo.toString(10.12, "n"); //10.12
616     * kendo.toString(10.12, "n0"); //10
617     * kendo.toString(10.12, "n5"); //10.12000
618     * kendo.toString(10.12, "c"); //$10.12
619     * kendo.toString(0.12, "p"); //12.00 %
620     * //format number using custom number format
621     * kendo.toString(19.12, "00##"); //0019
622     * //format date
623     * kendo.toString(new Date(2010, 9, 5), "yyyy/MM/dd" ); // "2010/10/05"
624     * kendo.toString(new Date(2010, 9, 5), "dddd MMMM d, yyyy" ); // "Tuesday October 5, 2010"
625     * kendo.toString(new Date(2010, 10, 10, 22, 12), "hh:mm tt" ); // "10:12 PM"
626     *
627     * @section
628     * <h4><code>kendo.format</code> - replaces each format item in a specified string with the text equivalent of a corresponding object's value.</h4>
629     *  @exampleTitle String format
630     *  @example
631     *  kendo.format("{0} - {1}", 12, 24); //12 - 24
632     *  kendo.format("{0:c} - {1:c}", 12, 24); //$12.00 - $24.00
633     *
634     * @section
635     * <h3>Parsing a string</h3>
636     *
637     * Kendo exposes methods which converts the specified string to date or number object:
638     * <ol>
639     *    <li>
640     *       <code>kendo.parseInt(string, [culture])</code> - converts a string to a whole number using the specified culture (current culture by default).
641     *        @exampleTitle Parse string to integer
642     *        @example
643     *
644     *        //assumes that current culture defines decimal separator as "."
645     *        kendo.parseInt("12.22"); //12
646     *
647     *        //assumes that current culture defines decimal separator as ",", group separator as "." and currency symbol as "€"
648     *        kendo.parseInt("1.212,22 €"); //1212
649     *    </li>
650     *    <li>
651     *       <code>kendo.parseFloat(string, [culture])</code> - converts a string to a number with floating point using the specified culture (current culture by default).
652     *        @exampleTitle Parse string to float
653     *        @example
654     *
655     *        //assumes that current culture defines decimal separator as "."
656     *        kendo.parseFloat("12.22"); //12.22
657     *
658     *        //assumes that current culture defines decimal separator as ",", group separator as "." and currency symbol as "€"
659     *        kendo.parseFloat("1.212,22 €"); //1212.22
660     *    </li>
661     *    <li>
662     *       <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).
663     *       Current culture is used if one is not specified.
664     *        @exampleTitle Parse string to float
665     *        @example
666     *
667     *        //current culture is "en-US"
668     *        kendo.parseDate("12/22/2000"); //Fri Dec 22 2000
669     *        kendo.parseDate("2000/12/22", "yyyy/MM/dd"); //Fri Dec 22 2000
670     *    </li>
671     * </ol>
672     *
673     * @section
674     * <h3>Number formatting</h3>
675     * 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>
676     * methods support standard and custom numeric formats:
677     * <h4>Standard numeric formats</h4>
678     *<strong>n</strong> for number
679     *       @exampleTitle Formatting using "n" format
680     *       @example
681     *       kendo.culture("en-US");
682     *       kendo.toString(1234.567, "n"); //1,234.57
683     *
684     *       kendo.culture("de-DE");
685     *       kendo.toString(1234.567, "n3"); //1.234,567
686     *@section
687     *<strong>c</strong> for currency
688     *       @exampleTitle Formatting using "c" format
689     *       @example
690     *       kendo.culture("en-US");
691     *       kendo.toString(1234.567, "c"); //$1,234.57
692     *
693     *       kendo.culture("de-DE");
694     *       kendo.toString(1234.567, "c3"); //1.234,567 €
695     *@section
696     *<strong>
696p</strong> for percentage (number is multiplied by 100)
697     *       @exampleTitle Formatting using "p" format
698     *       @example
699     *       kendo.culture("en-US");
700     *       kendo.toString(0.222, "p"); //22.20 %
701     *
702     *       kendo.culture("de-DE");
703     *       kendo.toString(0.22, "p3"); //22.000 %
704     *@section
705     *<strong>e</strong> for exponential
706     *       @exampleTitle Formatting using "e" format
707     *       @example
708     *       kendo.toString(0.122, "e"); //1.22e-1
709     *       kendo.toString(0.122, "e4"); //1.2200e-1
710     *
711     * @section
712     * <h4>Custom numeric formats</h4>
713     * 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.
714     * <div class="details-list">
715     *   <h4 class="details-title">Format specifiers</h4>
716     *   <dl>
717     *     <dt>
718     *       "0" - zero placeholder
719     *     </dt>
720     *     <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>
721     *     <dt>
722     *       "#" - digit placeholder
723     *     </dt>
724     *     <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>
725     *     <dt>
726     *       "." - Decimal placeholder
727     *     </dt>
728     *     <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>
729     *     <dt>
730     *       "," - group separator placeholder
731     *     </dt>
732     *     <dd>Insert localized group separator between each group - <code>kendo.tostring(12345678, "##,#") -> 12,345,678</code>(en-us)</dd>
733     *     <dt>
734     *       "%" - percentage placeholder
735     *     </dt>
736     *     <dd>Multiplies a number by 100 and inserts a localized percentage symbol in the result string</dd>
737     *     <dt>
738     *       "e" - exponential notation
739     *     </dt>
740     *     <dd><code>kendo.toString(0.45678, "e0") -> 5e-1</code></dd>
741     *     <dt>
742     *       ";" - section separator
743     *     </dt>
744     *     <dd>Defines sections wih separate format strings for positive, negative, and zero numbers</dd>
745     *     <dt>
746     *       "string"/'string' - Literal string delimiter
747     *     </dt>
748     *     <dd>Indicates that the enclosed characters should be copied to the result string</dd>
749     *   </dl>
750     * </div>
751     *
752     * @section
753     * <h3>Date formatting</h3>
754     * 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>
755     * methods support standard and custom date formats:
756     * <h4>Standard date formats</h4>
757     * <div class="details-list">
758     *   <h4 class="details-title">Format specifiers</h4>
759     *   <dl>
760     *     <dt>
761     *       "d" - short date pattern
762     *     </dt>
763     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "d") -> 11/6/2000</code></dd>
764     *     <dt>
765     *       "D" - long date pattern
766     *     </dt>
767     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "D") -> Monday, November 06, 2000</code></dd>
768     *     <dt>
769     *       "F" - Full date/time pattern
770     *     </dt>
771     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "D") -> Monday, November 06, 2000 12:00:00 AM</code></dd>
772     *     <dt>
773     *       "g" - General date/time pattern (short time)
774     *     </dt>
775     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "g") -> 11/6/2000 12:00 AM</code></dd>
776     *     <dt>
777     *       "G" - General date/time pattern (long time)
778     *     </dt>
779     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "G") -> 11/6/2000 12:00:00 AM</code></dd>
780     *     <dt>
781     *       "M/m" - Month/day pattern
782     *     </dt>
783     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "m") -> November 06</code></dd>
784     *     <dt>
785     *       "u" - Universal sortable date/time pattern
786     *     </dt>
787     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "u") -> 2000-11-06 00:00:00Z</code></dd>
788     *     <dt>
789     *       "Y/y" - Year/month pattern
790     *     </dt>
791     *     <dd><code>kendo.toString(new Date(2000, 10, 6), "y") -> November, 2000</code></dd>
792     *   </dl>
793     * </div>
794     *
795     *@section
796     * <h4>Custom date formats</h4>
797     * <div class="details-list">
798     *   <h4 class="details-title">Format specifiers</h4>
799     *   <dl>
800     *     <dt>
801     *       "d"
802     *     </dt>
803     *     <dd>The day of the month, from 1 through 31</dd>
804     *     <dt>
805     *       "dd"
806     *     </dt>
807     *     <dd>The day of the month, from 01 through 31.</dd>
808     *     <dt>
809     *       "ddd"
810     *     </dt>
811     *     <dd>iThe abbreviated name of the day of the week</dd>
812     *     <dt>
813     *       "dddd"
814     *     </dt>
815     *     <dd>The full name of the day of the week</dd>
816     *     <dt>
817     *       "f"
818     *     </dt>
819     *     <dd>The tenths of a second in a date and time value</dd>
820     *     <dt>
821     *       "ff"
822     *     </dt>
823     *     <dd>The hundredths of a second in a date and time value</dd>
824     *     <dt>
825     *       "fff"
826     *     </dt>
827     *     <dd>The milliseconds in a date and time value</dd>
828     *     <dt>
829     *       "M"
830     *     </dt>
831     *     <dd>The month, from 1 through 12</dd>
832     *     <dt>
833     *       "MM"
834     *     </dt>
835     *     <dd>The month, from 01 through 12</dd>
836     *     <dt>
837     *       "MMM"
838     *     </dt>
839     *     <dd>The abbreviated name of the month</dd>
840     *     <dt>
841     *       "MMMM"
842     *     </dt>
843     *     <dd>The full name of the month</dd>
844     *     <dt>
845     *       "h"
846     *     </dt>
847     *     <dd>The hour, using a 12-hour clock from 1 to 12</dd>
848     *     <dt>
849     *       "hh"
850     *     </dt>
851     *     <dd>The hour, using a 12-hour clock from 01 to 12</dd>
852     *     <dt>
853     *       "H"
854     *     </dt>
855     *     <dd>The hour, using a 24-hour clock from 1 to 23</dd>
856     *     <dt>
857     *       "HH"
858     *     </dt>
859     *     <dd>The hour, using a 24-hour clock from 01 to 23</dd>
860     *     <dt>
861     *       "m"
862     *     </dt>
863     *     <dd>The minute, from 0 through 59</dd>
864     *     <dt>
865     *       "mm"
866     *     </dt>
867     *     <dd>The minute, from 00 through 59</dd>
868     *     <dt>
869     *       "s"
870     *     </dt>
871     *     <dd>The second, from 0 through 59</dd>
872     *     <dt>
873     *       "ss"
874     *     </dt>
875     *     <dd>The second, from 00 through 59</dd>
876     *     <dt>
877     *       "tt"
878     *     </dt>
879     *     <dd>The AM/PM designator</dd>
880     *   </dl>
881     * </div>
882     *
883     * @section
884     * <p><h3>Widgets that depend on current culture are:</h3>
885     *    <ul>
886     *        <li> Calendar </li>
887     *        <li> DatePicker </li>
888     *        <li> TimePicker </li>
889     *        <li> NumericTextBox </li>
890     *    </ul>
891     * </p>
892     */
893
894    kendo.culture = function(cultureName) {
895        if (cultureName !== undefined) {
896            var cultures = kendo.cultures,
897                culture = cultures[cultureName] || cultures[EN];
898
899            culture.calendar = culture.calendars.standard;
900            cultures.current = culture;
901        } else {
902            return kendo.cultures.current;
903        }
904    };
905
906    //set current culture to en-US.
907    kendo.culture(EN);
908
909    function formatDate(date, format) {
910        var calendar = kendo.cultures.current.calendar,
911            days = calendar.days,
912            months = calendar.months;
913
914        format = calendar.patterns[format] || format;
915
916        return format.replace(dateFormatRegExp, function (match) {
917            var result;
918
919            if (match === "d") {
920                result = date.getDate();
921            } else if (match === "dd") {
922                result = pad(date.getDate());
923            } else if (match === "ddd") {
924                result = days.namesAbbr[date.getDay()];
925            } else if (match === "dddd") {
926                result = days.names[date.getDay()];
927            } else if (match === "M") {
928                result = date.getMonth() + 1;
929            } else if (match === "MM") {
930                result = pad(date.getMonth() + 1);
931            } else if (match === "MMM") {
932                result = months.namesAbbr[date.getMonth()];
933            } else if (match === "MMMM") {
934                result = months.names[date.getMonth()];
935            } else if (match === "yy") {
936                result = pad(date.getFullYear() % 100);
937            } else if (match === "yyyy") {
938                result = date.getFullYear();
939            } else if (match === "h" ) {
940                result = date.getHours() % 12 || 12;
941            } else if (match === "hh") {
942                result = pad(date.getHours() % 12 || 12);
943            } else if (match === "H") {
944                result = date.getHours();
945            } else if (match === "HH") {
946                result = pad(date.getHours());
947            } else if (match === "m") {
948                result = date.getMinutes();
949            } else if (match === "mm") {
950                result = pad(date.getMinutes());
951            } else if (match === "s") {
952                result = date.getSeconds();
953            } else if (match === "ss") {
954                result = pad(date.getSeconds());
955            } else if (match === "f") {
956                result = math.floor(date.getMilliseconds() / 100);
957            } else if (match === "ff") {
958                result = math.floor(date.getMilliseconds() / 10);
959            } else if (match === "fff") {
960                result = date.getMilliseconds();
961            } else if (match === "tt") {
962                result = date.getHours() < 12 ? calendar.AM[0] : calendar.PM[0];
963            }
964
965            return result !== undefined ? result : match.slice(1, match.length - 1);
966        });
967    }
968
969    //number formatting
970    function formatNumber(number, format) {
971        var culture = kendo.cultures.current,
972            numberFormat = culture.numberFormat,
973            groupSize = numberFormat.groupSize[0],
974            groupSeparator = numberFormat[COMMA],
975            decimal = numberFormat[POINT],
976            precision = numberFormat.decimals,
977            pattern = numberFormat.pattern[0],
978            literals = [],
979            symbol,
980            isCurrency, isPercent,
981            customPrecision,
982            formatAndPrecision,
983            negative = number < 0,
984            integer,
985            fraction,
986            integerLength,
987            fractionLength,
988            replacement = EMPTY,
989            value = EMPTY,
990            idx,
991            length,
992            ch,
993            hasGroup,
994            decimalIndex,
995            sharpIndex,
996            zeroIndex,
997            startZeroIndex,
998            start = -1,
999            end;
1000
1001        //return empty string if no number
1002        if (number === undefined) {
1003            return EMPTY;
1004        }
1005
1006        if (!isFinite(number)) {
1007            return number;
1008        }
1009
1010        //if no format then return number.toString() or number.toLocaleString() if culture.name is not defined
1011        if (!format) {
1012            return culture.name.length ? number.toLocaleString() : number.toString();
1013        }
1014
1015        formatAndPrecision = standardFormatRegExp.exec(format);
1016
1017        /* standard formatting */
1018        if (formatAndPrecision) {
1019            format = formatAndPrecision[1].toLowerCase();
1020
1021            isCurrency = format === "c";
1022            isPercent = format === "p";
1023
1024            if (isCurrency || isPercent) {
1025                //get specific number format information if format is currency or percent
1026                numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
1027                groupSize = numberFormat.groupSize[0];
1028                groupSeparator = numberFormat[COMMA];
1029                decimal = numberFormat[POINT];
1030                precision = numberFormat.decimals;
1031                symbol = numberFormat.symbol;
1032                pattern = numberFormat.pattern[negative ? 0 : 1];
1033            }
1034
1035            customPrecision = formatAndPrecision[2];
1036
1037            if (customPrecision) {
1038                precision = +customPrecision;
1039            }
1040
1041            //return number in exponential format
1042            if (format === "e") {
1043                return customPrecision ? number.toExponential(precision) : number.toExponential(); // toExponential() and toExponential(undefined) differ in FF #653438.
1044            }
1045
1046            // multiply if format is percent
1047            if (isPercent) {
1048                number *= 100;
1049            }
1050
1051            number = number.toFixed(precision);
1052            number = number.split(POINT);
1053
1054            integer = number[0];
1055            fraction = number[1];
1056
1057            //exclude "-" if number is negative.
1058            if (negative) {
1059                integer = integer.substring(1);
1060            }
1061
1062            value = integer;
1063            integerLength = integer.length;
1064
1065            //add group separator to the number if it is longer enough
1066            if (integerLength >= groupSize) {
1067                value = EMPTY;
1068                for (idx = 0; idx < integerLength; idx++) {
1069                    if (idx > 0 && (integerLength - idx) % groupSize === 0) {
1070                        value += groupSeparator;
1071                    }
1072                    value += integer.charAt(idx);
1073                }
1074            }
1075
1076            if (fraction) {
1077                value += decimal + fraction;
1078            }
1079
1080            if (format === "n" && !negative) {
1081                return value;
1082            }
1083
1084            number = EMPTY;
1085
1086            for (idx = 0, length = pattern.length; idx < length; idx++) {
1087                ch = pattern.charAt(idx);
1088
1089                if (ch === "n") {
1090                    number += value;
1091                } else if (ch === "$" || ch === "%") {
1092                    number += symbol;
1093                } else {
1094                    number += ch;
1095                }
1096            }
1097
1098            return number;
1099        }
1100
1101        //custom formatting
1102        //
1103        //separate format by sections.
1104        format = format.split(";");
1105        if (negative && format[1]) {
1106            //make number positive and get negative format
1107            number = -number;
1108            format = format[1];
1109        } else if (number === 0) {
1110            //format for zeros
1111            format = format[2] || format[0];
1112            if (format.indexOf(SHARP) == -1 && format.indexOf(ZERO) == -1) {
1113                //return format if it is string constant.
1114                return format;
1115            }
1116        } else {
1117            format = format[0];
1118        }
1119
1120        if (format.indexOf("'") > -1 || format.indexOf("\"") > -1) {
1121            format = format.replace(literalRegExp, function(match) {
1122                literals.push(match);
1123                return PLACEHOLDER;
1124            });
1125        }
1126
1127        isCurrency = format.indexOf("$") != -1;
1128        isPercent = format.indexOf("%") != -1;
1129
1130        //multiply number if the format has percent
1131        if (isPercent) {
1132            number *= 100;
1133        }
1134
1135        if (isCurrency || isPercent) {
1136            //get specific number format information if format is currency or percent
1137            numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
1138            groupSize = numberFormat.groupSize[0];
1139            groupSeparator = numberFormat[COMMA];
1140            decimal = numberFormat[POINT];
1141            precision = numberFormat.decimals;
1142            symbol = numberFormat.symbol;
1143        }
1144
1145        hasGroup = format.indexOf(COMMA) > -1;
1146        if (hasGroup) {
1147            format = format.replace(commaRegExp, EMPTY);
1148        }
1149
1150        decimalIndex = format.indexOf(POINT);
1151        length = format.length;
1152
1153        if (decimalIndex != -1) {
1154            zeroIndex = format.lastIndexOf(ZERO);
1155            sharpIndex = format.lastIndexOf(SHARP);
1156            fraction = number.toString().split(POINT)[1] || EMPTY;
1157
1158            if (sharpIndex > zeroIndex && fraction.length > (sharpIndex - zeroIndex)) {
1159                idx = sharpIndex;
1160            } else if (zeroIndex != -1 && zeroIndex >= decimalIndex) {
1161                idx = zeroIndex;
1162            }
1163
1164            if (idx) {
1165                number = number.toFixed(idx - decimalIndex);
1166            }
1167
1168        } else {
1169            number = number.toFixed(0);
1170        }
1171
1172        sharpIndex = format.indexOf(SHARP);
1173        startZeroIndex = zeroIndex = format.indexOf(ZERO);
1174
1175        //define the index of the first digit placeholder
1176        if (sharpIndex == -1 && zeroIndex != -1) {
1177            start = zeroIndex;
1178        } else if (sharpIndex != -1 && zeroIndex == -1) {
1179            start = sharpIndex;
1180        } else {
1181            start = sharpIndex > zeroIndex ? zeroIndex : sharpIndex;
1182        }
1183
1184        sharpIndex = format.lastIndexOf(SHARP);
1185        zeroIndex = format.lastIndexOf(ZERO);
1186
1187        //define the index of the last digit placeholder
1188        if (sharpIndex == -1 && zeroIndex != -1) {
1189            end = zeroIndex;
1190        } else if (sharpIndex != -1 && zeroIndex == -1) {
1191            end = sharpIndex;
1192        } else {
1193            end = sharpIndex > zeroIndex ? sharpIndex : zeroIndex;
1194        }
1195
1196        if (start == length) {
1197            end = start;
1198        }
1199
1200        if (start != -1) {
1201            value = number.toString().split(POINT);
1202            integer = value[0];
1203            fraction = value[1] || EMPTY;
1204
1205            integerLength = integer.length;
1206            fractionLength = fraction.length;
1207
1208            //add group separator to the number if it is longer enough
1209            if (hasGroup) {
1210                if (integerLength === groupSize && integerLength < decimalIndex - startZeroIndex) {
1211                    integer = groupSeparator + integer;
1212                } else if (integerLength > groupSize) {
1213                    value = EMPTY;
1214                    for (idx = 0; idx < integerLength; idx++) {
1215                        if (idx > 0 && (integerLength - idx) % groupSize === 0) {
1216                            value += groupSeparator;
1217                        }
1218                        value += integer.charAt(idx);
1219                    }
1220
1221                    integer = value;
1222                }
1223            }
1224
1225            number = format.substring(0, start);
1226
1227            for (idx = start; idx < length; idx++) {
1228                ch = format.charAt(idx);
1229
1230                if (decimalIndex == -1) {
1231                    if (end - idx < integerLength) {
1232                        number += integer;
1233                        break;
1234                    }
1235                } else {
1236                    if (zeroIndex != -1 && zeroIndex < idx) {
1237                        replacement = EMPTY;
1238                    }
1239
1240                    if ((decimalIndex - idx) <= integerLength && decimalIndex - idx > -1) {
1241                        number += integer;
1242                        idx = decimalIndex;
1243                    }
1244
1245                    if (decimalIndex === idx) {
1246                        number += (fraction ? decimal : EMPTY) + fraction;
1247                        idx += end - decimalIndex + 1;
1248                        continue;
1249                    }
1250                }
1251
1252                if (ch === ZERO) {
1253                    number += ch;
1254                    replacement = ch;
1255                } else if (ch === SHARP) {
1256                    number += replacement;
1257                }
1258            }
1259
1260            if (end >= start) {
1261                number += format.substring(end + 1);
1262            }
1263
1264            //replace symbol placeholders
1265            if (isCurrency || isPercent) {
1266                value = EMPTY;
1267                for (idx = 0, length = number.length; idx < length; idx++) {
1268                    ch = number.charAt(idx);
1269                    value += (ch === "$" || ch === "%") ? symbol : ch;
1270                }
1271                number = value;
1272            }
1273
1274            if (literals[0]) {
1275                length = literals.length;
1276                for (idx = 0; idx < length; idx++) {
1277                    number = number.replace(PLACEHOLDER, literals[idx]);
1278                }
1279            }
1280        }
1281
1282        return number;
1283    }
1284
1285    var toString = function(value, fmt) {
1286        if (fmt) {
1287            if (value instanceof Date) {
1288                return formatDate(value, fmt);
1289            } else if (typeof value === NUMBER) {
1290                return formatNumber(value, fmt);
1291            }
1292        }
1293
1294        return value !== undefined ? value : "";
1295    };
1296
1297    if (globalize) {
1298        toString = proxy(globalize.format, globalize);
1299    }
1300
1301    kendo.format = function(fmt) {
1302        var values = arguments;
1303
1304        return fmt.replace(formatRegExp, function(match, index, placeholderFormat) {
1305            var value = values[parseInt(index, 10) + 1];
1306
1307            return toString(value, placeholderFormat ? placeholderFormat.substring(1) : "");
1308        });
1309    };
1310
1311    kendo.toString = toString;
1312    })();
1313
1314
1315(function() {
1316
1317    var nonBreakingSpaceRegExp = /\u00A0/g,
1318        exponentRegExp = /[eE][\-+]?[0-9]+/,
1319        formatsSequence = ["G", "g", "d", "F", "D", "y", "m", "T", "t"];
1320
1321    function outOfRange(value, start, end) {
1322        return !(value >= start && value <= end);
1323    }
1324
1325    function parseExact(value, format, culture) {
1326        if (!value) {
1327            return null;
1328        }
1329
1330        var lookAhead = function (match) {
1331                var i = 0;
1332                while (format[idx] === match) {
1333                    i++;
1334                    idx++;
1335                }
1336                if (i > 0) {
1337                    idx -= 1;
1338                }
1339                return i;
1340            },
1341            getNumber = function(size) {
1342                var rg = new RegExp('^\\d{1,' + size + '}'),
1343                    match = value.substr(valueIdx, size).match(rg);
1344
1345                if (match) {
1346                    match = match[0];
1347                    valueIdx += match.length;
1348                    return parseInt(match, 10);
1349                }
1350                return null;
1351            },
1352            getIndexByName = function (names) {
1353                var i = 0,
1354                    length = names.length,
1355                    name, nameLength;
1356
1357                for (; i < length; i++) {
1358                    name = names[i];
1359                    nameLength = name.length;
1360
1361                    if (value.substr(valueIdx, nameLength) == name) {
1362                        valueIdx += nameLength;
1363                        return i + 1;
1364                    }
1365                }
1366                return null;
1367            },
1368            checkLiteral = function() {
1369                if (value.charAt(valueIdx) == format[idx]) {
1370                    valueIdx++;
1371                }
1372            },
1373            calendar = culture.calendar,
1374            year = null,
1375            month = null,
1376            day = null,
1377            hours = null,
1378            minutes = null,
1379            seconds = null,
1380            milliseconds = null,
1381            idx = 0,
1382            valueIdx = 0,
1383            literal = false,
1384            date = new Date(),
1385            defaultYear = date.getFullYear(),
1386            shortYearCutOff = 30,
1387            ch, count, pmHour, length, pattern;
1388
1389        if (!format) {
1390            format = "d"; //shord date format
1391        }
1392
1393        //if format is part of the patterns get real format
1394        pattern = calendar.patterns[format];
1395        if (pattern) {
1396            format = pattern;
1397        }
1398
1399        format = format.split("");
1400        length = format.length;
1401
1402        for (; idx < length; idx++) {
1403            ch = format[idx];
1404
1405            if (literal) {
1406                if (ch === "'") {
1407                    literal = false;
1408                } else {
1409                    checkLiteral();
1410                }
1411            } else {
1412                if (ch === "d") {
1413                    count = lookAhead("d");
1414                    day = count < 3 ? getNumber(2) : getIndexByName(calendar.days[count == 3 ? "namesAbbr" : "names"]);
1415
1416                    if (day === null || outOfRange(day, 1, 31)) {
1417                        return null;
1418                    }
1419                } else if (ch === "M") {
1420                    count = lookAhead("M");
1421                    month = count < 3 ? getNumber(2) : getIndexByName(calendar.months[count == 3 ? 'namesAbbr' : 'names']);
1422
1423                    if (month === null || outOfRange(month, 1, 12)) {
1424                        return null;
1425                    }
1426                    month -= 1; //because month is zero based
1427                } else if (ch === "y") {
1428                    count = lookAhead("y");
1429                    year = getNumber(count < 3 ? 2 : 4);
1430                    if (year === null) {
1431                        year = defaultYear;
1432                    }
1433                    if (year < shortYearCutOff) {
1434                        year = (defaultYear - defaultYear % 100) + year;
1435                    }
1436                } else if (ch === "h" ) {
1437                    lookAhead("h");
1438                    hours = getNumber(2);
1439                    if (hours == 12) {
1440                        hours = 0;
1441                    }
1442                    if (hours === null || outOfRange(hours, 0, 11)) {
1443                        return null;
1444                    }
1445                } else if (ch === "H") {
1446                    lookAhead("H");
1447                    hours = getNumber(2);
1448                    if (hours === null || outOfRange(hours, 0, 23)) {
1449                        return null;
1450                    }
1451                } else if (ch === "m") {
1452                    lookAhead("m");
1453                    minutes = getNumber(2);
1454                    if (minutes === null || outOfRange(minutes, 0, 59)) {
1455                        return null;
1456                    }
1457                } else if (ch === "s") {
1458                    lookAhead("s");
1459                    seconds = getNumber(2);
1460                    if (seconds === null || outOfRange(seconds, 0, 59)) {
1461                        return null;
1462                    }
1463                } else if (ch === "f") {
1464                    count = lookAhead("f");
1465                    milliseconds = getNumber(count);
1466                    if (milliseconds === null || outOfRange(milliseconds, 0, 999)) {
1467                        return null;
1468                    }
1469                } else if (ch === "t") {
1470                    count = lookAhead("t");
1471                    pmHour = getIndexByName(calendar.PM);
1472                } else if (ch === "'") {
1473                    checkLiteral();
1474                    literal = true;
1475                } else {
1476                    checkLiteral();
1477                }
1478            }
1479        }
1480
1481        if (pmHour && hours < 12) {
1482            hours += 12;
1483        }
1484
1485        if (day === null) {
1486            day = 1;
1487        }
1488
1489        return new Date(year, month, day, hours, minutes, seconds, milliseconds);
1490    }
1491
1492    kendo.parseDate = function(value, formats, culture) {
1493        if (value instanceof Date) {
1494            return value;
1495        }
1496
1497        var idx = 0,
1498            date = null,
1499            length, patterns;
1500
1501        if (!culture) {
1502            culture = kendo.culture();
1503        } else if (typeof culture === STRING) {
1504            kendo.culture(culture);
1505            culture = kendo.culture();
1506        }
1507
1508        if (!formats) {
1509            formats = [];
1510            patterns = culture.calendar.patterns;
1511            length = formatsSequence.length;
1512
1513            for (; idx < length; idx++) {
1514                formats[idx] = patterns[formatsSequence[idx]];
1515            }
1516            formats[idx] = "ddd MMM dd yyyy HH:mm:ss";
1517
1518            idx = 0;
1519        }
1520
1521        formats = isArray(formats) ? formats: [formats];
1522        length = formats.length;
1523
1524        for (; idx < length; idx++) {
1525            date = parseExact(value, formats[idx], culture);
1526            if (date) {
1527                return date;
1528            }
1529        }
1530
1531        return date;
1532    };
1533
1534    kendo.parseInt = function(value, culture) {
1535        var result = kendo.parseFloat(value, culture);
1536        if (result) {
1537            result = result | 0;
1538        }
1539        return result;
1540    };
1541
1542    kendo.parseFloat = function(value, culture) {
1543        if (!value && value !== 0) {
1544           return null;
1545        }
1546
1547        if (typeof value === NUMBER) {
1548           return value;
1549        }
1550
1551        value = value.toString();
1552        culture = kendo.cultures[culture] || kendo.cultures.current;
1553
1554        var number = culture.numberFormat,
1555            percent = number.percent,
1556            currency = number.currency,
1557            symbol = currency.symbol,
1558            percentSymbol = percent.symbol,
1559            negative = value.indexOf("-") > -1,
1560            parts, isPercent;
1561
1562        //handle exponential number
1563        if (exponentRegExp.test(value)) {
1564            value = parseFloat(value);
1565            if (isNaN(value)) {
1566                value = null;
1567            }
1568            return value;
1569        }
1570
1571        if (value.indexOf(symbol) > -1) {
1572            number = currency;
1573            parts = number.pattern[0].replace("$", symbol).split("n");
1574            if (value.indexOf(parts[0]) > -1 && value.indexOf(parts[1]) > -1) {
1575                value = value.replace(parts[0], "").replace(parts[1], "");
1576                negative = true;
1577            }
1578        } else if (value.indexOf(percentSymbol) > -1) {
1579            isPercent = true;
1580            number = percent;
1581            symbol = percentSymbol;
1582        }
1583
1584        value = value.replace("-", "")
1585                     .replace(symbol, "")
1586                     .replace(nonBreakingSpaceRegExp, " ")
1587                     .split(number[","].replace(nonBreakingSpaceRegExp, " ")).join("")
1588                     .replace(number["."], ".");
1589
1590        value = parseFloat(value);
1591
1592        if (isNaN(value)) {
1593            value = null;
1594        } else if (negative) {
1595            value *= -1;
1596        }
1597
1598        if (value && isPercent) {
1599            value /= 100;
1600        }
1601
1602        return value;
1603    };
1604
1605    if (globalize) {
1606        kendo.parseDate = proxy(globalize.parseDate, globalize);
1607        kendo.parseFloat = proxy(globalize.parseFloat, globalize);
1608    }
1609})();
1610
1611    function wrap(element) {
1612        var browser = $.browser;
1613
1614        if (!element.parent().hasClass("k-animation-container")) {
1615            var shadow = element.css(kendo.support.transitions.css + "box-shadow") || element.css("box-shadow"),
1616                radius = shadow ? shadow.match(boxShadowRegExp) || [ 0, 0, 0, 0, 0 ] : [ 0, 0, 0, 0, 0 ],
1617                blur = math.max((+radius[3]), +(radius[4] || 0)),
1618                left = (-radius[1]) + blur,
1619                right = (+radius[1]) + blur,
1620                bottom = (+radius[2]) + blur;
1621
1622            if (browser.opera) { // Box shadow can't be retrieved 
1622in Opera
1623                left = right = bottom = 5;
1624            }
1625
1626            element.wrap(
1627                         $("<div/>")
1628                         .addClass("k-animation-container")
1629                         .css({
1630                             width: element.outerWidth(),
1631                             height: element.outerHeight(),
1632                             marginLeft: -left,
1633                             paddingLeft: left,
1634                             paddingRight: right,
1635                             paddingBottom: bottom
1636                         }));
1637        } else {
1638            var wrapper = element.parent(".k-animation-container");
1639
1640            if (wrapper.is(":hidden")) {
1641                wrapper.show();
1642            }
1643
1644            wrapper.css({
1645                    width: element.outerWidth(),
1646                    height: element.outerHeight()
1647                });
1648        }
1649
1650        if (browser.msie && math.floor(browser.version) <= 7) {
1651            element.css({
1652                zoom: 1
1653            });
1654        }
1655
1656        return element.parent();
1657    }
1658
1659    function deepExtend(destination) {
1660        var i = 1,
1661            length = arguments.length;
1662
1663        for (i = 1; i < length; i++) {
1664            deepExtendOne(destination, arguments[i]);
1665        }
1666
1667        return destination;
1668    }
1669
1670    function deepExtendOne(destination, source) {
1671        var property,
1672            propValue,
1673            propType,
1674            destProp;
1675
1676        for (property in source) {
1677            propValue = source[property];
1678            propType = typeof propValue;
1679            if (propType === OBJECT && propValue !== null && propValue.constructor !== Array) {
1680                destProp = destination[property];
1681                if (typeof (destProp) === OBJECT) {
1682                    destination[property] = destProp || {};
1683                } else {
1684                    destination[property] = {};
1685                }
1686                deepExtendOne(destination[property], propValue);
1687            } else if (propType !== UNDEFINED) {
1688                destination[property] = propValue;
1689            }
1690        }
1691
1692        return destination;
1693    }
1694
1695    /**
1696     * Contains results from feature detection.
1697     * @name kendo.support
1698     * @namespace Contains results from feature detection.
1699     */
1700    (function() {
1701        /**
1702         * 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).
1703         * @name kendo.support.scrollbar
1704         * @property {Boolean}
1705         */
1706        support.scrollbar = function() {
1707            var div = document.createElement("div"),
1708                result;
1709
1710            div.style.cssText = "overflow:scroll;overflow-x:hidden;zoom:1;clear:both";
1711            div.innerHTML = "&nbsp;";
1712            document.body.appendChild(div);
1713
1714            result = div.offsetWidth - div.scrollWidth;
1715
1716            document.body.removeChild(div);
1717            return result;
1718        };
1719
1720        var table = document.createElement("table");
1721
1722        // Internet Explorer does not support setting the innerHTML of TBODY and TABLE elements
1723        try {
1724            table.innerHTML = "<tr><td></td></tr>";
1725
1726            /**
1727             * Indicates whether the browser supports setting of the &lt;tbody&gt; innerHtml.
1728             * @name kendo.support.tbodyInnerHtml
1729             * @property {Boolean}
1730             */
1731            support.tbodyInnerHtml = true;
1732        } catch (e) {
1733            support.tbodyInnerHtml = false;
1734        }
1735
1736        /**
1737         * Indicates whether the browser supports touch events.
1738         * @name kendo.support.touch
1739         * @property {Boolean}
1740         */
1741        support.touch = "ontouchstart" in window;
1742        support.pointers = navigator.msPointerEnabled;
1743
1744        /**
1745         * Indicates whether the browser supports CSS transitions.
1746         * @name kendo.support.transitions
1747         * @property {Boolean}
1748         */
1749        var transitions = support.transitions = false;
1750        var transforms = support.transforms = false;
1751
1752        /**
1753         * Indicates whether the browser supports hardware 3d transitions.
1754         * @name kendo.support.hasHW3D
1755         * @property {Boolean}
1756         */
1757        support.hasHW3D = ("WebKitCSSMatrix" in window && "m11" in new window.WebKitCSSMatrix()) || "MozPerspective" in document.documentElement.style;
1758        support.hasNativeScrolling = typeof document.documentElement.style.webkitOverflowScrolling == "string";
1759
1760        each([ "Moz", "webkit", "O", "ms" ], function () {
1761            var prefix = this.toString(),
1762                hasTransitions = typeof table.style[prefix + "Transition"] === STRING;
1763
1764            if (hasTransitions || typeof table.style[prefix + "Transform"] === STRING) {
1765                var lowPrefix = prefix.toLowerCase();
1766
1767                transforms = {
1768                    css: "-" + lowPrefix + "-",
1769                    prefix: prefix,
1770                    event: (lowPrefix === "o" || lowPrefix === "webkit") ? lowPrefix : ""
1771                };
1772
1773                if (hasTransitions) {
1774                    transitions = transforms;
1775                    transitions.event = transitions.event ? transitions.event + "TransitionEnd" : "transitionend";
1776                }
1777
1778                return false;
1779            }
1780        });
1781
1782        support.transforms = transforms;
1783        support.transitions = transitions;
1784
1785        support.detectOS = function (ua) {
1786            var os = false, minorVersion, match = [],
1787                agentRxs = {
1788                    fire: /(Silk)\/(\d+)\.(\d+(\.\d+)?)/,
1789                    android: /(Android|Android.*(?:Opera|Firefox).*?\/)\s*(\d+)\.(\d+(\.\d+)?)/,
1790                    iphone: /(iPhone|iPod).*OS\s+(\d+)[\._]([\d\._]+)/,
1791                    ipad: /(iPad).*OS\s+(\d+)[\._]([\d_]+)/,
1792                    meego: /(MeeGo).+NokiaBrowser\/(\d+)\.([\d\._]+)/,
1793                    webos: /(webOS)\/(\d+)\.(\d+(\.\d+)?)/,
1794                    blackberry: /(BlackBerry).*?Version\/(\d+)\.(\d+(\.\d+)?)/,
1795                    playbook: /(PlayBook).*?Tablet\s*OS\s*(\d+)\.(\d+(\.\d+)?)/,
1796                    winphone: /(IEMobile)\/(\d+)\.(\d+(\.\d+)?)/,
1797                    windows: /(MSIE)\s+(\d+)\.(\d+(\.\d+)?)/
1798                },
1799                osRxs = {
1800                    ios: /^i(phone|pad|pod)$/i,
1801                    android: /^android|fire$/i,
1802                    blackberry: /^blackberry|playbook/i,
1803                    windows: /windows|winphone/
1804                },
1805                browserRxs = {
1806                    omini: /Opera\sMini/i,
1807                    omobile: /Opera\sMobi/i,
1808                    firefox: /Firefox|Fennec/i,
1809                    webkit: /webkit/i,
1810                    ie: /MSIE|Windows\sPhone/i
1811                },
1812                testOs = function (agent) {
1813                    for (var os in osRxs) {
1814                        if (osRxs.hasOwnProperty(os) && osRxs[os].test(agent)) {
1815                            return os;
1816                        }
1817                    }
1818                    return agent;
1819                },
1820                testBrowser = function (ua) {
1821                    for (var browser in browserRxs) {
1822                        if (browserRxs.hasOwnProperty(browser) && browserRxs[browser].test(ua)) {
1823                            return browser;
1824                        }
1825                    }
1826                    return "default";
1827                };
1828
1829            for (var agent in agentRxs) {
1830                if (agentRxs.hasOwnProperty(agent)) {
1831                    match = ua.match(agentRxs[agent]);
1832                    if (match) {
1833                        if (agent == "windows" && "plugins" in navigator) { return false; } // Break if not Metro/Mobile Windows
1834
1835                        os = {};
1836                        os.device = agent;
1837                        os.browser = testBrowser(ua);
1838                        os.name = testOs(agent);
1839                        os[os.name] = true;
1840                        os.majorVersion = match[2];
1841                        os.minorVersion = match[3].replace("_", ".");
1842                        minorVersion = os.minorVersion.replace(".", "").substr(0, 2);
1843                        os.flatVersion = os.majorVersion + minorVersion + (new Array(3 - (minorVersion.length < 3 ? minorVersion.length : 2)).join("0"));
1844                        os.appMode = window.navigator.standalone || (/file|local/).test(window.location.protocol) || typeof window.PhoneGap !== UNDEFINED; // Use file protocol to detect appModes.
1845
1846                        break;
1847                    }
1848                }
1849            }
1850            return os;
1851        };
1852
1853        /**
1854         * Parses the mobile OS type and version from the browser user agent.
1855         * @name kendo.support.mobileOS
1856         */
1857        support.mobileOS = support.detectOS(navigator.userAgent);
1858
1859        support.zoomLevel = function() {
1860            return support.touch ? (document.documentElement.clientWidth / window.innerWidth) : 1;
1861        };
1862
1863        /**
1864         * Indicates the browser device pixel ratio.
1865         * @name kendo.support.devicePixelRatio
1866         * @property {Float}
1867         */
1868        support.devicePixelRatio = window.devicePixelRatio === undefined ? 1 : window.devicePixelRatio;
1869
1870        /**
1871         * Indicates the browser support for event capturing
1872         * @name kendo.support.eventCapture
1873         * @property {Boolean}
1874         */
1875        support.eventCapture = document.documentElement.addEventListener;
1876
1877        /**
1878         * Indicates whether the browser supports input placeholder.
1879         * @name kendo.support.placeholder
1880         * @property {Boolean}
1881         */
1882        support.placeholder = "placeholder" in document.createElement("input");
1883        support.stableSort = (function() {
1884            var sorted = [0,1,2,3,4,5,6,7,8,9,10,11,12].sort(function() { return 0; } );
1885            return sorted[0] === 0 && sorted[1] === 1 && sorted[2] === 2 && sorted[3] === 3 && sorted[4] === 4 &&
1886                sorted[5] === 5 && sorted[6] === 6 && sorted[7] === 7 && sorted[8] === 8 &&
1887                sorted[9] === 9 && sorted[10] === 10 && sorted[11] === 11 && sorted[12] === 12;
1888        })();
1889
1890    })();
1891
1892    /**
1893     * Exposed by jQuery.
1894     * @ignore
1895     * @name jQuery.fn
1896     * @namespace Handy jQuery plug-ins that are used by all Kendo widgets.
1897     */
1898
1899    function size(obj) {
1900        var result = 0, key;
1901        for (key in obj) {
1902            if (obj.hasOwnProperty(key)) {
1903                result++;
1904            }
1905        }
1906
1907        return result;
1908    }
1909
1910    function isNodeEmpty(element) {
1911        return $.trim($(element).contents().filter(function () { return this.nodeType != 8; }).html()) === "";
1912    }
1913
1914    function getOffset(element, type) {
1915        if (!type) {
1916            type = "offset";
1917        }
1918
1919        var result = element[type](),
1920            mobileOS = support.mobileOS;
1921
1922        if (support.touch && mobileOS.ios && mobileOS.flatVersion < 410) { // Extra processing only in broken iOS'
1923            var offset = type == "offset" ? result : element.offset(),
1924                positioned = (result.left == offset.left && result.top == offset.top);
1925
1926            if (positioned) {
1927                return {
1928                    top: result.top - window.scrollY,
1929                    left: result.left - window.scrollX
1930                };
1931            }
1932        }
1933
1934        return result;
1935    }
1936
1937    var directions = {
1938        left: { reverse: "right" },
1939        right: { reverse: "left" },
1940        down: { reverse: "up" },
1941        up: { reverse: "down" },
1942        top: { reverse: "bottom" },
1943        bottom: { reverse: "top" },
1944        "in": { reverse: "out" },
1945        out: { reverse: "in" }
1946    };
1947
1948    function parseEffects(input) {
1949        var effects = {};
1950
1951        each((typeof input === "string" ? input.split(" ") : input), function(idx) {
1952            effects[idx] = this;
1953        });
1954
1955        return effects;
1956    }
1957
1958    var fx = {
1959        promise: function (element, options) {
1960            if (options.show) {
1961                element.css({ display: element.data("olddisplay") || "block" }).css("display");
1962            }
1963
1964            if (options.hide) {
1965                element.data("olddisplay", element.css("display")).hide();
1966            }
1967
1968            if (options.init) {
1969                options.init();
1970            }
1971
1972            if (options.completeCallback) {
1973                options.completeCallback(element);
1973 // call the external complete callback with the element
1974            }
1975
1976            element.dequeue();
1977        },
1978
1979        transitionPromise: function(element, destination, options) {
1980            var container = kendo.wrap(element);
1981            container.append(destination);
1982
1983            element.hide();
1984            destination.show();
1985
1986            if (options.completeCallback) {
1987                options.completeCallback(element); // call the external complete callback with the element
1988            }
1989
1990            return element;
1991        }
1992    };
1993
1994    function prepareAnimationOptions(options, duration, reverse, complete) {
1995        if (typeof options === STRING) {
1996            // options is the list of effect names separated by space e.g. animate(element, "fadeIn slideDown")
1997
1998            // only callback is provided e.g. animate(element, options, function() {});
1999            if (isFunction(duration)) {
2000                complete = duration;
2001                duration = 400;
2002                reverse = false;
2003            }
2004
2005            if (isFunction(reverse)) {
2006                complete = reverse;
2007                reverse = false;
2008            }
2009
2010            if (typeof duration === BOOLEAN){
2011                reverse = duration;
2012                duration = 400;
2013            }
2014
2015            options = {
2016                effects: options,
2017                duration: duration,
2018                reverse: reverse,
2019                complete: complete
2020            };
2021        }
2022
2023        return extend({
2024            //default options
2025            effects: {},
2026            duration: 400, //jQuery default duration
2027            reverse: false,
2028            init: noop,
2029            teardown: noop,
2030            hide: false,
2031            show: false
2032        }, options, { completeCallback: options.complete, complete: noop }); // Move external complete callback, so deferred.resolve can be always executed.
2033
2034    }
2035
2036    function animate(element, options, duration, reverse, complete) {
2037        element.each(function (idx, el) { // fire separate queues on every element to separate the callback elements
2038            el = $(el);
2039            el.queue(function () {
2040                fx.promise(el, prepareAnimationOptions(options, duration, reverse, complete));
2041            });
2042        });
2043
2044        return element;
2045    }
2046
2047    function animateTo(element, destination, options, duration, reverse, complete) {
2048        return fx.transitionPromise(element, destination, prepareAnimationOptions(options, duration, reverse, complete));
2049    }
2050
2051    extend($.fn, /** @lends jQuery.fn */{
2052        kendoStop: function(clearQueue, gotoEnd) {
2053            return this.stop(clearQueue, gotoEnd);
2054        },
2055
2056        kendoAnimate: function(options, duration, reverse, complete) {
2057            return animate(this, options, duration, reverse, complete);
2058        },
2059
2060        kendoAnimateTo: function(destination, options, duration, reverse, complete) {
2061            return animateTo(this, destination, options, duration, reverse, complete);
2062        }
2063    });
2064
2065    function toggleClass(element, classes, options, add) {
2066        if (classes) {
2067            classes = classes.split(" ");
2068
2069            each(classes, function(idx, value) {
2070                element.toggleClass(value, add);
2071            });
2072        }
2073
2074        return element;
2075    }
2076
2077    extend($.fn, /** @lends jQuery.fn */{
2078        kendoAddClass: function(classes, options){
2079            return toggleClass(this, classes, options, true);
2080        },
2081        kendoRemoveClass: function(classes, options){
2082            return toggleClass(this, classes, options, false);
2083        },
2084        kendoToggleClass: function(classes, options, toggle){
2085            return toggleClass(this, classes, options, toggle);
2086        }
2087    });
2088
2089    var ampRegExp = /&/g,
2090        ltRegExp = /</g,
2091        gtRegExp = />/g;
2092    /**
2093     * Encodes HTML characters to entities.
2094     * @name kendo.htmlEncode
2095     * @function
2096     * @param {String} value The string that needs to be HTML encoded.
2097     * @returns {String} The encoded string.
2098     */
2099    function htmlEncode(value) {
2100        return ("" + value).replace(ampRegExp, "&amp;").replace(ltRegExp, "&lt;").replace(gtRegExp, "&gt;");
2101    }
2102
2103    var touchLocation = function(e) {
2104        return {
2105            idx: 0,
2106            x: e.pageX,
2107            y: e.pageY
2108        };
2109    };
2110
2111    var eventTarget = function (e) {
2112        return e.target;
2113    };
2114
2115    if (support.touch) {
2116        /** @ignore */
2117        touchLocation = function(e, id) {
2118            var changedTouches = e.changedTouches || e.originalEvent.changedTouches;
2119
2120            if (id) {
2121                var output = null;
2122                each(changedTouches, function(idx, value) {
2123                    if (id == value.identifier) {
2124                        output = {
2125                            idx: value.identifier,
2126                            x: value.pageX,
2127                            y: value.pageY
2128                        };
2129                    }
2130                });
2131                return output;
2132            } else {
2133                return {
2134                    idx: changedTouches[0].identifier,
2135                    x: changedTouches[0].pageX,
2136                    y: changedTouches[0].pageY
2137                };
2138            }
2139        };
2140
2141        eventTarget = function(e) {
2142            var touches = "originalEvent" in e ? e.originalEvent.changedTouches : "changedTouches" in e ? e.changedTouches : null;
2143
2144            return touches ? document.elementFromPoint(touches[0].clientX, touches[0].clientY) : null;
2145        };
2146
2147        each(["swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap"], function(m, value) {
2148            $.fn[value] = function(callback) {
2149                return this.bind(value, callback);
2150            };
2151        });
2152    }
2153
2154    if (support.touch) {
2155        support.mousedown = "touchstart";
2156        support.mouseup = "touchend";
2157        support.mousemove = "touchmove";
2158        support.mousecancel = "touchcancel";
2159    } else {
2160        support.mousemove = "mousemove";
2161        support.mousedown = "mousedown";
2162        support.mouseup = "mouseup";
2163        support.mousecancel = "mouseleave";
2164    }
2165
2166    var wrapExpression = function(members) {
2167        var result = "d",
2168            index,
2169            idx,
2170            length,
2171            member,
2172            count = 1;
2173
2174        for (idx = 0, length = members.length; idx < length; idx++) {
2175            member = members[idx];
2176            if (member !== "") {
2177                index = member.indexOf("[");
2178
2179                if (index !== 0) {
2180                    if (index == -1) {
2181                        member = "." + member;
2182                    } else {
2183                        count++;
2184                        member = "." + member.substring(0, index) + " || {})" + member.substring(index);
2185                    }
2186                }
2187
2188                count++;
2189                result += member + ((idx < length - 1) ? " || {})" : ")");
2190            }
2191        }
2192        return new Array(count).join("(") + result;
2193    },
2194    localUrlRe = /^([a-z]+:)?\/\//i;
2195
2196    extend(kendo, /** @lends kendo */ {
2197        /**
2198         * @name kendo.ui
2199         * @namespace Contains all classes for the Kendo UI widgets.
2200         */
2201        ui: kendo.ui || {},
2202        fx: kendo.fx || fx,
2203        data: kendo.data || {},
2204        mobile: kendo.mobile || {},
2205        keys: {
2206            INSERT: 45,
2207            DELETE: 46,
2208            BACKSPACE: 8,
2209            TAB: 9,
2210            ENTER: 13,
2211            ESC: 27,
2212            LEFT: 37,
2213            UP: 38,
2214            RIGHT: 39,
2215            DOWN: 40,
2216            END: 35,
2217            HOME: 36,
2218            SPACEBAR: 32,
2219            PAGEUP: 33,
2220            PAGEDOWN: 34,
2221            F10: 121,
2222            F12: 123
2223        },
2224        support: support,
2225        animate: animate,
2226        ns: "",
2227        attr: function(value) {
2228            return "data-" + kendo.ns + value;
2229        },
2230        wrap: wrap,
2231        deepExtend: deepExtend,
2232        size: size,
2233        isNodeEmpty: isNodeEmpty,
2234        getOffset: getOffset,
2235        parseEffects: parseEffects,
2236        toggleClass: toggleClass,
2237        directions: directions,
2238        Observable: Observable,
2239        Class: Class,
2240        Template: Template,
2241        /**
2242         * Shorthand for {@link kendo.Template.compile}.
2243         * @name kendo.template
2244         * @function
2245         */
2246        template: proxy(Template.compile, Template),
2247        /**
2248         * Shorthand for {@link kendo.Template.render}.
2249         * @name kendo.render
2250         * @function
2251         */
2252        render: proxy(Template.render, Template),
2253        stringify: proxy(JSON.stringify, JSON),
2254        touchLocation: touchLocation,
2255        eventTarget: eventTarget,
2256        htmlEncode: htmlEncode,
2257        isLocalUrl: function(url) {
2258            return url && !localUrlRe.test(url);
2259        },
2260        /** @ignore */
2261        expr: function(expression, safe) {
2262            expression = expression || "";
2263
2264            if (expression && expression.charAt(0) !== "[") {
2265                expression = "." + expression;
2266            }
2267
2268            if (safe) {
2269                expression =  wrapExpression(expression.split("."));
2270            } else {
2271                expression = "d" + expression;
2272            }
2273
2274            return expression;
2275        },
2276        /** @ignore */
2277        getter: function(expression, safe) {
2278            return getterCache[expression] = getterCache[expression] || new Function("d", "return " + kendo.expr(expression, safe));
2279        },
2280        /** @ignore */
2281        setter: function(expression) {
2282            return setterCache[expression] = setterCache[expression] || new Function("d,value", "d." + expression + "=value");
2283        },
2284        /** @ignore */
2285        accessor: function(expression) {
2286            return {
2287                get: kendo.getter(expression),
2288                set: kendo.setter(expression)
2289            };
2290        },
2291        /** @ignore */
2292        guid: function() {
2293            var id = "", i, random;
2294
2295            for (i = 0; i < 32; i++) {
2296                random = math.random() * 16 | 0;
2297
2298                if (i == 8 || i == 12 || i == 16 || i == 20) {
2299                    id += "-";
2300                }
2301                id += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
2302            }
2303
2304            return id;
2305        },
2306
2307        roleSelector: function(role) {
2308            return role.replace(/(\S+)/g, "[" + kendo.attr("role") + "=$1],").slice(0, -1);
2309        },
2310
2311        /** @ignore */
2312        logToConsole: function(message) {
2313            var console = window.console;
2314
2315            if (typeof(console) != "undefined" && console.log) {
2316                console.log(message);
2317            }
2318        }
2319    });
2320
2321    var Widget = Observable.extend( /** @lends kendo.ui.Widget.prototype */ {
2322        /**
2323         * Initializes widget. Sets `element` and `options` properties.
2324         * @constructs
2325         * @class Represents a UI widget. Base class for all Kendo widgets
2326         * @extends kendo.Observable
2327         */
2328        init: function(element, options) {
2329            var that = this;
2330
2331            that.element = $(element);
2332
2333            Observable.fn.init.call(that);
2334
2335            that.options = extend(true, {}, that.options, options);
2336
2337            if (!that.element.attr(kendo.attr("role"))) {
2338                that.element.attr(kendo.attr("role"), (that.options.name || "").toLowerCase());
2339            }
2340
2341            that.element.data("kendo" + that.options.prefix + that.options.name, that);
2342
2343            that.bind(that.events, that.options);
2344        },
2345
2346        events: [],
2347
2348        options: {
2349            prefix: ""
2350        },
2351
2352        setOptions: function(options) {
2353            $.extend(this.options, options);
2354
2355            this.bind(this.events, options);
2356        }
2357    });
2358
2359    kendo.notify = noop;
2360
2361    var templateRegExp = /template$/i,
2362        jsonRegExp = /^(?:\{(?:.|\n)*\}|\[(?:.|\n)*\])$/,
2363        jsonFormatRegExp = /^\{(\d+)(:[^\}]+)?\}/,
2364        dashRegExp = /([A-Z])/g;
2365
2366    function parseOption(element, option) {
2367        var value;
2368
2369        if (option.indexOf("data") === 0) {
2370            option = option.substring(4);
2371            option = option.charAt(0).toLowerCase() + option.substring(1);
2372        }
2373
2374        option = option.replace(dashRegExp, "-$1");
2375        value = element.getAttribute("data-" + kendo.ns + option);
2376
2377        if (value === null) {
2378            value = undefined;
2379        } else if (value === "null") {
2380            value = null;
2381        } else if (value === "true") {
2382            value = true;
2383        } else if (value === "false") {
2384            value = false;
2385        } else if (!isNaN(parseFloat(value))) {
2386            value = parseFloat(value);
2387        } else if (jsonRegExp.test(value) && !jsonFormatRegExp.test(value)) {
2388            value = $.parseJSON(value);
2389        }
2390
2391        return value;
2392    }
2393
2394    function parseOptions(element, options) {
2395        var result = {},
2396            option,
2397            value;
2398
2399        for (option in options) {
2400            value = parseOption(element, option);
2401
2402            if (value !== undefined) {
2403
2404                if (templateRegExp.test(option)) {
2405                    value = kendo.template($("#" + value).html());
2406                }
2407
2408                result[option] = value;
2409            }
2410        }
2411
2412        return result;
2413    }
2414
2415    kendo.initWidget = function(element, options, namespace) {
2416        var result,
2417            option,
2418            widget,
2419            idx,
2420            length,
2421            role,
2422            value,
2423            dataSource;
2424
2425        element = element.nodeType ? element : element[0];
2426
2427        role = element.getAttribute("data-" + kendo.ns + "role");
2428
2429        if (!role) {
2430            return;
2431        }
2432
2433        dataSource = parseOption(element, "dataSource");
2434
2435        widget = (namespace || kendo.ui).roles[role];
2436
2437        if (!widget) {
2438            return;
2439        }
2440
2441        options = $.extend({}, parseOptions(element, widget.fn.options), options);
2442
2443        if (dataSource) {
2444            if (typeof dataSource === STRING) {
2445                options.dataSource = kendo.getter(dataSource)(window);
2446            } else {
2447                options.dataSource = dataSource;
2448            }
2449        }
2450
2451        for (idx = 0, length = widget.fn.events.length; idx < length; idx++) {
2452            option = widget.fn.events[idx];
2453
2454            value = parseOption(element, option);
2455
2456            if (value !== undefined) {
2457                options[option] = kendo.getter(value)(window);
2458            }
2459        }
2460
2461        result = $(element).data("kendo" + widget.fn.options.name);
2462
2463        if (!result) {
2464            result = new widget(element, options);
2465        } else {
2466            result.setOptions(options);
2467        }
2468
2469        return result;
2470    };
2471
2472    kendo.init = function(element, namespace) {
2473        $(element).find("[data-" + kendo.ns + "role]").andSelf().each(function(){
2474            kendo.initWidget(this, {}, namespace);
2475        });
2476    };
2477
2478    kendo.parseOptions = parseOptions;
2479
2480    extend(kendo.ui, /** @lends kendo.ui */{
2481        Widget: Widget,
2482        roles: {},
2483        /**
2484         * Shows an overlay with a loading message, indicating that an action is in progress.
2485         * @name kendo.ui.progress
2486         * @function
2487         * @param {jQueryObject} container The container that will hold the overlay
2488         * @param {Boolean} toggle Whether the overlay should be shown or hidden
2489         */
2490        progress: function(container, toggle) {
2491            var mask = container.find(".k-loading-mask");
2492
2493            if (toggle) {
2494                if (!mask.length) {
2495                    mask = $("<div class='k-loading-mask'><span class='k-loading-text'>Loading...</span><div class='k-loading-image'/><div class='k-loading-color'/></div>")
2496                        .width("100%").height("100%")
2497                        .prependTo(container)
2498                        .css({ top: container.scrollTop(), left: container.scrollLeft() });
2499                }
2500            } else if (mask) {
2501                mask.remove();
2502            }
2503        },
2504        /**
2505         * Helper method for writing new widgets.
2506         * Exposes a jQuery plug-in that will handle the widget creation and attach its client-side object in the appropriate data-* attribute.
2507         * @name kendo.ui.plugin
2508         * @function
2509         * @param {kendo.ui.Widget} widget The widget function.
2510         * @param {Object} register <kendo.ui> The object where the reference to the widget is recorded.
2511         * @param {Object} prefix <""> The plugin function prefix, e.g. "Mobile" will register "kendoMobileFoo".
2512         * @example
2513         * function TextBox(element, options);
2514         * kendo.ui.plugin(TextBox);
2515         *
2516         * // initialize a new TextBox for each input, with the given options object.
2517         * $("input").kendoTextBox({ });
2518         * // get the TextBox object and call the value API method
2519         * $("input").data("kendoTextBox").value();
2520         */
2521        plugin: function(widget, register, prefix) {
2522            var name = widget.fn.options.name;
2523
2524            register = register || kendo.ui;
2525            prefix = prefix || "";
2526
2527            register[name] = widget;
2528
2529            register.roles[name.toLowerCase()] = widget;
2530
2531            name = "kendo" + prefix + name;
2532            $.fn[name] = function(options) {
2533                var value = this,
2534                    args;
2535
2536                if (typeof options === STRING) {
2537                    args = slice.call(arguments, 1);
2538
2539                    this.each(function(){
2540                        var widget = $.data(this, name),
2541                            method,
2542                            result;
2543
2544                        if (!widget) {
2545                            throw new Error(kendo.format("Cannot call method '{0}' of {1} before it is initialized", options, name));
2546                        }
2547
2548                        method = widget[options];
2549
2550                        if (typeof method !== FUNCTION) {
2551                            throw new Error(kendo.format("Cannot find method '{0}' of {1}", options, name));
2552                        }
2553
2554                        result = method.apply(widget, args);
2555
2556                        if (result !== undefined) {
2557                            value = result;
2558                            return false;
2559                        }
2560                    });
2561                } else {
2562                    this.each(function() {
2563                        new widget(this, options);
2564                    });
2565                }
2566
2567                return value;
2568            };
2569        }
2570    });
2571
2572    var MobileWidget = Widget.extend(/** @lends kendo.mobile.ui.Widget.prototype */{
2573        /**
2574         * Initializes mobile widget. Sets `element` and `options` properties.
2575         * @constructs
2576         * @class Represents a mobile UI widget. Base class for all Kendo mobile widgets.
2577         * @extends kendo.ui.Widget
2578         */
2579        init: function(element, options) {
2580            Widget.fn.init.call(this, element, options);
2581            this.wrapper = this.element;
2582        },
2583
2584        options: {
2585            prefix: "Mobile"
2586        },
2587
2588        events: [],
2589
2590        viewShow: $.noop,
2591        viewInit: $.noop,
2592
2593        view: function() {
2594            return this.element.closest(kendo.roleSelector("view")).data("kendoMobileView");
2595        }
2596    });
2597
2598    /**
2599     * @name kendo.mobile
2600     * @namespace This object contains all code introduced by the Kendo mobile suite, plus helper functions that are used across all mobile widgets.
2601     */
2602    extend(kendo.mobile, {
2603        init: function(element) {
2604            kendo.init(element, kendo.mobile.ui);
2605        },
2606
2607        /**
2608         * @name kendo.mobile.ui
2609         * @namespace Contains all classes for the Kendo Mobile UI widgets.
2610         */
2611        ui: {
2612            Widget: MobileWidget,
2613            roles: {},
2614            plugin: function(widget) {
2615                kendo.ui.plugin(widget, kendo.mobile.ui, "Mobile");
2616            }
2617        }
2618    });
2619
2620    /**
2621     * Enables kinetic scrolling on touch devices
2622     * @name kendo.touchScroller
2623     * @function
2624     * @param {Selector} element The container element to enable scrolling for.
2625     */
2626    kendo.touchScroller = function(element, options) {
2627        if (support.touch && kendo.mobile.ui.Scroller) {
2628            element.kendoMobileScroller(options);
2629            return element.data("kendoMobileScroller");
2630        } else {
2631            return false;
2632        }
2633    };
2634
2635    /**
2636     * Prevents the default event action. Should be supplied as an event callback
2637     * @name kendo.preventDefault
2638     * @function
2639     */
2640    kendo.preventDefault = function(e) {
2641        e.preventDefault();
2642    };
2643
2644    /**
2645     * Retrieves the widget for a given element (if any)
2646     */
2647    kendo.widgetInstance = function(element, suite) {
2648        var widget = suite.roles[element.data(kendo.ns + "role")];
2649
2650        if (widget) {
2651            return element.data("kendo" + widget.fn.options.prefix + widget.fn.options.name);
2652        }
2653    };
2654})(jQuery);
2655(function($, undefined) {
2656    /**
2657     * @name kendo.fx
2658     * @namespace This object contains the fx library that is used by all widgets using animation.
2659     * If this file is not included, all animations will be disabled but the basic functionality preserved.
2660     */
2661    var kendo = window.kendo,
2662        fx = kendo.fx,
2663        each = $.each,
2664        extend = $.extend,
2665        proxy = $.proxy,
2666        size = kendo.size,
2667        browser = $.browser,
2668        support = kendo.support,
2669        transforms = support.transforms,
2670        transitions = support.transitions,
2671        scaleProperties = { scale: 0, scaleX: 0, scaleY: 0, scale3d: 0 },
2672        translateProperties = { translate: 0, translateX: 0, translateY: 0, translate3d: 0 },
2673        hasZoom = (typeof document.documentElement.style.zoom !== "undefined") && !transitions,
2674        matrix3dRegExp = /matrix3?d?\s*\(.*,\s*([\d\w\.\-]+),\s*([\d\w\.\-]+),\s*([\d\w\.\-]+)/,
2675        cssParamsRegExp = /^(-?[\d\.\-]+)?[\w\s]*,?\s*(-?[\d\.\-]+)?[\w\s]*/i,
2676        translateXRegExp = /translatex?$/i,
2677        oldEffectsRegExp = /(zoom|fade|expand)(\w+)/,
2678        singleEffectRegExp = /(zoom|fade|expand)/,
2679        transformProps = ["perspective", "rotate", "rotateX", "rotateY", "rotateZ", "rotate3d", "scale", "scaleX", "scaleY", "scaleZ", "scale3d", "skew", "skewX", "skewY", "translate", "translateX", "translateY", "translateZ", "translate3d", "matrix", "matrix3d"],
2680        cssPrefix = transforms.css,
2681        round = Math.round,
2682        BLANK = "",
2683        PX = "px",
2684        NONE = "none",
2685        AUTO = "auto",
2686        WIDTH = "width",
2687        HEIGHT = "height",
2688        HIDDEN = "hidden",
2689        ORIGIN = "origin",
2690        ABORT_ID = "abortId",
2691        OVERFLOW = "overflow",
2692        TRANSLATE = "translate",
2693        TRANSITION = cssPrefix + "transition",
2694        TRANSFORM = cssPrefix + "transform";
2695
2696    kendo.directions = {
2697        left: {
2698            reverse: "right",
2699            property: "left",
2700            transition: "translateX",
2701            vertical: false,
2702            modifier: -1
2703        },
2704        right: {
2705            reverse: "left",
2706            property: "left",
2707            transition: "translateX",
2708            vertical: false,
2709            modifier: 1
2710        },
2711        down: {
2712            reverse: "up",
2713            property: "top",
2714            transition: "translateY",
2715            vertical: true,
2716            modifier: 1
2717        },
2718        up: {
2719            reverse: "down",
2720            property: "top",
2721            transition: "translateY",
2722            vertical: true,
2723            modifier: -1
2724        },
2725        top: {
2726            reverse: "bottom"
2727        },
2728        bottom: {
2729            reverse: "top"
2730        },
2731        "in": {
2732            reverse: "out",
2733            modifier: -1
2734        },
2735        out: {
2736            reverse: "in",
2737            modifier: 1
2738        }
2739    };
2740
2741    extend($.fn, {
2742        kendoStop: function(clearQueue, gotoEnd) {
2743            if (transitions) {
2744                return kendo.fx.stopQueue(this, clearQueue || false, gotoEnd || false);
2745            } else {
2746                return this.stop(clearQueue, gotoEnd);
2747            }
2748        }
2749    });
2750
2751    /* jQuery support for scale transform animation (FF 3.5/3.6, Opera 10.x */
2752    $.fn.scale = function (val) {
2753        if (typeof val == "undefined") {
2754            return animationProperty(this, "scale");
2755        } else {
2756            $(this).css(TRANSFORM, "scale(" + val + ")");
2757        }
2758        return this;
2759    };
2760
2761    var curProxy = $.fx.prototype.cur;
2762    $.fx.prototype.cur = function () {
2763        if (this.prop == "scale") {
2764            return parseFloat($(this.elem).scale());
2765        }
2766
2767        return curProxy.apply(this, arguments);
2768    };
2769
2770    $.fx.step.scale = function (fx) {
2771        $(fx.elem).scale(fx.now);
2772    };
2773
2774    kendo.toggleClass = function(element, classes, options, add) {
2775        if (classes) {
2776            classes = classes.split(" ");
2777
2778            if (transitions) {
2779                options = extend({
2780                    exclusive: "all",
2781                    duration: 400,
2782                    ease: "ease-out"
2783                }, options);
2784
2785                element.css(TRANSITION, options.exclusive + " " + options.duration + "ms " + options.ease);
2786                setTimeout(function() {
2787                    element.css(TRANSITION, NONE).css(HEIGHT);
2788                }, options.duration); // TODO: this should fire a kendoAnimate session instead.
2789            }
2790
2791            each(classes, function(idx, value) {
2792                element.toggleClass(value, add);
2793            });
2794        }
2795
2796        return element;
2797    };
2798
2799    kendo.parseEffects = function(input, mirror) {
2800        var effects = {};
2801
2802        if (typeof input === "string") {
2803            each(input.split(" "), function(idx, value) {
2804                var redirectedEffect = !singleEffectRegExp.test(value),
2805                    resolved = value.replace(oldEffectsRegExp, function(match, $1, $2) {
2806                        return $1 + ":" + $2.toLowerCase();
2807                    }), // Support for old zoomIn/fadeOut style, now deprecated.
2808                    effect = resolved.split(":"),
2809                    direction = effect[1],
2810                    effectBody = {};
2811
2812                if (effect.length > 1) {
2813                    effectBody.direction = (mirror && redirectedEffect ? kendo.directions[direction].reverse : direction);
2814                }
2815
2816                effects[effect[0]] = effectBody;
2817            });
2818        } else {
2819            each(input, function(idx) {
2820                var direction = this.direction;
2821
2822                if (direction && mirror && !singleEffectRegExp.test(idx)) {
2823                    this.direction = kendo.directions[direction].reverse;
2824                }
2825
2826                effects[idx] = this;
2827            });
2828        }
2829
2830        return effects;
2831    };
2832
2833    function parseInteger(value) {
2834        return parseInt(value, 10);
2835    }
2836
2837    function parseCSS(element, property) {
2838        return parseInteger(element.css(property));
2839    }
2840
2841    function getComputedStyles(element, properties) {
2842        var styles = {};
2843
2844        if (properties) {
2845            if (document.defaultView && document.defaultView.getComputedStyle) {
2846                var computedStyle = document.defaultView.getComputedStyle(element, "");
2847
2848                each(properties, function(idx, value) {
2849                    styles[value] = computedStyle.getPropertyValue(value);
2850                });
2851            } else
2852                if (element.currentStyle) { // Not really needed
2853                    var style = element.currentStyle;
2854
2855                    each(properties, function(idx, value) {
2856                        styles[value] = style[value.replace(/\-(\w)/g, function (strMatch, g1) { return g1.toUpperCase(); })];
2857                    });
2858                }
2859        } else {
2860            styles = document.defaultView.getComputedStyle(element, "");
2861        }
2862
2863        return styles;
2864    }
2865
2866    function slideToSlideIn(options) {
2867      options.effects.slideIn = options.effects.slide;
2868      delete options.effects.slide;
2869      return options;
2870    }
2871
2872    function parseTransitionEffects(options) {
2873        var effects = options.effects,
2874            mirror;
2875
2876        if (effects === "zoom") {
2877            effects = "zoomIn fadeIn";
2878        }
2879        if (effects === "slide") {
2880            effects = "slide:left";
2881        }
2882        if (effects === "fade") {
2883            effects = "fadeIn";
2884        }
2885        if (effects === "overlay") {
2886            effects = "slideIn:left";
2887        }
2888        if (/^overlay:(.+)$/.test(effects)) {
2889            effects = "slideIn:" + RegExp.$1;
2890        }
2891
2892        mirror = options.reverse && /^(slide:)/.test(effects);
2893
2894        if (mirror) {
2895            delete options.reverse;
2896        }
2897
2898        options.effects = $.extend(kendo.parseEffects(effects, mirror), {show: true});
2899
2900        return options;
2901    }
2902
2903    function keys(obj) {
2904        var acc = [];
2905        for (var propertyName in obj) {
2906            acc.push(propertyName);
2907        }
2908        return acc;
2909    }
2910
2911    function removeTransitionStyles(element) {
2912        element.css(TRANSITION, NONE);
2913
2914        if (!browser.safari) {
2915            element.css(HEIGHT);
2916        }
2917    }
2918
2919    function activateTask(currentTransition) {
2920        var element = currentTransition.object;
2921
2922        if (!currentTransition) {
2923            return;
2924        }
2925
2926        element.css(currentTransition.setup);
2927        element.css(TRANSITION);
2928        element.data(ABORT_ID, setTimeout(function() {
2929
2930            removeTransitionStyles(element);
2931            element.dequeue();
2932            currentTransition.complete.call(element);
2933
2934        }, currentTransition.duration));
2935
2936        element.css(currentTransition.CSS);
2937    }
2938
2939    if (transitions) {
2940
2941        extend(kendo.fx, {
2942            transition: function(element, properties, options) {
2943
2944                options = extend({
2945                        duration: 200,
2946                        ease: "ease-out",
2947                        complete: null,
2948                        exclusive: "all"
2949                    },
2950                    options
2951                );
2952
2953                options.duration = $.fx ? $.fx.speeds[options.duration] || options.duration : options.duration;
2954
2955                var transforms = [],
2956                    cssValues = {},
2957                    key;
2958
2959                for (key in properties) {
2960                    if (transformProps.indexOf(key) != -1) {
2961                        transforms.push(key + "(" + properties[key] + ")");
2962                    } else {
2963                        cssValues[key] = properties[key];
2964                    }
2965                }
2966
2967                if (transforms.length) {
2968                    cssValues[TRANSFORM] = transforms.join(" ");
2969                }
2970
2971                var currentTask = {
2972                    keys: keys(cssValues),
2973                    CSS: cssValues,
2974                    object: element,
2975                    setup: {},
2976                    duration: options.duration,
2977                    complete: options.complete
2978                };
2979                currentTask.setup[TRANSITION] = options.exclusive + " " + options.duration + "ms " + options.ease;
2980
2981                var oldKeys = element.data("keys") || [];
2982                $.merge(oldKeys, currentTask.keys);
2983                element.data("keys", $.unique(oldKeys));
2984
2985                activateTask(currentTask);
2986            },
2987
2988            stopQueue: function(element, clearQueue, gotoEnd) {
2989
2990                if (element.data(ABORT_ID)) {
2991                    clearTimeout(element.data(ABORT_ID));
2992                    element.removeData(ABORT_ID);
2993                }
2994
2995                var that = this, cssValues,
2996                    taskKeys = element.data("keys"),
2997                    retainPosition = (gotoEnd === false && taskKeys);
2998
2999                if (retainPosition) {
3000                    cssValues = getComputedStyles(element[0], taskKeys);
3001                }
3002
3003                removeTransitionStyles(element);
3004
3005                if (retainPosition) {
3006                    element.css(cssValues);
3007                }
3008
3009                element.removeData("keys");
3010
3011                if (that.complete) {
3012                    that.complete.call(element);
3013                }
3014
3015                element.stop(clearQueue);
3016                return element;
3017            }
3018
3019        });
3020    }
3021
3022    function animationProperty(element, property) {
3023        if (transforms) {
3024            var transform = element.css(TRANSFORM);
3025            if (transform == "none") {
3026                return property == "scale" ? 1 : 0;
3027            }
3028
3029            var match = transform.match(new RegExp(property + "\\s*\\(([\\d\\w\\.]+)")),
3030                computed = 0;
3031
3032            if (match) {
3033                computed = parseInteger(match[1]);
3034            } else {
3035                match = transform.match(matrix3dRegExp) || [0, 0, 0, 0];
3036
3037                if (translateXRegExp.test(property)) {
3038                    computed = parseInteger(match[2]);
3039                } else if (property.toLowerCase() == "translatey") {
3040                    computed = parseInteger(match[3]);
3041                } else if (property.toLowerCase() == "scale") {
3042                    computed = parseFloat(match[1]);
3043                }
3044            }
3045
3046            return computed;
3047        } else {
3048            return element.css(property);
3049        }
3050    }
3051
3052    kendo.fx.promise = function(element, options) {
3053        var promises = [], effects;
3054
3055        effects = kendo.parseEffects(options.effects);
3056        options.effects = effects;
3057
3058        element.data("animating", true);
3059
3060        var props = { keep: [], restore: [] }, css = {},
3061            methods = { setup: [], teardown: [] }, properties = {},
3062
3063            // create a promise for each effect
3064            promise = $.Deferred(function(deferred) {
3065                if (size(effects)) {
3066                    var opts = extend({}, options, { complete: deferred.resolve });
3067
3068                    each(effects, function(effectName, settings) {
3069                        var effect = kendo.fx[effectName];
3070
3071                        if (effect) {
3072                            var dir = kendo.directions[settings.direction];
3073                            if (settings.direction && dir) {
3074                                settings.direction = (options.reverse ? dir.reverse : settings.direction);
3075                                element.data(effectName, settings);
3076                            }
3077
3078                            opts = extend(true, opts, settings);
3079
3080                            each(methods, function (idx) {
3081                                if (effect[idx]) {
3082                                    methods[idx].push(effect[idx]);
3083                                }
3084                            });
3085
3086                            each(props, function(idx) {
3087                                if (effect[idx]) {
3088                                    $.merge(props[idx], effect[idx]);
3089                                }
3090                            });
3091
3092                            if (effect.css) {
3093                                css = extend(css, effect.css);
3094                            }
3095                        }
3096                    });
3097
3098                    if (methods.setup.length) {
3099                        each ($.unique(props.keep), function(idx, value) {
3100                            if (!element.data(value)) {
3101                                element.data(value, element.css(value));
3102                            }
3103                        });
3104
3105                        if (options.show) {
3106                            css = extend(css, { display: element.data("olddisplay") || "block" }); // Add show to the set
3107                        }
3108
3109                        var buffer = css.transform;
3110                        delete css.transform;
3111                        if (transforms) {
3112                            css[TRANSFORM] = buffer;
3113                        }
3114
3115                        element.css(css);
3116                        element.css("overflow"); // Nudge Chrome
3117
3118                        each (methods.setup, function() { properties = extend(properties, this(element, opts)); });
3119
3120                        if (kendo.fx.animate) {
3121                            options.init();
3122                            kendo.fx.animate(element, properties, opts);
3123                        }
3124
3125                        return;
3126                    }
3127                } else if (options.show) {
3128                    element.css({ display: element.data("olddisplay") || "block" }).css("display");
3129                    options.init();
3130                }
3131
3132                deferred.resolve();
3133            }).promise();
3134
3135        promises.push(promise);
3136
3137        //wait for all effects to complete
3138        $.when.apply(null, promises).then(function() {
3139            element
3140                .removeData("animating")
3141                .dequeue(); // call next animation from the queue
3142
3143            if (options.hide) {
3144                element.data("olddisplay", element.css("display")).hide();
3145            }
3146
3147            if (size(effects)) {
3148                var restore = function() {
3149                    each ($.unique(props.restore), function(idx, value) {
3150                        element.css(value, element.data(value));
3151                    });
3152                };
3153
3154                if ($.browser.msie) {
3155                    setTimeout(restore, 0); // Again jQuery callback in IE.
3156                }
3157                else {
3158                    restore();
3159                }
3160
3161                each(methods.teardown, function() { this(element, options); }); // call the internal completion callbacks
3162            }
3163
3164            if (options.completeCallback) {
3165                options.completeCallback(element); // call the external complete callback with the element
3166            }
3167        });
3168    };
3169
3170    kendo.fx.transitionPromise = function(element, destination, options) {
3171        kendo.fx.animateTo(element, destination, options);
3172        return element;
3173    };
3174
3175    extend(kendo.fx, {
3176        animate: function(elements, properties, options) {
3177            var useTransition = options.transition !== false;
3178            delete options.transition;
3179
3180            if (transitions && "transition" in fx && useTransition) {
3181                fx.transition(elements, properties, options);
3182            } else {
3183                elements.each(function() {
3184                    var element = $(this),
3185                        multiple = {};
3186
3187                    each(transformProps, function(idx, value) { // remove transforms to avoid IE and older browsers confusion
3188                        var params,
3189                            currentValue = properties ? properties[value]+ " " : null; // We need to match
3190
3191                        if (currentValue) {
3192                            var single = properties;
3193
3194                            if (value in scaleProperties && properties[value] !== undefined) {
3195                                params = currentValue.match(cssParamsRegExp);
3196                                if (hasZoom) {
3197                                    var half = (1 - params[1]) / 2;
3198                                    extend(single, {
3199                                                       zoom: +params[1],
3200                                                       marginLeft: element.width() * half,
3201                                                       marginTop: element.height() * half
3202                                                   });
3203                                } else if (transforms) {
3204                                    extend(single, {
3205                                                       scale: +params[0]
3206                                                   });
3207                                }
3208                            } else
3209                                if (value in translateProperties && properties[value] !== undefined) {
3210                                    var position = element.css("position"),
3211                                        isFixed = (position == "absolute" || position == "fixed");
3212
3213                                    if (!element.data(TRANSLATE)) {
3214                                        if (isFixed) {
3215                                            element.data(TRANSLATE, {
3216                                                top: parseCSS(element, "top") || 0,
3217                                                left: parseCSS(element, "left") || 0,
3218                                                bottom: parseCSS(element, "bottom"),
3219                                                right: parseCSS(element, "right")
3220                                            });
3221                                        } else {
3222                                            element.data(TRANSLATE, {
3223                                                top: parseCSS(element, "marginTop") || 0,
3224                                                left: parseCSS(element, "marginLeft") || 0
3225                                            });
3226                                        }
3227                                    }
3228
3229                                    var originalPosition = element.data(TRANSLATE);
3230
3231                                    params = currentValue.match(cssParamsRegExp);
3232                                    if (params) {
3233
3234                                        var dX = value == TRANSLATE + "Y" ? +null : +params[1],
3235                                            dY = value == TRANSLATE + "Y" ? +params[1] : +params[2];
3236
3237                                        if (isFixed) {
3238                                            if (!isNaN(originalPosition.right)) {
3239                                                if (!isNaN(dX)) { extend(single, { right: originalPosition.right - dX }); }
3240                                            } else {
3241                                                if (!isNaN(dX)) { extend(single, { left: originalPosition.left + dX }); }
3242                                            }
3243
3244                                            if (!isNaN(originalPosition.bottom)) {
3245                                                if (!isNaN(dY)) { extend(single, { bottom: originalPosition.bottom - dY }); }
3246                                            } else {
3247                                                if (!isNaN(dY)) { extend(single, { top: originalPosition.top + dY }); }
3248                                            }
3249                                        } else {
3250                                            if (!isNaN(dX)) { extend(single, { marginLeft: originalPosition.left + dX }); }
3251                                            if (!isNaN(dY)) { extend(single, { marginTop: originalPosition.top + dY }); }
3252                                        }
3253                                    }
3254                                }
3255
3256                            if (!transforms && value != "scale" && value in single) {
3257                                delete single[value];
3258                            }
3259
3260                            if (single) {
3261                                extend(multiple, single);
3262                            }
3263                        }
3264                    });
3265
3266                    if ($.browser.msie) {
3267                        delete multiple.scale;
3268                    }
3269
3270                    element.animate(multiple, { queue: false, show: false, hide: false, duration: options.duration, complete: options.complete }); // Stop animate from showing/hiding the element to be able to hide it later on.
3271                });
3272            }
3273        },
3274
3275        animateTo: function(element, destination, options) {
3276            var direction,
3277                commonParent = element.parents().filter(destination.parents()).first(),
3278                originalOverflow = commonParent.css(OVERFLOW);
3279
3280            options = parseTransitionEffects(options);
3281            commonParent.css(OVERFLOW, "hidden");
3282
3283            $.each(options.effects, function(name, definition) {
3284                direction = direction || definition.direction;
3285            });
3286
3287            function complete() {
3288                destination[0].style.cssText = "";
3289                element[0].style.cssText = ""; // Removing the whole style attribute breaks Android.
3290                commonParent.css(OVERFLOW, originalOverflow);
3291                if (options.completeCallback) {
3292                    options.completeCallback();
3293                }
3294            }
3295
3296            options.complete = $.browser.msie ? function() { setTimeout(complete); } : complete;
3297
3298            if ("slide" in options.effects) {
3299              element.kendoAnimate(options);
3300              destination.kendoAnimate(slideToSlideIn(options));
3301            } else {
3302              (options.reverse ? element : destination).kendoAnimate(options);
3303            }
3304        },
3305
3306        fade: {
3307            css: {
3308                opacity: function() {
3309                    var fade = $(this).data("fade"),
3310                        inDirection = fade ? fade.direction == "in" : false;
3311
3312                    return inDirection && !this.style.opacity ? 0 : undefined;
3313                }
3314            },
3315            setup: function(element, options) {
3316                return extend({ opacity: options.effects.fade.direction == "out" ? 0 : 1 }, options.properties);
3317            }
3318        },
3319        zoom: {
3320            css: {
3321                transform: function() {
3322                    var zoom = $(this).data("zoom"),
3323                        inDirection = zoom ? zoom.direction == "in" : false;
3324
3325                    return inDirection && transitions ? "scale(.01)" : undefined;
3326                },
3327                zoom: function() {
3328                    var zoom = $(this).data("zoom"),
3329                        inDirection = zoom ? zoom.direction == "in" : false;
3330
3331                    return inDirection && hasZoom ? ".01" : undefined;
3332                }
3333            },
3334            setup: function(element, options) {
3335                var reverse = options.effects.zoom.direction == "out";
3336
3337                if (hasZoom) {
3338                    var version = $.browser.version,
3339                        style = element[0].currentStyle,
3340                        width = style.width.indexOf("%") != -1 ? element.parent().width() : element.width(),
3341                        height = style.height.indexOf("%") != -1 ? element.parent().height() : parseInt(style.height, 10),
3342                        half = version < 9 && options.effects.fade ? 0 : (1 - (parseInt(element.css("zoom"), 10) / 100)) / 2; // Kill margins in IE7/8 if using fade
3343
3344                    element.css({
3345                        marginLeft: width * (version < 8 ? 0 : half),
3346                        marginTop: height * half
3347                    });
3348                }
3349
3350                return extend({ scale: reverse ? 0.01 : 1 }, options.properties);
3351            }
3352        },
3353        slide: {
3354            setup: function(element, options) {
3355                var direction = kendo.directions[options.effects.slide.direction],
3356                    extender = {}, offset, reverse = options.reverse,
3357                    property = transitions && options.transition !== false ? direction.transition : direction.property,
3358                    divisor = options.divisor || 1;
3359
3360                if (!reverse) {
3361                    var origin = element.data(ORIGIN);
3362                    offset = (direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()) / divisor);
3363                    if (!origin && origin !== 0) {
3364                        element.data(ORIGIN, parseFloat(animationProperty(element, property)));
3365                    }
3366                }
3367
3368                extender[property] = reverse ? (element.data(ORIGIN) || 0) : (element.data(ORIGIN) || 0) + offset + PX;
3369
3370                return extend(extender, options.properties);
3371            }
3372        },
3373        slideMargin: {
3374            setup: function(element, options) {
3375                var origin = element.data(ORIGIN),
3376                    offset = options.offset, margin,
3377                    extender = {}, reverse = options.reverse;
3378
3379                if (!reverse && !origin && origin !== 0) {
3380                    element.data(ORIGIN, parseInt(element.css("margin-" + options.axis), 10));
3381                }
3382
3383                margin = (element.data(ORIGIN) || 0);
3384                extender["margin-" + options.axis] = !reverse ? margin + offset : margin;
3385                return extend(extender, options.properties);
3386            }
3387        },
3388        slideTo: {
3389            setup: function(element, options) {
3390                var offset = (options.offset+"").split(","),
3391                    extender = {}, reverse = options.reverse;
3392
3393                if (transitions && options.transition !== false) {
3394                    extender.translate = !reverse ? offset + PX : 0;
3395                } else {
3396                    extender.left = !reverse ? offset[0] : 0;
3397                    extender.top = !reverse ? offset[1] : 0;
3398                }
3399                element.css("left");
3400
3401                return extend(extender, options.properties);
3402            }
3403        },
3404        slideIn: {
3405            setup: function(element, options) {
3406                var dir = kendo.directions[options.effects.slideIn.direction],
3407                    reverse = options.reverse,
3408                    direction = reverse ? kendo.directions[dir.reverse] : dir,
3409                    offset = -direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()),
3410                    extender = {};
3411
3412                if (transitions && options.transition !== false) {
3413                    element.css(TRANSFORM, direction.transition + "(" + (!reverse ? offset : 0) + "px)");
3414                    extender[direction.transition] = reverse ? offset + PX : 0;
3415                    element.css(TRANSFORM);
3416                } else {
3417                    if (!reverse) {
3418                        element.css(direction.property, offset + PX);
3419                    }
3420                    extender[direction.property] = reverse ? offset + PX : 0;
3421                    element.css(direction.property); // Read a style to force Chrome to apply the change.
3422                }
3423
3424                return extend(extender, options.properties);
3425            }
3426        },
3427        expand: {
3428            keep: [ OVERFLOW ],
3429            css: { overflow: HIDDEN },
3430            restore: [ OVERFLOW ],
3431            setup: function(element, options) {
3432                var reverse = options.reverse,
3433                    direction = options.effects.expand.direction,
3434                    property = (direction ? direction == "vertical" : true) ? HEIGHT : WIDTH,
3435                    setLength = element[0].style[property],
3436                    oldLength = element.data(property),
3437                    length = parseInteger(oldLength || setLength) || round(element.css(property, AUTO )[property]()),
3438                    completion = {};
3439
3440                completion[property] = (reverse ? 0 : length) + PX;
3441                element.css(property, reverse ? length : 0).css(property);
3442                if (oldLength === undefined) {
3443                    element.data(property, setLength);
3444                }
3445
3446                return extend(completion, options.properties);
3447            },
3448            teardown: function(element, options) {
3449                var direction = options.effects.expand.direction,
3450                    property = (direction ? direction == "vertical" : true) ? HEIGHT : WIDTH,
3451                    length = element.data(property);
3452                if (length == AUTO || length === BLANK) {
3453                    setTimeout(function() { element.css(property, AUTO).css(property); }, 0); // jQuery animate complete callback in IE is called before the last animation step!
3454                }
3455            }
3456        },
3457        simple: {
3458            setup: function(element, options) {
3459                return options.properties;
3460            }
3461        }
3462    });
3463
3464    kendo.fx.expandVertical = kendo.fx.expand; // expandVertical is deprecated.
3465
3466    var animationFrame  = window.requestAnimationFrame       ||
3467                          window.webkitRequestAnimationFrame ||
3468                          window.mozRequestAnimationFrame    ||
3469                          window.oRequestAnimationFrame      ||
3470                          window.msRequestAnimationFrame     ||
3471                          function(callback){ setTimeout(callback, 1000 / 60); };
3472
3473    var Animation = kendo.Class.extend({
3474        init: function() {
3475            var that = this;
3476            that._tickProxy = proxy(that._tick, that);
3477            that._started = false;
3478        },
3479
3480        tick: $.noop,
3481        done: $.noop,
3482        onEnd: $.noop,
3483        onCancel: $.noop,
3484
3485        start: function() {
3486            this._started = true;
3487            animationFrame(this._tickProxy);
3488        },
3489
3490        cancel: function() {
3491            this._started = false;
3492            this.onCancel();
3493        },
3494
3495        _tick: function() {
3496            var that = this;
3497            if (!that._started) { return; }
3498
3499            that.tick();
3500
3501            if (!that.done()) {
3502                animationFrame(that._tickProxy);
3503            } else {
3504                that._started = false;
3505                that.onEnd();
3506            }
3507        }
3508    });
3509
3510    var Transition = Animation.extend({
3511        init: function(options) {
3512            var that = this;
3513            extend(that, options);
3514            Animation.fn.init.call(that);
3515        },
3516
3517        done: function() {
3518            return this.timePassed() >= this.duration;
3519        },
3520
3521        timePassed: function() {
3522            return Math.min(this.duration, (+new Date()) - this.startDate);
3523        },
3524
3525        moveTo: function(options) {
3526            var that = this,
3527                movable = that.movable;
3528
3529            that.initial = movable[that.axis];
3530            that.delta = options.location - that.initial;
3531
3532            that.duration = options.duration || 300;
3533
3534            that.tick = that._easeProxy(options.ease);
3535
3536            that.startDate = +new Date();
3537            that.start();
3538        },
3539
3540        _easeProxy: function(ease) {
3541            var that = this;
3542
3543            return function() {
3544                that.movable.moveAxis(that.axis, ease(that.timePassed(), that.initial, that.delta, that.duration));
3545            };
3546        }
3547    });
3548
3549    extend(Transition, {
3550        easeOutExpo: function (t, b, c, d) {
3551            return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
3552        },
3553
3554        easeOutBack: function (t, b, c, d, s) {
3555            s = 1.70158;
3556            return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
3557        }
3558    });
3559
3560    fx.Animation = Animation;
3561    fx.Transition = Transition;
3562})(jQuery);
3563(function($, undefined) {
3564    var kendo = window.kendo,
3565        extend = $.extend,
3566        odataFilters = {
3567            eq: "eq",
3568            neq: "ne",
3569            gt: "gt",
3570            gte: "ge",
3571            lt: "lt",
3572            lte: "le",
3573            contains : "substringof",
3574            endswith: "endswith",
3575            startswith: "startswith"
3576        },
3577        mappers = {
3578            pageSize: $.noop,
3579            page: $.noop,
3580            filter: function(params, filter) {
3581                if (filter) {
3582                    params.$filter = toOdataFilter(filter);
3583                }
3584            },
3585            sort: function(params, orderby) {
3586                var expr = $.map(orderby, function(value) {
3587                    var order = value.field.replace(/\./g, "/");
3588
3589                    if (value.dir === "desc") {
3590                        order += " desc";
3591                    }
3592
3593                    return order;
3594                }).join(",");
3595
3596                if (expr) {
3597                    params.$orderby = expr;
3598                }
3599            },
3600            skip: function(params, skip) {
3601                if (skip) {
3602                    params.$skip = skip;
3603                }
3604            },
3605            take: function(params, take) {
3606                if (take) {
3607                    params.$top = take;
3608                }
3609            }
3610        },
3611        defaultDataType = {
3612            read: {
3613                dataType: "jsonp"
3614            }
3615        };
3616
3617    function toOdataFilter(filter) {
3618        var result = [],
3619            logic = filter.logic || "and",
3620            idx,
3621            length,
3622            field,
3623            type,
3624            format,
3625            operator,
3626            value,
3627            ignoreCase,
3628            filters = filter.filters;
3629
3630        for (idx = 0, length = filters.length; idx < length; idx++) {
3631            filter = filters[idx];
3632            field = filter.field;
3633            value = filter.value;
3634            operator = filter.operator;
3635
3636            if (filter.filters) {
3637                filter = toOdataFilter(filter);
3638            } else {
3639                ignoreCase = filter.ignoreCase;
3640                field = field.replace(/\./g, "/");
3641                filter = odataFilters[operator];
3642
3643                if (filter && value !== undefined) {
3644                    type = $.type(value);
3645                    if (type === "string") {
3646                        format = "'{1}'";
3647                        value = value.replace(/'/g, "''");
3648
3649                        if (ignoreCase === true) {
3650                            field = "tolower(" + field + ")";
3651                        }
3652
3653                    } else if (type === "date") {
3654                        format = "datetime'{1:yyyy-MM-ddTHH:mm:ss}'";
3655                    } else {
3656                        format = "{1}";
3657                    }
3658
3659                    if (filter.length > 3) {
3660                        if (filter !== "substringof") {
3661                            format = "{0}({2}," + format + ")";
3662                        } else {
3663                            format = "{0}(" + format + ",{2})";
3664                        }
3665                    } else {
3666                        format = "{2} {0} " + format;
3667                    }
3668
3669                    filter = kendo.format(format, filter, value, field);
3670                }
3671            }
3672
3673            result.push(filter);
3674        }
3675
3676        filter = result.join(" " + logic + " ");
3677
3678        if (result.length > 1) {
3679            filter = "(" + filter + ")";
3680        }
3681
3682        return filter;
3683    }
3684
3685    extend(true, kendo.data, {
3686        schemas: {
3687            odata: {
3688                type: "json",
3689                data: function(data) {
3690                    return data.d.results || [data.d];
3691                },
3692                total: "d.__count"
3693            }
3694        },
3695        transports: {
3696            odata: {
3697                read: {
3698                    cache: true, // to prevent jQuery from adding cache buster
3699                    dataType: "jsonp",
3700                    jsonp: "$callback"
3701                },
3702                update: {
3703                    cache: true,
3704                    dataType: "json",
3705                    contentType: "application/json", // to inform the server the the request body is JSON encoded
3706                    type: "PUT" // can be PUT or MERGE
3707                },
3708                create: {
3709                    cache: true,
3710                    dataType: "json",
3711                    contentType: "application/json",
3712                    type: "POST" // must be POST to create new entity
3713                },
3714                destroy: {
3715                    cache: true,
3716                    dataType: "json",
3717                    type: "DELETE"
3718                },
3719                parameterMap: function(options, type) {
3720                    var params,
3721                        value,
3722                        option,
3723                        dataType;
3724
3725                    options = options || {};
3726                    type = type || "read";
3727                    dataType = (this.options || defaultDataType)[type];
3728                    dataType = dataType ? dataType.dataType : "json";
3729
3730                    if (type === "read") {
3731                        params = {
3732                            $inlinecount: "allpages"
3733                        };
3734
3735                        if (dataType != "json") {
3736                            params.$format = "json";
3737                        }
3738
3739                        for (option in options) {
3740                            if (mappers[option]) {
3741                                mappers[option](params, options[option]);
3742                            } else {
3743                                params[option] = options[option];
3744                            }
3745                        }
3746                    } else {
3747                        if (dataType !== "json") {
3748                            throw new Error("Only json dataType can be used for " + type + " operation.");
3749                        }
3750
3751                        if (type !== "destroy") {
3752                            for (option in options) {
3753                                value = options[option];
3754                                if (typeof value === "number") {
3755                                    options[option] = value + "";
3756                                }
3757                            }
3758
3759                            params = kendo.stringify(options);
3760                        }
3761                    }
3762
3763                    return params;
3764                }
3765            }
3766        }
3767    });
3768})(jQuery);
3769(function($, undefined) {
3770    var kendo = window.kendo,
3771        isArray = $.isArray,
3772        isPlainObject = $.isPlainObject,
3773        map = $.map,
3774        each = $.each,
3775        extend = $.extend,
3776        getter = kendo.getter,
3777        Class = kendo.Class;
3778
3779    var XmlDataReader = Class.extend({
3780        init: function(options) {
3781            var that = this,
3782                total = options.total,
3783                model = options.model,
3784                data = options.data;
3785
3786            if (model) {
3787                if (isPlainObject(model)) {
3788                    if (model.fields) {
3789                        each(model.fields, function(field, value) {
3790                            if (isPlainObject(value) && value.field) {
3791                                value = extend(value, { field: that.getter(value.field) });
3792                            } else {
3793                                value = { field: that.getter(value) };
3794                            }
3795                            model.fields[field] = value;
3796                        });
3797                    }
3798                    var id = model.id;
3799                    if (id) {
3800                        var idField = {};
3801
3802                        idField[that.xpathToMember(id, true)] = { field : that.getter(id) };
3803                        model.fields = extend(idField, model.fields);
3804                        model.id = that.xpathToMember(id);
3805                    }
3806                    model = kendo.data.Model.define(model);
3807                }
3808
3809                that.model = model;
3810            }
3811
3812            if (total) {
3813                total = that.getter(total);
3814                that.total = function(data) {
3815                    return parseInt(total(data), 10);
3816                };
3817            }
3818
3819            if (data) {
3820                data = that.xpathToMember(data);
3821                that.data = function(value) {
3822                    var result = that.evaluate(value, data),
3823                        modelInstance;
3824
3825                    result = isArray(result) ? result : [result];
3826
3827                    if (that.model && model.fields) {
3828                        modelInstance = new that.model();
3829
3830                        return map(result, function(value) {
3831                            if (value) {
3832                                var record = {}, field;
3833
3834                                for (field in model.fields) {
3835                                    record[field] = modelInstance._parse(field, model.fields[field].field(value));
3836                                }
3837
3838                                return record;
3839                            }
3840                        });
3841                    }
3842
3843                    return result;
3844                };
3845            }
3846        },
3847        total: function(result) {
3848            return this.data(result).length;
3849        },
3850        parseDOM: function(element) {
3851            var result = {},
3852                parsedNode,
3853                node,
3854                nodeType,
3855                nodeName,
3856                member,
3857                attribute,
3858                attributes = element.attributes,
3859                attributeCount = attributes.length,
3860                idx;
3861
3862            for (idx = 0; idx < attributeCount; idx++) {
3863                attribute = attributes[idx];
3864                result["@" + attribute.nodeName] = attribute.nodeValue;
3865            }
3866
3867            for (node = element.firstChild; node; node = node.nextSibling) {
3868                nodeType = node.nodeType;
3869
3870                if (nodeType === 3 || nodeType === 4) {
3871                    // text nodes or CDATA are stored as #text field
3872                    result["#text"] = node.nodeValue;
3873                } else if (nodeType === 1) {
3874                    // elements are stored as fields
3875                    parsedNode = this.parseDOM(node);
3876
3877                    nodeName = node.nodeName;
3878
3879                    member = result[nodeName];
3880
3881                    if (isArray(member)) {
3882                        // elements of same nodeName are stored as array
3883                        member.push(parsedNode);
3884                    } else if (member !== undefined) {
3885                        member = [member, parsedNode];
3886                    } else {
3887                        member = parsedNode;
3888                    }
3889
3890                    result[nodeName] = member;
3891                }
3892            }
3893            return result;
3894        },
3895
3896        evaluate: function(value, expression) {
3897            var members = expression.split("."),
3898                member,
3899                result,
3900                length,
3901                intermediateResult,
3902                idx;
3903
3904            while (member = members.shift()) {
3905                value = value[member];
3906
3907                if (isArray(value)) {
3908                    result = [];
3909                    expression = members.join(".");
3910
3911                    for (idx = 0, length = value.length; idx < length; idx++) {
3912                        intermediateResult = this.evaluate(value[idx], expression);
3913
3914                        intermediateResult = isArray(intermediateResult) ? intermediateResult : [intermediateResult];
3915
3916                        result.push.apply(result, intermediateResult);
3917                    }
3918
3919                    return result;
3920                }
3921            }
3922
3923            return value;
3924        },
3925
3926        parse: function(xml) {
3927            var documentElement,
3928                tree,
3929                result = {};
3930
3931            documentElement = xml.documentElement || $.parseXML(xml).documentElement;
3932
3933            tree = this.parseDOM(documentElement);
3934
3935            result[documentElement.nodeName] = tree;
3936
3937            return result;
3938        },
3939
3940        xpathToMember: function(member, raw) {
3941            if (!member) {
3942                return "";
3943            }
3944
3945            member = member.replace(/^\//, "") // remove the first "/"
3946                           .replace(/\//g, "."); // replace all "/" with "."
3947
3948            if (member.indexOf("@") >= 0) {
3949                // replace @attribute with '["@attribute"]'
3950                return member.replace(/\.?(@.*)/, raw? '$1':'["$1"]');
3951            }
3952
3953            if (member.indexOf("text()") >= 0) {
3954                // replace ".text()" with '["#text"]'
3955                return member.replace(/(\.?text\(\))/, raw? '#text':'["#text"]');
3956            }
3957
3958            return member;
3959        },
3960        getter: function(member) {
3961            return getter(this.xpathToMember(member), true);
3962        }
3963    });
3964
3965    $.extend(true, kendo.data, {
3966        XmlDataReader: XmlDataReader,
3967        readers: {
3968            xml: XmlDataReader
3969        }
3970    });
3971})(jQuery);
3972(function($, undefined) {
3973    var extend = $.extend,
3974        proxy = $.proxy,
3975        isFunction = $.isFunction,
3976        isPlainObject = $.isPlainObject,
3977        isEmptyObject = $.isEmptyObject,
3978        isArray = $.isArray,
3979        grep = $.grep,
3980        ajax = $.ajax,
3981        map,
3982        each = $.each,
3983        noop = $.noop,
3984        kendo = window.kendo,
3985        Observable = kendo.Observable,
3986        Class = kendo.Class,
3987        STRING = "string",
3988        FUNCTION = "function",
3989        CREATE = "create",
3990        READ = "read",
3991        UPDATE = "update",
3992        DESTROY = "destroy",
3993        CHANGE = "change",
3994        GET = "get",
3995        ERROR = "error",
3996        REQUESTSTART = "requestStart",
3997        crud = [CREATE, READ, UPDATE, DESTROY],
3998        identity = function(o) { return o; },
3999        getter = kendo.getter,
4000        stringify = kendo.stringify,
4001        math = Math,
4002        push = [].push,
4003        join = [].join,
4004        pop = [].pop,
4005        splice = [].splice,
4006        shift = [].shift,
4007        slice = [].slice,
4008        unshift = [].unshift,
4009        toString = {}.toString,
4010        stableSort = kendo.support.stableSort,
4011        dateRegExp = /^\/Date\((.*?)\)\/$/,
4012        quoteRegExp = /(?=['\\])/g;
4013
4014    var ObservableArray = Observable.extend({
4015        init: function(array, type) {
4016            var that = this;
4017
4018            that.type = type || ObservableObject;
4019
4020            Observable.fn.init.call(that);
4021
4022            that.length = array.length;
4023
4024            that.wrapAll(array, that);
4025        },
4026
4027        toJSON: function() {
4028            return slice.call(this);
4029        },
4030
4031        parent: noop,
4032
4033        wrapAll: function(source, target) {
4034            var that = this,
4035                idx,
4036                length,
4037                parent = function() {
4038                    return that;
4039                };
4040
4041            target = target || [];
4042
4043            for (idx = 0, length = source.length; idx < length; idx++) {
4044                target[idx] = that.wrap(source[idx], parent);
4045            }
4046
4047            return target;
4048        },
4049
4050        wrap: function(object, parent) {
4051            var that = this,
4052                observable;
4053
4054            if (object !== null && toString.call(object) === "[object Object]") {
4055                observable = object instanceof that.type || object instanceof Model;
4056
4057                if (!observable) {
4058                    object = object instanceof ObservableObject ? object.toJSON() : object;
4059                    object = new that.type(object);
4060                }
4061
4062                object.parent = parent;
4063
4064                object.bind(CHANGE, function(e) {
4065                    that.trigger(CHANGE, {
4066                        field: e.field,
4067                        items: [this],
4068                        action: "itemchange"
4069                    });
4070                });
4071            }
4072
4073            return object;
4074        },
4075
4076        push: function() {
4077            var index = this.length,
4078                items = this.wrapAll(arguments),
4079                result;
4080
4081            result = push.apply(this, items);
4082
4083            this.trigger(CHANGE, {
4084                action: "add",
4085                index: index,
4086                items: items
4087            });
4088
4089            return result;
4090        },
4091
4092        slice: slice,
4093
4094        join: join,
4095
4096        pop: function() {
4097            var length = this.length, result = pop.apply(this);
4098
4099            if (length) {
4100                this.trigger(CHANGE, {
4101                    action: "remove",
4102                    index: length - 1,
4103                    items:[result]
4104                });
4105            }
4106
4107            return result;
4108        },
4109
4110        splice: function(index, howMany, item) {
4111            var items = this.wrapAll(slice.call(arguments, 2)),
4112                result;
4113
4114            result = splice.apply(this, [index, howMany].concat(items));
4115
4116            if (result.length) {
4117                this.trigger(CHANGE, {
4118                    action: "remove",
4119                    index: index,
4120                    items: result
4121                });
4122            }
4123
4124            if (item) {
4125                this.trigger(CHANGE, {
4126                    action: "add",
4127                    index: index,
4128                    items: items
4129                });
4130            }
4131            return result;
4132        },
4133
4134        shift: function() {
4135            var length = this.length, result = shift.apply(this);
4136
4137            if (length) {
4138                this.trigger(CHANGE, {
4139                    action: "remove",
4140                    index: 0,
4141                    items:[result]
4142                });
4143            }
4144
4145            return result;
4146        },
4147
4148        unshift: function() {
4149            var items = this.wrapAll(arguments),
4150                result;
4151
4152            result = unshift.apply(this, items);
4153
4154            this.trigger(CHANGE, {
4155                action: "add",
4156                index: 0,
4157                items: items
4158            });
4159
4160            return result;
4161        },
4162
4163        indexOf: function(item) {
4164            var that = this,
4165                idx,
4166                length;
4167
4168            for (idx = 0, length = that.length; idx < length; idx++) {
4169                if (that[idx] === item) {
4170                    return idx;
4171                }
4172            }
4173            return -1;
4174        }
4175    });
4176
4177    var ObservableObject = Observable.extend({
4178        init: function(value) {
4179            var that = this,
4180                member,
4181                field,
4182                parent = function() {
4183                    return that;
4184                },
4185                type;
4186
4187            Observable.fn.init.call(this);
4188
4189            for (field in value) {
4190                member = value[field];
4191                if (field.charAt(0) != "_") {
4192                    type = toString.call(member);
4193
4194                    member = that.wrap(member, field, parent);
4195                }
4196                that[field] = member;
4197            }
4198
4199            that.uid = kendo.guid();
4200        },
4201
4202        shouldSerialize: function(field) {
4203            return this.hasOwnProperty(field) && field !== "_events" && typeof this[field] !== FUNCTION && field !== "uid";
4204        },
4205
4206        toJSON: function() {
4207            var result = {}, field;
4208
4209            for (field in this) {
4210                if (this.shouldSerialize(field)) {
4211                    result[field] = this[field];
4212                }
4213            }
4214
4215            return result;
4216        },
4217
4218        get: function(field) {
4219            var that = this, result;
4220
4221            that.trigger(GET, { field: field });
4222
4223            if (field === "this") {
4224                result = that;
4225            } else {
4226                result = kendo.getter(field, true)(that);
4227            }
4228
4229            return result;
4230        },
4231
4232        _set: function(field, value) {
4233            var that = this;
4234            if (field.indexOf(".")) {
4235                var paths = field.split("."),
4236                    path = "";
4237
4238                while (paths.length > 1) {
4239                    path += paths.shift();
4240                    var obj = kendo.getter(path, true)(that);
4241                    if (obj instanceof ObservableObject) {
4242                        obj.set(paths.join("."), value);
4243                        return;
4244                    }
4245                    path += ".";
4246                }
4247            }
4248
4249            kendo.setter(field)(that, value);
4250        },
4251
4252        set: function(field, value) {
4253            var that = this,
4254                current = that[field],
4255                parent = function() { return that; };
4256
4257            if (current !== value) {
4258                if (!that.trigger("set", { field: field, value: value })) {
4259
4260                    that._set(field, that.wrap(value, field, parent));
4261
4262                    that.trigger(CHANGE, { field: field });
4263                }
4264            }
4265        },
4266
4267        parent: noop,
4268
4269        wrap: function(object, field, parent) {
4270            var that = this,
4271                type = toString.call(object),
4272                isObservableArray = object instanceof ObservableArray;
4273
4274            if (object !== null && type === "[object Object]" && !(object instanceof DataSource) && !isObservableArray) {
4275                if (!(object instanceof ObservableObject)) {
4276                    object = new ObservableObject(object);
4277                }
4278
4279                object.parent = parent;
4280
4281                (function(field) {
4282                    object.bind(GET, function(e) {
4283                        e.field = field + "." + e.field;
4284                        that.trigger(GET, e);
4285                    });
4286
4287                    object.bind(CHANGE, function(e) {
4288                        e.field = field + "." + e.field;
4289                        that.trigger(CHANGE, e);
4290                    });
4291                })(field);
4292            } else if (object !== null && (type === "[object Array]" || isObservableArray)) {
4293                if (!isObservableArray) {
4294                    object = new ObservableArray(object);
4295                }
4296                object.parent = parent;
4297
4298                (function(field) {
4299                    object.bind(CHANGE, function(e) {
4300                        that.trigger(CHANGE, { field: field, index: e.index, items: e.items, action: e.action});
4301                    });
4302                })(field);
4303            } else if (object !== null && object instanceof DataSource) {
4304                object._parent = parent; // assign parent to the DataSource if part of observable object
4305            }
4306
4307
4308            return object;
4309        }
4310    });
4311
4312    function equal(x, y) {
4313        if (x === y) {
4314            return true;
4315        }
4316
4317        var xtype = $.type(x), ytype = $.type(y), field;
4318
4319        if (xtype !== ytype) {
4320            return false;
4321        }
4322
4323        if (xtype === "date") {
4324            return x.getTime() === y.getTime();
4325        }
4326
4327        if (xtype !== "object" && xtype !== "array") {
4328            return false;
4329        }
4330
4331        for (field in x) {
4332            if (!equal(x[field], y[field])) {
4333                return false;
4334            }
4335        }
4336
4337        return true;
4338    }
4339
4340    var parsers = {
4341        "number": function(value) {
4342            return kendo.parseFloat(value);
4343        },
4344
4345        "date": function(value) {
4346            if (typeof value === STRING) {
4347                var date = dateRegExp.exec(value);
4348                if (date) {
4349                    return new Date(parseInt(date[1], 10));
4350                }
4351            }
4352            return kendo.parseDate(value);
4353        },
4354
4355        "boolean": function(value) {
4356            if (typeof value === STRING) {
4357                return value.toLowerCase() === "true";
4358            }
4359            return !!value;
4360        },
4361
4362        "string": function(value) {
4363            return value + "";
4364        },
4365
4366        "default": function(value) {
4367            return value;
4368        }
4369    };
4370
4371    var defaultValues = {
4372        "string": "",
4373        "number": 0,
4374        "date": new Date(),
4375        "boolean": false,
4376        "default": ""
4377    };
4378
4379    var Model = ObservableObject.extend({
4380        init: function(data) {
4381            var that = this;
4382
4383            if (!data || $.isEmptyObject(data)) {
4384                data = $.extend({}, that.defaults, data);
4385            }
4386
4387            ObservableObject.fn.init.call(that, data);
4388
4389            that.dirty = false;
4390
4391            if (that.idField) {
4392                that.id = that.get(that.idField);
4393
4394                if (that.id === undefined) {
4395                    that.id = that._defaultId;
4396                }
4397            }
4398        },
4399
4400        shouldSerialize: function(field) {
4401            return ObservableObject.fn.shouldSerialize.call(this, field) && field !== "uid" && !(this.idField !== "id" && field === "id") && field !== "dirty" && field !== "_accessors";
4402        },
4403
4404        _parse: function(field, value) {
4405            var that = this,
4406            parse;
4407
4408            field = (that.fields || {})[field];
4409            if (field) {
4410                parse = field.parse;
4411                if (!parse && field.type) {
4412                    parse = parsers[field.type.toLowerCase()];
4413                }
4414            }
4415
4416            return parse ? parse(value) : value;
4417        },
4418
4419        editable: function(field) {
4420            field = (this.fields || {})[field];
4421            return field ? field.editable !== false : true;
4422        },
4423
4424        set: function(field, value, initiator) {
4425            var that = this;
4426
4427            if (that.editable(field)) {
4428                value = that._parse(field, value);
4429
4430                if (!equal(value, that.get(field))) {
4431                    that.dirty = true;
4432                    ObservableObject.fn.set.call(that, field, value, initiator);
4433                }
4434            }
4435        },
4436
4437        accept: function(data) {
4438            var that = this;
4439
4440            extend(that, data);
4441
4442            if (that.idField) {
4443                that.id = that.get(that.idField);
4444            }
4445            that.dirty = false;
4446        },
4447
4448        isNew: function() {
4449            return this.id === this._defaultId;
4450        }
4451    });
4452
4453    Model.define = function(options) {
4454        var model,
4455            proto = extend({}, { defaults: {} }, options),
4456            id = proto.id;
4457
4458        if (id) {
4459            proto.idField = id;
4460        }
4461
4462        if (proto.id) {
4463            delete proto.id;
4464        }
4465
4466        if (id) {
4467            proto.defaults[id] = proto._defaultId = "";
4468        }
4469
4470        for (var name in proto.fields) {
4471            var field = proto.fields[name],
4472            type = field.type || "default",
4473            value = null;
4474
4475            name = field.field || name;
4476
4477            if (!field.nullable) {
4478                value = proto.defaults[name] = field.defaultValue !== undefined ? field.defaultValue : defaultValues[type.toLowerCase()];
4479            }
4480
4481            if (options.id === name) {
4482                proto._defaultId = value;
4483            }
4484
4485            proto.defaults[name] = value;
4486
4487            field.parse = field.parse || parsers[type];
4488        }
4489
4490        model = Model.extend(proto);
4491
4492        if (proto.fields) {
4493            model.fields = proto.fields;
4494            model.idField = proto.idField;
4495        }
4496
4497        return model;
4498    };
4499
4500    var Comparer = {
4501        selector: function(field) {
4502            return isFunction(field) ? field : getter(field);
4503        },
4504
4505        asc: function(field) {
4506            var selector = this.selector(field);
4507            return function (a, b) {
4508                a = selector(a);
4509                b = selector(b);
4510
4511                return a > b ? 1 : (a < b ? -1 : 0);
4512            };
4513        },
4514
4515        desc: function(field) {
4516            var selector = this.selector(field);
4517            return function (a, b) {
4518                a = selector(a);
4519                b = selector(b);
4520
4521                return a < b ? 1 : (a > b ? -1 : 0);
4522            };
4523        },
4524
4525        create: function(descriptor) {
4526            return this[descriptor.dir.toLowerCase()](descriptor.field);
4527        },
4528
4529        combine: function(comparers) {
4530            return function(a, b) {
4531                var result = comparers[0](a, b),
4532                    idx,
4533                    length;
4534
4535                for (idx = 1, length = comparers.length; idx < length; idx ++) {
4536                    result = result || comparers[idx](a, b);
4537                }
4538
4539                return result;
4540            };
4541        }
4542    };
4543
4544    var PositionComparer = extend({}, Comparer, {
4545        asc: function(field) {
4546            var selector = this.selector(field);
4547            return function (a, b) {
4548                var valueA = selector(a);
4549                var valueB = selector(b);
4550
4551                if (valueA === valueB) {
4552                    return a.__position - b.__position;
4553                }
4554                return valueA > valueB ? 1 : (valueA < valueB ? -1 : 0);
4555            };
4556        },
4557
4558        desc: function(field) {
4559            var selector = this.selector(field);
4560            return function (a, b) {
4561                var valueA = selector(a);
4562                var valueB = selector(b);
4563
4564                if (valueA === valueB) {
4565                    return a.__position - b.__position;
4566                }
4567
4568                return valueA < valueB ? 1 : (valueA > valueB ? -1 : 0);
4569            };
4570        }
4571    });
4572
4573    map = function (array, callback) {
4574        var idx, length = array.length, result = new Array(length);
4575
4576        for (idx = 0; idx < length; idx++) {
4577            result[idx] = callback(array[idx], idx, array);
4578        }
4579
4580        return result;
4581    };
4582
4583    var operators = (function(){
4584
4585        function quote(value) {
4586            return value.replace(quoteRegExp, "\\");
4587        }
4588
4589        function operator(op, a, b, ignore) {
4590            var date;
4591
4592            if (b != null) {
4593                if (typeof b === STRING) {
4594                    b = quote(b);
4595                    date = dateRegExp.exec(b);
4596                    if (date) {
4597                        b = new Date(+date[1]);
4598                    } else if (ignore) {
4599                        b = "'" + b.toLowerCase() + "'";
4600                        a = a + ".toLowerCase()";
4601                    } else {
4602                        b = "'" + b + "'";
4603                    }
4604                }
4605
4606                if (b.getTime) {
4607                    //b looks like a Date
4608                    a = "(" + a + "?" + a + ".getTime():" + a + ")";
4609                    b = b.getTime();
4610                }
4611            }
4612
4613            return a + " " + op + " " + b;
4614        }
4615
4616        return {
4617            eq: function(a, b, ignore) {
4618                return operator("==", a, b, ignore);
4619            },
4620            neq: function(a, b, ignore) {
4621                return operator("!=", a, b, ignore);
4622            },
4623            gt: function(a, b, ignore) {
4624                return operator(">", a, b, ignore);
4625            },
4626            gte: function(a, b, ignore) {
4627                return operator(">=", a, b, ignore);
4628            },
4629            lt: function(a, b, ignore) {
4630                return operator("<", a, b, ignore);
4631            },
4632            lte: function(a, b, ignore) {
4633                return operator("<=", a, b, ignore);
4634            },
4635            startswith: function(a, b, ignore) {
4636                if (ignore) {
4637                    a = a + ".toLowerCase()";
4638                    if (b) {
4639                        b = b.toLowerCase();
4640                    }
4641                }
4642
4643                if (b) {
4644                    b = quote(b);
4645                }
4646
4647                return a + ".lastIndexOf('" + b + "', 0) == 0";
4648            },
4649            endswith: function(a, b, ignore) {
4650                if (ignore) {
4651                    a = a + ".toLowerCase()";
4652                    if (b) {
4653                        b = b.toLowerCase();
4654                    }
4655                }
4656
4657                if (b) {
4658                    b = quote(b);
4659                }
4660
4661                return a + ".lastIndexOf('" + b + "') == " + a + ".length - " + (b || "").length;
4662            },
4663            contains: function(a, b, ignore) {
4664                if (ignore) {
4665                    a = a + ".toLowerCase()";
4666                    if (b) {
4667                        b = b.toLowerCase();
4668                    }
4669                }
4670
4671                if (b) {
4672                    b = quote(b);
4673                }
4674
4675                return a + ".indexOf('" + b + "') >= 0";
4676            }
4677        };
4678    })();
4679
4680    function Query(data) {
4681        this.data = data || [];
4682    }
4683
4684    Query.filterExpr = function(expression) {
4685        var expressions = [],
4686            logic = { and: " && ", or: " || " },
4687            idx,
4688            length,
4689            filter,
4690            expr,
4691            fieldFunctions = [],
4692            operatorFunctions = [],
4693            field,
4694            operator,
4695            filters = expression.filters;
4696
4697        for (idx = 0, length = filters.length; idx < length; idx++) {
4698            filter = filters[idx];
4699            field = filter.field;
4700            operator = filter.operator;
4701
4702            if (filter.filters) {
4703                expr = Query.filterExpr(filter);
4704                //Nested function fields or operators - update their index e.g. __o[0] -> __o[1]
4705                filter = expr.expression
4706                .replace(/__o\[(\d+)\]/g, function(match, index) {
4707                    index = +index;
4708                    return "__o[" + (operatorFunctions.length + index) + "]";
4709                })
4710                .replace(/__f\[(\d+)\]/g, function(match, index) {
4711                    index = +index;
4712                    return "__f[" + (fieldFunctions.length + index) + "]";
4713                });
4714
4715                operatorFunctions.push.apply(operatorFunctions, expr.operators);
4716                fieldFunctions.push.apply(fieldFunctions, expr.fields);
4717            } else {
4718                if (typeof field === FUNCTION) {
4719                    expr = "__f[" + fieldFunctions.length +"](d)";
4720                    fieldFunctions.push(field);
4721                } else {
4722                    expr = kendo.expr(field);
4723                }
4724
4725                if (typeof operator === FUNCTION) {
4726                    filter = "__o[" + operatorFunctions.length + "](" + expr + ", " + filter.value + ")";
4727                    operatorFunctions.push(operator);
4728                } else {
4729                    filter = operators[(operator || "eq").toLowerCase()](expr, filter.value, filter.ignoreCase !== undefined? filter.ignoreCase : true);
4730                }
4731            }
4732
4733            expressions.push(filter);
4734        }
4735
4736        return  { expression: "(" + expressions.join(logic[expression.logic]) + ")", fields: fieldFunctions, operators: operatorFunctions };
4737    };
4738
4739    function normalizeSort(field, dir) {
4740        if (field) {
4741            var descriptor = typeof field === STRING ? { field: field, dir: dir } : field,
4742            descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);
4743
4744            return grep(descriptors, function(d) { return !!d.dir; });
4745        }
4746    }
4747
4748    var operatorMap = {
4749        "==": "eq",
4750        equals: "eq",
4751        isequalto: "eq",
4752        equalto: "eq",
4753        equal: "eq",
4754        "!=": "neq",
4755        ne: "neq",
4756        notequals: "neq",
4757        isnotequalto: "neq",
4758        notequalto: "neq",
4759        notequal: "neq",
4760        "<": "lt",
4761        islessthan: "lt",
4762        lessthan: "lt",
4763        less: "lt",
4764        "<=": "lte",
4765        le: "lte",
4766        islessthanorequalto: "lte",
4767        lessthanequal: "lte",
4768        ">": "gt",
4769        isgreaterthan: "gt",
4770        greaterthan: "gt",
4771        greater: "gt",
4772        ">=": "gte",
4773        isgreaterthanorequalto: "gte",
4774        greaterthanequal: "gte",
4775        ge: "gte"
4776    };
4777
4778    function normalizeOperator(expression) {
4779        var idx,
4780        length,
4781        filter,
4782        operator,
4783        filters = expression.filters;
4784
4785        if (filters) {
4786            for (idx = 0, length = filters.length; idx < length; idx++) {
4787                filter = filters[idx];
4788                operator = filter.operator;
4789
4790                if (operator && typeof operator === STRING) {
4791                    filter.operator = operatorMap[operator.toLowerCase()] || operator;
4792                }
4793
4794                normalizeOperator(filter);
4795            }
4796        }
4797    }
4798
4799    function normalizeFilter(expression) {
4800        if (expression && !isEmptyObject(expression)) {
4801            if (isArray(expression) || !expression.filters) {
4802                expression = {
4803                    logic: "and",
4804                    filters: isArray(expression) ? expression : [expression]
4805                };
4806            }
4807
4808            normalizeOperator(expression);
4809
4810            return expression;
4811        }
4812    }
4813
4814    Query.normalizeFilter = normalizeFilter;
4815
4816    function normalizeAggregate(expressions) {
4817        return isArray(expressions) ? expressions : [expressions];
4818    }
4819
4820    function normalizeGroup(field, dir) {
4821        var descriptor = typeof field === STRING ? { field: field, dir: dir } : field,
4822        descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);
4823
4824        return map(descriptors, function(d) { return { field: d.field, dir: d.dir || "asc", aggregates: d.aggregates }; });
4825    }
4826
4827    Query.prototype = {
4828        toArray: function () {
4829            return this.data;
4830        },
4831        range: function(index, count) {
4832            return new Query(this.data.slice(index, index + count));
4833        },
4834        skip: function (count) {
4835            return new Query(this.data.slice(count));
4836        },
4837        take: function (count) {
4838            return new Query(this.data.slice(0, count));
4839        },
4840        select: function (selector) {
4841            return new Query(map(this.data, selector));
4842        },
4843        orderBy: function (selector) {
4844            var result = this.data.slice(0),
4845            comparer = isFunction(selector) || !selector ? Comparer.asc(selector) : selector.compare;
4846
4847            return new Query(result.sort(comparer));
4848        },
4849        orderByDescending: function (selector) {
4850            return new Query(this.data.slice(0).sort(Comparer.desc(selector)));
4851        },
4852        sort: function(field, dir, comparer) {
4853            var idx,
4854            length,
4855            descriptors = normalizeSort(field, dir),
4856            comparers = [];
4857
4858            comparer = comparer || Comparer;
4859
4860            if (descriptors.length) {
4861                for (idx = 0, length = descriptors.length; idx < length; idx++) {
4862                    comparers.push(comparer.create(descriptors[idx]));
4863                }
4864
4865                return this.orderBy({ compare: comparer.combine(comparers) });
4866            }
4867
4868            return this;
4869        },
4870
4871        filter: function(expressions) {
4872            var idx,
4873            current,
4874            length,
4875            compiled,
4876            predicate,
4877            data = this.data,
4878            fields,
4879            operators,
4880            result = [],
4881            filter;
4882
4883            expressions = normalizeFilter(expressions);
4884
4885            if (!expressions || expressions.filters.length === 0) {
4886                return this;
4887            }
4888
4889            compiled = Query.filterExpr(expressions);
4890            fields = compiled.fields;
4891            operators = compiled.operators;
4892
4893            predicate = filter = new Function("d, __f, __o", "return " + compiled.expression);
4894
4895            if (fields.length || operators.length) {
4896                filter = function(d) {
4897                    return predicate(d, fields, operators);
4898                };
4899            }
4900
4901            for (idx = 0, length = data.length; idx < length; idx++) {
4902                current = data[idx];
4903
4904                if (filter(current)) {
4905                    result.push(current);
4906                }
4907            }
4908            return new Query(result);
4909        },
4910
4911        group: function(descriptors, allData) {
4912            descriptors =  normalizeGroup(descriptors || []);
4913            allData = allData || this.data;
4914
4915            var that = this,
4916            result = new Query(that.data),
4917            descriptor;
4918
4919            if (descriptors.length > 0) {
4920                descriptor = descriptors[0];
4921                result = result.groupBy(descriptor).select(function(group) {
4922                    var data = new Query(allData).filter([ { field: group.field, operator: "eq", value: group.value } ]);
4923                    return {
4924                        field: group.field,
4925                        value: group.value,
4926                        items: descriptors.length > 1 ? new Query(group.items).group(descriptors.slice(1), data.toArray()).toArray() : group.items,
4927                        hasSubgroups: descriptors.length > 1,
4928                        aggregates: data.aggregate(descriptor.aggregates)
4929                    };
4930                });
4931            }
4932            return result;
4933        },
4934
4935        groupBy: function(descriptor) {
4936            if (isEmptyObject(descriptor) || !this.data.length) {
4937                return new Query([]);
4938            }
4939
4940            var field = descriptor.field,
4941                sorted = this._sortForGrouping(field, descriptor.dir || "asc"),
4942                accessor = kendo.accessor(field),
4943                item,
4944                groupValue = accessor.get(sorted[0], field),
4945                group = {
4946                    field: field,
4947                    value: groupValue,
4948                    items: []
4949                },
4950                currentValue,
4951                idx,
4952                len,
4953                result = [group];
4954
4955            for(idx = 0, len = sorted.length; idx < len; idx++) {
4956                item = sorted[idx];
4957                currentValue = accessor.get(item, field);
4958                if(!groupValueComparer(groupValue, currentValue)) {
4959                    groupValue = currentValue;
4960                    group = {
4961                        field: field,
4962                        value: groupValue,
4963                        items: []
4964                    };
4965                    result.push(group);
4966                }
4967                group.items.push(item);
4968            }
4969            return new Query(result);
4970        },
4971
4972        _sortForGrouping: function(field, dir) {
4973            var idx, length,
4974                data = this.data;
4975
4976            if (!stableSort) {
4977                for (idx = 0, length = data.length; idx < length; idx++) {
4978                    data[idx].__position = idx;
4979                }
4980
4981                data = new Query(data).sort(field, dir, PositionComparer).toArray();
4982
4983                for (idx = 0, length = data.length; idx < length; idx++) {
4984                    delete data[idx].__position;
4985                }
4986                return data;
4987            }
4988            return this.sort(field, dir).toArray();
4989        },
4990
4991        aggregate: function (aggregates) {
4992            var idx,
4993            len,
4994            result = {};
4995
4996            if (aggregates && aggregates.length) {
4997                for(idx = 0, len = this.data.length; idx < len; idx++) {
4998                    calculateAggregate(result, aggregates, this.data[idx], idx, len);
4999                }
5000            }
5001            return result;
5002        }
5003    };
5004
5005    function groupValueComparer(a, b) {
5006        if (a && a.getTime && b && b.getTime) {
5007            return a.getTime() === b.getTime();
5008        }
5009        return a === b;
5010    }
5011
5012    function calculateAggregate(accumulator, aggregates, item, index, length) {
5013        aggregates = aggregates || [];
5014        var idx,
5015        aggr,
5016        functionName,
5017        len = aggregates.length;
5018
5019        for (idx = 0; idx < len; idx++) {
5020            aggr = aggregates[idx];
5021            functionName = aggr.aggregate;
5022            var field = aggr.field;
5023            accumulator[field] = accumulator[field] || {};
5024            accumulator[field][functionName] = functions[functionName.toLowerCase()](accumulator[field][functionName], item, kendo.accessor(field), index, length);
5025        }
5026    }
5027
5028    var functions = {
5029        sum: function(accumulator, item, accessor) {
5030            return (accumulator || 0) + accessor.get(item);
5031        },
5032        count: function(accumulator, item, accessor) {
5033            return (accumulator || 0) + 1;
5034        },
5035        average: function(accumulator, item, accessor, index, length) {
5036            accumulator = (accumulator || 0) + accessor.get(item);
5037            if(index == length - 1) {
5038                accumulator = accumulator / length;
5039            }
5040            return accumulator;
5041        },
5042        max: function(accumulator, item, accessor) {
5043            var value = accessor.get(item);
5044
5045            accumulator = accumulator || 0;
5046
5047            if(accumulator < value) {
5048                accumulator = value;
5049            }
5050            return accumulator;
5051        },
5052        min: function(accumulator, item, accessor) {
5053            var value = accessor.get(item);
5054
5055            accumulator = (accumulator || value);
5056
5057            if(accumulator > value) {
5058                accumulator = value;
5059            }
5060            return accumulator;
5061        }
5062    };
5063
5064    function toJSON(array) {
5065        var idx, length = array.length, result = new Array(length);
5066
5067        for (idx = 0; idx < length; idx++) {
5068            result[idx] = array[idx].toJSON();
5069        }
5070
5071        return result;
5072    }
5073
5074    function process(data, options) {
5075        options = options || {};
5076
5077        var query = new Query(data),
5078            group = options.group,
5079            sort = normalizeGroup(group || []).concat(normalizeSort(options.sort || [])),
5080            total,
5081            filter = options.filter,
5082            skip = options.skip,
5083            take = options.take;
5084
5085        if (filter) {
5086            query = query.filter(filter);
5087            total = query.toArray().length;
5088        }
5089
5090        if (sort) {
5091            query = query.sort(sort);
5092
5093            if (group) {
5094                data = query.toArray();
5095            }
5096        }
5097
5098        if (skip !== undefined && take !== undefined) {
5099            query = query.range(skip, take);
5100        }
5101
5102        if (group) {
5103            query = query.group(group, data);
5104        }
5105
5106        return {
5107            total: total,
5108            data: query.toArray()
5109        };
5110    }
5111
5112    function calculateAggregates(data, options) {
5113        options = options || {};
5114
5115        var query = new Query(data),
5116            aggregates = options.aggregate,
5117            filter = options.filter;
5118
5119        if(filter) {
5120            query = query.filter(filter);
5121        }
5122
5123        return query.aggregate(aggregates);
5124    }
5125
5126    var LocalTransport = Class.extend({
5127        init: function(options) {
5128            this.data = options.data;
5129        },
5130
5131        read: function(options) {
5132            options.success(this.data);
5133        },
5134        update: function(options) {
5135            options.success(options.data);
5136        },
5137        create: function(options) {
5138            options.success(options.data);
5139        },
5140        destroy: noop
5141    });
5142
5143    var RemoteTransport = Class.extend( {
5144        init: function(options) {
5145            var that = this, parameterMap;
5146
5147            options = that.options = extend({}, that.options, options);
5148
5149            each(crud, function(index, type) {
5150                if (typeof options[type] === STRING) {
5151                    options[type] = {
5152                        url: options[type]
5153                    };
5154                }
5155            });
5156
5157            that.cache = options.cache? Cache.create(options.cache) : {
5158                find: noop,
5159                add: noop
5160            };
5161
5162            parameterMap = options.parameterMap;
5163
5164            that.parameterMap = isFunction(parameterMap) ? parameterMap : function(options) {
5165                var result = {};
5166
5167                each(options, function(option, value) {
5168                    if (option in parameterMap) {
5169                        option = parameterMap[option];
5170                        if (isPlainObject(option)) {
5171                            value = option.value(value);
5172                            option = option.key;
5173                        }
5174                    }
5175
5176                    result[option] = value;
5177                });
5178
5179                return result;
5180            };
5181        },
5182
5183        options: {
5184            parameterMap: identity
5185        },
5186
5187        create: function(options) {
5188            return ajax(this.setup(options, CREATE));
5189        },
5190
5191        read: function(options) {
5192            var that = this,
5193                success,
5194                error,
5195                result,
5196                cache = that.cache;
5197
5198            options = that.setup(options, READ);
5199
5200            success = options.success || noop;
5201            error = options.error || noop;
5202
5203            result = cache.find(options.data);
5204
5205            if(result !== undefined) {
5206                success(result);
5207            } else {
5208                options.success = function(result) {
5209                    cache.add(options.data, result);
5210
5211                    success(result);
5212                };
5213
5214                $.ajax(options);
5215            }
5216        },
5217
5218        update: function(options) {
5219            return ajax(this.setup(options, UPDATE));
5220        },
5221
5222        destroy: function(options) {
5223            return ajax(this.setup(options, DESTROY));
5224        },
5225
5226        setup: function(options, type) {
5227            options = options || {};
5228
5229            var that = this,
5230                parameters,
5231                operation = that.options[type],
5232                data = isFunction(operation.data) ? operation.data() : operation.data;
5233
5234            options = extend(true, {}, operation, options);
5235            parameters = extend(data, options.data);
5236
5237            options.data = that.parameterMap(parameters, type);
5238
5239            if (isFunction(options.url)) {
5240                options.url = options.url(parameters);
5241            }
5242
5243            return options;
5244        }
5245    });
5246
5247    var Cache = Class.extend({
5248        init: function() {
5249            this._store = {};
5250        },
5251        add: function(key, data) {
5252            if(key !== undefined) {
5253                this._store[stringify(key)] = data;
5254            }
5255        },
5256        find: function(key) {
5257            return this._store[stringify(key)];
5258        },
5259        clear: function() {
5260            this._store = {};
5261        },
5262        remove: function(key) {
5263            delete this._store[stringify(key)];
5264        }
5265    });
5266
5267    Cache.create = function(options) {
5268        var store = {
5269            "inmemory": function() { return new Cache(); }
5270        };
5271
5272        if (isPlainObject(options) && isFunction(options.find)) {
5273            return options;
5274        }
5275
5276        if (options === true) {
5277            return new Cache();
5278        }
5279
5280        return store[options]();
5281    };
5282
5283    var DataReader = Class.extend({
5284        init: function(schema) {
5285            var that = this, member, get, model;
5286
5287            schema = schema || {};
5288
5289            for (member in schema) {
5290                get = schema[member];
5291
5292                that[member] = typeof get === STRING ? getter(get) : get;
5293            }
5294
5295            if (isPlainObject(that.model)) {
5296                that.model = model = kendo.data.Model.define(that.model);
5297
5298                var dataFunction = that.data,
5299                getters = {};
5300
5301                if (model.fields) {
5302                    each(model.fields, function(field, value) {
5303                        if (isPlainObject(value) && value.field) {
5304                            getters[value.field] = getter(value.field);
5305                        } else {
5306                            getters[field] = getter(field);
5307                        }
5308                    });
5309                }
5310
5311                that.data = function(data) {
5312                    var record,
5313                        getter,
5314                        idx,
5315                        length,
5316                        modelInstance = new that.model();
5317
5318                    data = dataFunction(data);
5319
5320                    if (data && !isEmptyObject(getters)) {
5321                        if (toString.call(data) !== "[object Array]" && !(data instanceof ObservableArray)) {
5322                            data = [data];
5323                        }
5324
5325                        for (idx = 0, length = data.length; idx < length; idx++) {
5326                            record = data[idx];
5327                            for (getter in getters) {
5328                                record[getter] = modelInstance._parse(getter, getters[getter](record));
5329                            }
5330                        }
5331                    }
5332
5333                    return data || [];
5334                };
5335            }
5336        },
5337        parse: identity,
5338        data: identity,
5339        total: function(data) {
5340            return data.length;
5341        },
5342        groups: identity,
5343        status: function(data) {
5344            return data.status;
5345        },
5346        aggregates: function() {
5347            return {};
5348        }
5349    });
5350
5351    function flattenGroups(data) {
5352        var idx, length, result = [];
5353
5354        for (idx = 0, length = data.length; idx < length; idx++) {
5355            if (data[idx].hasSubgroups) {
5356                result = result.concat(flattenGroups(data[idx].items));
5357            } else {
5358                result = result.concat(data[idx].items.toJSON());
5359            }
5360        }
5361        return result;
5362    }
5363
5364    var DataSource = Observable.extend({
5365        init: function(options) {
5366            var that = this, id, model, transport;
5367
5368            options = that.options = extend({}, that.options, options);
5369
5370            extend(that, {
5371                _map: {},
5372                _prefetch: {},
5373                _data: [],
5374                _ranges: [],
5375                _view: [],
5376                _pristine: [],
5377                _destroyed: [],
5378                _pageSize: options.pageSize,
5379                _page: options.page  || (options.pageSize ? 1 : undefined),
5380                _sort: normalizeSort(options.sort),
5381                _filter: normalizeFilter(options.filter),
5382                _group: normalizeGroup(options.group),
5383                _aggregate: options.aggregate
5384            });
5385
5386            Observable.fn.init.call(that);
5387
5388            transport = options.transport;
5389
5390            if (transport) {
5391                transport.read = typeof transport.read === STRING ? { url: transport.read } : transport.read;
5392
5393                if (options.type) {
5394                    transport = extend(true, {}, kendo.data.transports[options.type], transport);
5395                    options.schema = extend(true, {}, kendo.data.schemas[options.type], options.schema);
5396                }
5397
5398                that.transport = isFunction(transport.read) ? transport: new RemoteTransport(transport);
5399            } else {
5400                that.transport = new LocalTransport({ data: options.data });
5401            }
5402
5403            that.reader = new kendo.data.readers[options.schema.type || "json" ](options.schema);
5404
5405            model = that.reader.model || {};
5406
5407            that._data = that._observe(that._data);
5408
5409            id = model.id;
5410
5411            if (id) {
5412                that.id = function(record) {
5413                    return id(record);
5414                };
5415            }
5416
5417            that.bind([ERROR, CHANGE, REQUESTSTART], options);
5418        },
5419
5420        options: {
5421            data: [],
5422            schema: {},
5423            serverSorting: false,
5424            serverPaging: false,
5425            serverFiltering: false,
5426            serverGrouping: false,
5427            serverAggregates: false,
5428            sendAllFields: true,
5429            batch: false
5430        },
5431
5432        _flatData: function(data) {
5433            if (this.options.serverGrouping) {
5434                return flattenGroups(data);
5435            }
5436            return data;
5437        },
5438
5439        get: function(id) {
5440            var idx, length, data = this._flatData(this._data);
5441
5442            for (idx = 0, length = data.length; idx < length; idx++) {
5443                if (data[idx].id == id) {
5444                    return data[idx];
5445                }
5446            }
5447        },
5448
5449        getByUid: function(id) {
5450            var idx, length, data = this._flatData(this._data);
5451
5452            for (idx = 0, length = data.length; idx < length; idx++) {
5453                if (data[idx].uid == id) {
5454                    return data[idx];
5455                }
5456            }
5457        },
5458
5459        sync: function() {
5460            var that = this,
5461            idx,
5462            length,
5463            created = [],
5464            updated = [],
5465            destroyed = that._destroyed,
5466            data = that._data;
5467
5468            if (!that.reader.model) {
5469                return;
5470            }
5471
5472            for (idx = 0, length = data.length; idx < length; idx++) {
5473                if (data[idx].isNew()) {
5474                    created.push(data[idx]);
5475                } else if (data[idx].dirty) {
5476                    updated.push(data[idx]);
5477                }
5478            }
5479
5480            var promises = that._send("create", created);
5481
5482            promises.push.apply(promises ,that._send("update", updated));
5483            promises.push.apply(promises ,that._send("destroy", destroyed));
5484
5485            $.when.apply(null, promises)
5486            .then(function() {
5487                var idx,
5488                length;
5489
5490                for (idx = 0, length = arguments.length; idx < length; idx++){
5491                    that._accept(arguments[idx]);
5492                }
5493
5494                that._change();
5495            });
5496
5497        },
5498
5499        _accept: function(result) {
5500            var that = this,
5501                models = result.models,
5502                response = result.response,
5503                idx = 0,
5504                pristine = that.reader.data(that._pristine),
5505                type = result.type,
5506                length;
5507
5508            if (response) {
5509                response = that.reader.parse(response);
5510
5511                response = that.reader.data(response);
5512
5513                if (!$.isArray(response)) {
5514                    response = [response];
5515                }
5516            } else {
5517                response = $.map(models, function(model) { return model.toJSON(); } );
5518            }
5519
5520            if (type === "destroy") {
5521                that._destroyed = [];
5522            }
5523
5524            for (idx = 0, length = models.length; idx < length; idx++) {
5525                if (type !== "destroy") {
5526                    models[idx].accept(response[idx]);
5527
5528                    if (type === "create") {
5529                        pristine.push(models[idx]);
5530                    } else if (type === "update") {
5531                        extend(pristine[that._pristineIndex(models[idx])], response[idx]);
5532                    }
5533                } else {
5534                    pristine.splice(that._pristineIndex(models[idx]), 1);
5535                }
5536            }
5537        },
5538
5539        _pristineIndex: function(model) {
5540            var that = this,
5541            idx,
5542            length,
5543            pristine = that.reader.data(that._pristine);
5544
5545            for (idx = 0, length = pristine.length; idx < length; idx++) {
5546                if (pristine[idx][model.idField] === model.id) {
5547                    return idx;
5548                }
5549            }
5550            return -1;
5551        },
5552
5553        _promise: function(data, models, type) {
5554            var that = this,
5555            transport = that.transport;
5556
5557            return $.Deferred(function(deferred) {
5558                transport[type].call(transport, extend({
5559                    success: function(response) {
5560                        deferred.resolve({
5561                            response: response,
5562                            models: models,
5563                            type: type
5564                        });
5565                    },
5566                    error: function(response) {
5567                        deferred.reject(response);
5568                        that.trigger(ERROR, response);
5569                    }
5570                }, data)
5571                );
5572            }).promise();
5573        },
5574
5575        _send: function(method, data) {
5576            var that = this,
5577                idx,
5578                length,
5579                promises = [];
5580
5581            if (that.options.batch) {
5582                if (data.length) {
5583                    promises.push(that._promise( { data: { models: toJSON(data) } }, data , method));
5584                }
5585            } else {
5586                for (idx = 0, length = data.length; idx < length; idx++) {
5587                    promises.push(that._promise( { data: data[idx].toJSON() }, [ data[idx] ], method));
5588                }
5589            }
5590
5591            return promises;
5592        },
5593
5594        add: function(model) {
5595            return this.insert(this._data.length, model);
5596        },
5597
5598        insert: function(index, model) {
5599            if (!model) {
5600                model = index;
5601                index = 0;
5602            }
5603
5604            if (!(model instanceof Model)) {
5605                if (this.reader.model) {
5606                    model = new this.reader.model(model);
5607                } else {
5608                    model = new ObservableObject(model);
5609                }
5610            }
5611
5612            this._data.splice(index, 0, model);
5613
5614            return model;
5615        },
5616
5617        cancelChanges: function(model) {
5618            var that = this,
5619                pristineIndex,
5620                pristine = that.reader.data(that._pristine),
5621                index;
5622
5623            if (model instanceof kendo.data.Model) {
5624                index = that.indexOf(model);
5625                pristineIndex = that._pristineIndex(model);
5626                if (index != -1) {
5627                    if (pristineIndex != -1 && !model.isNew()) {
5628                        extend(true, that._data[index], pristine[pristineIndex]);
5629                    } else {
5630                        that._data.splice(index, 1);
5631                    }
5632                }
5633            } else {
5634                that._destroyed = [];
5635                that._data = that._observe(pristine);
5636                that._change();
5637            }
5638        },
5639
5640        read: function(data) {
5641            var that = this, params = that._params(data);
5642
5643            that._queueRequest(params, function() {
5644                that.trigger(REQUESTSTART);
5645                that._ranges = [];
5646                that.transport.read({
5647                    data: params,
5648                    success: proxy(that.success, that),
5649                    error: proxy(that.error, that)
5650                });
5651            });
5652        },
5653
5654        indexOf: function(model) {
5655            var idx, length, data = this._data;
5656
5657            if (model) {
5658                for (idx = 0, length = data.length; idx < length; idx++) {
5659                    if (data[idx].uid == model.uid) {
5660                        return idx;
5661                    }
5662                }
5663            }
5664            return -1;
5665        },
5666
5667        _params: function(data) {
5668            var that = this,
5669            options =  extend({
5670                take: that.take(),
5671                skip: that.skip(),
5672                page: that.page(),
5673                pageSize: that.pageSize(),
5674                sort: that._sort,
5675                filter: that._filter,
5676                group: that._group,
5677                aggregate: that._aggregate
5678            }, data);
5679
5680            if (!that.options.serverPaging) {
5681                delete options.take;
5682                delete options.skip;
5683                delete options.page;
5684                delete options.pageSize;
5685            }
5686            return options;
5687        },
5688
5689        _queueRequest: function(options, callback) {
5690            var that = this;
5691            if (!that._requestInProgress) {
5692                that._requestInProgress = true;
5693                that._pending = undefined;
5694                callback();
5695            } else {
5696                that._pending = { callback: proxy(callback, that), options: options };
5697            }
5698        },
5699
5700        _dequeueRequest: function() {
5701            var that = this;
5702            that._requestInProgress = false;
5703            if (that._pending) {
5704                that._queueRequest(that._pending.options, that._pending.callback);
5705            }
5706        },
5707
5708        remove: function(model) {
5709            var idx, length, data = this._data;
5710
5711            for (idx = 0, length = data.length; idx < length; idx++) {
5712                if (data[idx].uid == model.uid) {
5713                    model = data[idx];
5714                    data.splice(idx, 1);
5715                    return model;
5716                }
5717            }
5718        },
5719
5720        error: function(xhr, status, errorThrown) {
5721            this._dequeueRequest();
5722            this.trigger(ERROR, { xhr: xhr, status: status, errorThrown: errorThrown });
5723        },
5724
5725        _parent: noop,
5726
5727        success: function(data) {
5728            var that = this,
5729                options = that.options,
5730                hasGroups = options.serverGrouping === true && that._group && that._group.length > 0;
5731
5732            data = that.reader.parse(data);
5733
5734            that._pristine = isPlainObject(data) ? $.extend(true, {}, data) : data.slice(0);
5735
5736            that._total = that.reader.total(data);
5737
5738            if (that._aggregate && options.serverAggregates) {
5739                that._aggregateResult = that.reader.aggregates(data);
5740            }
5741
5742            if (hasGroups) {
5743                data = that.reader.groups(data);
5744            } else {
5745                data = that.reader.data(data);
5746            }
5747
5748            that._data = that._observe(data);
5749
5750            var start = that._skip || 0,
5751            end = start + that._data.length;
5752
5753            that._ranges.push({ start: start, end: end, data: that._data });
5754            that._ranges.sort( function(x, y) { return x.start - y.start; } );
5755
5756            that._dequeueRequest();
5757            that._process(that._data);
5758        },
5759
5760        _observe: function(data) {
5761            var that = this,
5762                model = that.reader.model,
5763                wrap = false;
5764
5765            if (model && data.length) {
5766                wrap = !(data[0] instanceof model);
5767            }
5768
5769            if (data instanceof ObservableArray) {
5770                if (wrap) {
5771                    data.type = that.reader.model;
5772                    data.wrapAll(data, data);
5773                }
5774            } else {
5775                data = new ObservableArray(data, that.reader.model);
5776                data.parent = function() { return that._parent(); };
5777            }
5778
5779            return data.bind(CHANGE, proxy(that._change, that));
5780        },
5781
5782        _change: function(e) {
5783            var that = this, idx, length, action = e ? e.action : "";
5784
5785            if (action === "remove") {
5786                for (idx = 0, length = e.items.length; idx < length; idx++) {
5787                    if (!e.items[idx].isNew || !e.items[idx].isNew()) {
5788                        that._destroyed.push(e.items[idx]);
5789                    }
5790                }
5791            }
5792
5793            if (that.options.autoSync && (action === "add" || action === "remove" || action === "itemchange")) {
5794                that.sync();
5795            } else {
5796                var total = that._total || that.reader.total(that._pristine);
5797                if (action === "add") {
5798                    total++;
5799                } else if (action === "remove") {
5800                    total--;
5801                } else if (action !== "itemchange" && !that.options.serverPaging) {
5802                    total = that.reader.total(that._pristine);
5803                }
5804
5805                that._total = total;
5806                that._process(that._data, e);
5807            }
5808        },
5809
5810        _process: function (data, e) {
5811            var that = this,
5812                options = {},
5813                result;
5814
5815            if (that.options.serverPaging !== true) {
5816                options.skip = that._skip;
5817                options.take = that._take || that._pageSize;
5818
5819                if(options.skip === undefined && that._page !== undefined && that._pageSize !== undefined) {
5820                    options.skip = (that._page - 1) * that._pageSize;
5821                }
5822            }
5823
5824            if (that.options.serverSorting !== true) {
5825                options.sort = that._sort;
5826            }
5827
5828            if (that.options.serverFiltering !== true) {
5829                options.filter = that._filter;
5830            }
5831
5832            if (that.options.serverGrouping !== true) {
5833                options.group = that._group;
5834            }
5835
5836            if (that.options.serverAggregates !== true) {
5837                options.aggregate = that._aggregate;
5838                that._aggregateResult = calculateAggregates(data, options);
5839            }
5840
5841            result = process(data, options);
5842
5843            that._view = result.data;
5844
5845            if (result.total !== undefined && !that.options.serverFiltering) {
5846                that._total = result.total;
5847            }
5848
5849            that.trigger(CHANGE, e);
5850        },
5851
5852        at: function(index) {
5853            return this._data[index];
5854        },
5855
5856        data: function(value) {
5857            var that = this;
5858            if (value !== undefined) {
5859                that._data = this._observe(value);
5860
5861                that._total = that._data.length;
5862
5863                that._process(that._data);
5864            } else {
5865                return that._data;
5866            }
5867        },
5868
5869        view: function() {
5870            return this._view;
5871        },
5872
5873        query: function(options) {
5874            var that = this,
5875            result,
5876            remote = that.options.serverSorting || that.options.serverPaging || that.options.serverFiltering || that.options.serverGrouping || that.options.serverAggregates;
5877
5878            if (options !== undefined) {
5879                that._pageSize = options.pageSize;
5880                that._page = options.page;
5881                that._sort = options.sort;
5882                that._filter = options.filter;
5883                that._group = options.group;
5884                that._aggregate = options.aggregate;
5885                that._skip = options.skip;
5886                that._take = options.take;
5887
5888                if(that._skip === undefined) {
5889                    that._skip = that.skip();
5890                    options.skip = that.skip();
5891                }
5892
5893                if(that._take === undefined && that._pageSize !== undefined) {
5894                    that._take = that._pageSize;
5895                    options.take = that._take;
5896                }
5897
5898                if (options.sort) {
5899                    that._sort = options.sort = normalizeSort(options.sort);
5900                }
5901
5902                if (options.filter) {
5903                    that._filter = options.filter = normalizeFilter(options.filter);
5904                }
5905
5906                if (options.group) {
5907                    that._group = options.group = normalizeGroup(options.group);
5908                }
5909                if (options.aggregate) {
5910                    that._aggregate = options.aggregate = normalizeAggregate(options.aggregate);
5911                }
5912            }
5913
5914            if (remote || (that._data === undefined || that._data.length === 0)) {
5915                that.read(options);
5916            } else {
5917                that.trigger(REQUESTSTART);
5918                result = process(that._data, options);
5919
5920                if (!that.options.serverFiltering) {
5921                    if (result.total !== undefined) {
5922                        that._total = result.total;
5923                    } else {
5924                        that._total = that._data.length;
5925                    }
5926                }
5927
5928                that._view = result.data;
5929                that._aggregateResult = calculateAggregates(that._data, options);
5930                that.trigger(CHANGE);
5931            }
5932        },
5933
5934        fetch: function(callback) {
5935            var that = this;
5936
5937            if (callback && isFunction(callback)) {
5938                that.one(CHANGE, callback);
5939            }
5940
5941            that._query();
5942        },
5943
5944        _query: function(options) {
5945            var that = this;
5946
5947            that.query(extend({}, {
5948                page: that.page(),
5949                pageSize: that.pageSize(),
5950                sort: that.sort(),
5951                filter: that.filter(),
5952                group: that.group(),
5953                aggregate: that.aggregate()
5954            }, options));
5955        },
5956
5957        next: function() {
5958            var that = this,
5959                page = that.page(),
5960                total = that.total();
5961
5962            if (!page) {
5963                return;
5964            }
5965
5966            if (total) {
5967                that.page(page + 1);
5968            } else {
5969                that._skip = page * that.take();
5970                that._query({ page: page + 1 });
5971            }
5972        },
5973
5974        prev: function() {
5975            var that = this,
5976                page = that.page(),
5977                total = that.total();
5978
5979            if (!page || page === 1) {
5980                return;
5981            }
5982
5983            if (total) {
5984                that.page(page - 1);
5985            } else {
5986                that._skip = that._skip - that.take();
5987                that._query({ page: page - 1});
5988            }
5989        },
5990
5991        page: function(val) {
5992            var that = this,
5993            skip;
5994
5995            if(val !== undefined) {
5996                val = math.max(math.min(math.max(val, 1), that.totalPages()), 1);
5997                that._query({ page: val });
5998                return;
5999            }
6000            skip = that.skip();
6001
6002            return skip !== undefined ? math.round((skip || 0) / (that.take() || 1)) + 1 : undefined;
6003        },
6004
6005        pageSize: function(val) {
6006            var that = this;
6007
6008            if(val !== undefined) {
6009                that._query({ pageSize: val });
6010                return;
6011            }
6012
6013            return that.take();
6014        },
6015
6016        sort: function(val) {
6017            var that = this;
6018
6019            if(val !== undefined) {
6020                that._query({ sort: val });
6021                return;
6022            }
6023
6024            return that._sort;
6025        },
6026
6027        filter: function(val) {
6028            var that = this;
6029
6030            if (val === undefined) {
6031                return that._filter;
6032            }
6033
6034            that._query({ filter: val, page: 1 });
6035        },
6036
6037        group: function(val) {
6038            var that = this;
6039
6040            if(val !== undefined) {
6041                that._query({ group: val });
6042                return;
6043            }
6044
6045            return that._group;
6046        },
6047
6048        total: function() {
6049            return this._total || 0;
6050        },
6051
6052        aggregate: function(val) {
6053            var that = this;
6054
6055            if(val !== undefined) {
6056                that._query({ aggregate: val });
6057                return;
6058            }
6059
6060            return that._aggregate;
6061        },
6062
6063        aggregates: function() {
6064            return this._aggregateResult;
6065        },
6066
6067        totalPages: function() {
6068            var that = this,
6069            pageSize = that.pageSize() || that.total();
6070
6071            return math.ceil((that.total() || 0) / pageSize);
6072        },
6073
6074        inRange: function(skip, take) {
6075            var that = this,
6076            end = math.min(skip + take, that.total());
6077
6078            if (!that.options.serverPaging && that.data.length > 0) {
6079                return true;
6080            }
6081
6082            return that._findRange(skip, end).length > 0;
6083        },
6084
6085        range: function(skip, take) {
6086            skip = math.min(skip || 0, this.total());
6087            var that = this,
6088            pageSkip = math.max(math.floor(skip / take), 0) * take,
6089            size = math.min(pageSkip + take, that.total()),
6090            data;
6091
6092            data = that._findRange(skip, math.min(skip + take, that.total()));
6093
6094            if (data.length) {
6095                that._skip = skip > that.skip() ? math.min(size, (that.totalPages() - 1) * that.take()) : pageSkip;
6096
6097                that._take = take;
6098
6099                var paging = that.options.serverPaging;
6100                var sorting = that.options.serverSorting;
6101                try {
6102                    that.options.serverPaging = true;
6103                    that.options.serverSorting = true;
6104                    if (paging) {
6105                        that._data = data = that._observe(data);
6106                    }
6107                    that._process(data);
6108                } finally {
6109                    that.options.serverPaging = paging;
6110                    that.options.serverSorting = sorting;
6111                }
6112
6113                return;
6114            }
6115
6116            if (take !== undefined) {
6117                if (!that._rangeExists(pageSkip, size)) {
6118                    that.prefetch(pageSkip, take, function() {
6119                        if (skip > pageSkip && size < that.total() && !that._rangeExists(size, math.min(size + take, that.total()))) {
6120                            that.prefetch(size, take, function() {
6121                                that.range(skip, take);
6122                            });
6123                        } else {
6124                            that.range(skip, take);
6125                        }
6126                    });
6127                } else if (pageSkip < skip) {
6128                    that.prefetch(size, take, function() {
6129                        that.range(skip, take);
6130                    });
6131                }
6132            }
6133        },
6134
6135        _findRange: function(start, end) {
6136            var that = this,
6137                ranges = that._ranges,
6138                range,
6139                data = [],
6140                skipIdx,
6141                takeIdx,
6142                startIndex,
6143                endIndex,
6144                rangeData,
6145                rangeEnd,
6146                processed,
6147                options = that.options,
6148                remote = options.serverSorting || options.serverPaging || options.serverFiltering || options.serverGrouping || options.serverAggregates,
6149                length;
6150
6151            for (skipIdx = 0, length = ranges.length; skipIdx < length; skipIdx++) {
6152                range = ranges[skipIdx];
6153                if (start >= range.start && start <= range.end) {
6154                    var count = 0;
6155
6156                    for (takeIdx = skipIdx; takeIdx < length; takeIdx++) {
6157                        range = ranges[takeIdx];
6158
6159                        if (range.data.length && start + count >= range.start /*&& count + count <= range.end*/) {
6160                            rangeData = range.data;
6161                            rangeEnd = range.end;
6162
6163                            if (!remote) {
6164                                processed = process(range.data, { sort: that.sort(), filter: that.filter() });
6165                                rangeData = processed.data;
6166
6167                                if (processed.total !== undefined) {
6168                                    rangeEnd = processed.total;
6169                                }
6170                            }
6171
6172                            startIndex = 0;
6173                            if (start + count > range.start) {
6174                                startIndex = (start + count) - range.start;
6175                            }
6176                            endIndex = rangeData.length;
6177                            if (rangeEnd > end) {
6178                                endIndex = endIndex - (rangeEnd - end);
6179                            }
6180                            count += endIndex - startIndex;
6181                            data = data.concat(rangeData.slice(startIndex, endIndex));
6182
6183                            if (end <= range.end && count == end - start) {
6184                                return data;
6185                            }
6186                        }
6187                    }
6188                    break;
6189                }
6190            }
6191            return [];
6192        },
6193
6194        skip: function() {
6195            var that = this;
6196
6197            if (that._skip === undefined) {
6198                return (that._page !== undefined ? (that._page  - 1) * (that.take() || 1) : undefined);
6199            }
6200            return that._skip;
6201        },
6202
6203        take: function() {
6204            var that = this;
6205            return that._take || that._pageSize;
6206        },
6207
6208        prefetch: function(skip, take, callback) {
6209            var that = this,
6210            size = math.min(skip + take, that.total()),
6211            range = { start: skip, end: size, data: [] },
6212            options = {
6213                take: take,
6214                skip: skip,
6215                page: skip / take + 1,
6216                pageSize: take,
6217                sort: that._sort,
6218                filter: that._filter,
6219                group: that._group,
6220                aggregate: that._aggregate
6221            };
6222
6223            if (!that._rangeExists(skip, size)) {
6224                clearTimeout(that._timeout);
6225
6226                that._timeout = setTimeout(function() {
6227                    that._queueRequest(options, function() {
6228                        that.transport.read({
6229                            data: options,
6230                            success: function (data) {
6231                                that._dequeueRequest();
6232                                var found = false;
6233                                for (var i = 0, len = that._ranges.length; i < len; i++) {
6234                                    if (that._ranges[i].start === skip) {
6235                                        found = true;
6236                                        range = that._ranges[i];
6237                                        break;
6238                                    }
6239                                }
6240                                if (!found) {
6241                                    that._ranges.push(range);
6242                                }
6243
6244                                data = that.reader.parse(data);
6245                                range.data = that._observe(that.reader.data(data));
6246                                range.end = range.start + range.data.length;
6247                                that._ranges.sort( function(x, y) { return x.start - y.start; } );
6248                                that._total = that.reader.total(data);
6249                                if (callback) {
6250                                    callback();
6251                                }
6252                            }
6253                        });
6254                    });
6255                }, 100);
6256            } else if (callback) {
6257                callback();
6258            }
6259        },
6260
6261        _rangeExists: function(start, end) {
6262            var that = this,
6263            ranges = that._ranges,
6264            idx,
6265            length;
6266
6267            for (idx = 0, length = ranges.length; idx < length; idx++) {
6268                if (ranges[idx].start <= start && ranges[idx].end >= end) {
6269                    return true;
6270                }
6271            }
6272            return false;
6273        }
6274    });
6275
6276    DataSource.create = function(options) {
6277        options = options && options.push ? { data: options } : options;
6278
6279        var dataSource = options || {},
6280        data = dataSource.data,
6281        fields = dataSource.fields,
6282        table = dataSource.table,
6283        select = dataSource.select,
6284        idx,
6285        length,
6286        model = {},
6287        field;
6288
6289        if (!data && fields && !dataSource.transport) {
6290            if (table) {
6291                data = inferTable(table, fields);
6292            } else if (select) {
6293                data = inferSelect(select, fields);
6294            }
6295        }
6296
6297        if (kendo.data.Model && fields && (!dataSource.schema || !dataSource.schema.model)) {
6298            for (idx = 0, length = fields.length; idx < length; idx++) {
6299                field = fields[idx];
6300                if (field.type) {
6301                    model[field.field] = field;
6302                }
6303            }
6304
6305            if (!isEmptyObject(model)) {
6306                dataSource.schema = extend(true, dataSource.schema, { model:  { fields: model } });
6307            }
6308        }
6309
6310        dataSource.data = data;
6311
6312        return dataSource instanceof DataSource ? dataSource : new DataSource(dataSource);
6313    };
6314
6315    function inferSelect(select, fields) {
6316        var options = $(select)[0].children,
6317            idx,
6318            length,
6319            data = [],
6320            record,
6321            firstField = fields[0],
6322            secondField = fields[1],
6323            value,
6324            option;
6325
6326        for (idx = 0, length = options.length; idx < length; idx++) {
6327            record = {};
6328            option = options[idx];
6329
6330            record[firstField.field] = option.text;
6331
6332            value = option.attributes.value;
6333
6334            if (value && value.specified) {
6335                value = option.value;
6336            } else {
6337                value = option.text;
6338            }
6339
6340            record[secondField.field] = value;
6341
6342            data.push(record);
6343        }
6344
6345        return data;
6346    }
6347
6348    function inferTable(table, fields) {
6349        var tbody = $(table)[0].tBodies[0],
6350        rows = tbody ? tbody.rows : [],
6351        idx,
6352        length,
6353        fieldIndex,
6354        fieldCount = fields.length,
6355        data = [],
6356        cells,
6357        record,
6358        cell,
6359        empty;
6360
6361        for (idx = 0, length = rows.length; idx < length; idx++) {
6362            record = {};
6363            empty = true;
6364            cells = rows[idx].cells;
6365
6366            for (fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
6367                cell = cells[fieldIndex];
6368                if(cell.nodeName.toLowerCase() !== "th") {
6369                    empty = false;
6370                    record[fields[fieldIndex].field] = cell.innerHTML;
6371                }
6372            }
6373            if(!empty) {
6374                data.push(record);
6375            }
6376        }
6377
6378        return data;
6379    }
6380
6381    extend(true, kendo.data, /** @lends kendo.data */ {
6382        readers: {
6383            json: DataReader
6384        },
6385        Query: Query,
6386        DataSource: DataSource,
6387        ObservableObject: ObservableObject,
6388        ObservableArray: ObservableArray,
6389        LocalTransport: LocalTransport,
6390        RemoteTransport: RemoteTransport,
6391        Cache: Cache,
6392        DataReader: DataReader,
6393        Model: Model
6394    });
6395})(jQuery);
6396(function ($, unefined) {
6397    var kendo = window.kendo,
6398        Observable = kendo.Observable,
6399        ObservableObject = kendo.data.ObservableObject,
6400        ObservableArray = kendo.data.ObservableArray,
6401        toString = {}.toString,
6402        binders = {},
6403        Class = kendo.Class,
6404        innerText,
6405        proxy = $.proxy,
6406        VALUE = "value",
6407        CHECKED = "checked",
6408        CHANGE = "change";
6409
6410    (function() {
6411        var a = document.createElement("a");
6412        if (a.innerText !== undefined) {
6413            innerText = "innerText";
6414        } else if (a.textContent !== undefined) {
6415            innerText = "textContent";
6416        }
6417    })();
6418
6419    var Binding = Observable.extend( {
6420        init: function(source, path) {
6421            var that = this;
6422
6423            Observable.fn.init.call(that);
6424
6425            that.source = source;
6426            that.path = path;
6427            that.dependencies = {};
6428            that.dependencies[path] = true;
6429            that.observable = that.source instanceof Observable;
6430
6431            that._access = function(e) {
6432                that.dependencies[e.field] = true;
6433            };
6434
6435            if (that.observable) {
6436                that._change = function(e) {
6437                    that.change(e);
6438                };
6439
6440                that.source.bind(CHANGE, that._change);
6441            }
6442        },
6443
6444        change: function(e) {
6445            var dependency,
6446                idx,
6447                ch,
6448                that = this;
6449
6450            if (that.path === "this") {
6451                that.trigger(CHANGE, e);
6452            } else {
6453                for (dependency in that.dependencies) {
6454                    idx = dependency.indexOf(e.field);
6455
6456                    if (idx === 0) {
6457                       ch = dependency.charAt(e.field.length);
6458
6459                       if (!ch || ch === "." || ch === "[") {
6460                            that.trigger(CHANGE, e);
6461                            break;
6462                       }
6463                    }
6464                }
6465            }
6466        },
6467
6468        start: function() {
6469            if (this.observable) {
6470                this.source.bind("get", this._access);
6471            }
6472        },
6473
6474        stop: function() {
6475            if (this.observable) {
6476                this.source.unbind("get", this._access);
6477            }
6478        },
6479
6480        get: function() {
6481            var that = this,
6482                source = that.source,
6483                index,
6484                path = that.path,
6485                result = source;
6486
6487            that.start();
6488
6489            if (that.observable) {
6490                result = source.get(path);
6491
6492                // Traverse the observable hierarchy if the binding is not resolved at the current level.
6493                while (result === undefined && source) {
6494                    source = source.parent();
6495
6496                    if (source instanceof ObservableObject) {
6497                        result = source.get(path);
6498                    }
6499                }
6500
6501                // If the result is a function - invoke it
6502                if (typeof result === "function") {
6503                    index = path.lastIndexOf(".");
6504
6505                    // If the function is a member of a nested observable object make that nested observable the context (this) of the function
6506                    if (index > 0) {
6507                        source = source.get(path.substring(0, index));
6508                    }
6509
6510                    // Set the context (this) of the function
6511                    result = proxy(result, source);
6512
6513                    // Invoke the function
6514                    result = result(that.source);
6515                }
6516            }
6517
6518            that.stop();
6519
6520            return result;
6521        },
6522
6523        set: function(value) {
6524            this.source.set(this.path, value);
6525        },
6526
6527        destroy: function() {
6528            if (this.observable) {
6529                this.source.unbind(CHANGE, this._change);
6530            }
6531        }
6532    });
6533
6534    var EventBinding = Binding.extend( {
6535        get: function() {
6536            var source = this.source,
6537                path = this.path,
6538                handler;
6539
6540            handler = source.get(path);
6541
6542            while (!handler && source) {
6543                source = source.parent();
6544                if (source instanceof ObservableObject) {
6545                    handler = source.get(path);
6546                }
6547            }
6548
6549            return proxy(handler, source);
6550        }
6551    });
6552
6553    var TemplateBinding = Binding.extend( {
6554        init: function(source, path, template) {
6555            var that = this;
6556
6557            Binding.fn.init.call(that, source, path);
6558
6559            that.template = template;
6560        },
6561
6562        render: function(value) {
6563            var html;
6564
6565            this.start();
6566
6567            html = kendo.render(this.template, value);
6568
6569            this.stop();
6570
6571            return html;
6572        }
6573    });
6574
6575    var Binder = Class.extend({
6576        init: function(element, bindings, options) {
6577            this.element = element;
6578            this.bindings = bindings;
6579            this.options = options;
6580        },
6581
6582        bind: function(binding, attribute) {
6583            var that = this;
6584
6585            binding = attribute ? binding[attribute] : binding;
6586
6587            binding.bind(CHANGE, function(e) {
6588                that.refresh(attribute || e);
6589            });
6590
6591            that.refresh(attribute);
6592        },
6593
6594        destroy: function() {
6595        }
6596    });
6597
6598    binders.attr = Binder.extend({
6599        refresh: function(key) {
6600            this.element.setAttribute(key, this.bindings.attr[key].get());
6601        }
6602    });
6603
6604    binders.style = Binder.extend({
6605        refresh: function(key) {
6606            this.element.style[key] = this.bindings.style[key].get();
6607        }
6608    });
6609
6610    binders.enabled = Binder.extend({
6611        refresh: function() {
6612            if (this.bindings.enabled.get()) {
6613                this.element.removeAttribute("disabled");
6614            } else {
6615                this.element.setAttribute("disabled", "disabled");
6616            }
6617        }
6618    });
6619
6620    binders.disabled = Binder.extend({
6621        refresh: function() {
6622            if (this.bindings.disabled.get()) {
6623                this.element.setAttribute("disabled", "disabled");
6624            } else {
6625                this.element.removeAttribute("disabled");
6626            }
6627        }
6628    });
6629
6630    binders.events = Binder.extend({
6631        init: function(element, bindings, options) {
6632            Binder.fn.init.call(this, element, bindings, options);
6633            this.handlers = {};
6634        },
6635
6636        refresh: function(key) {
6637            var binding = this.bindings.events[key],
6638                handler = this.handlers[key] = binding.get();
6639
6640            $(this.element).bind(key, binding.source, handler);
6641        },
6642
6643        destroy: function() {
6644            var element = $(this.element),
6645                handler;
6646
6647            for (handler in this.handlers) {
6648                element.unbind(handler, this.handlers[handler]);
6649            }
6650        }
6651    });
6652
6653    binders.text = Binder.extend({
6654        refresh: function() {
6655            var text = this.bindings.text.get();
6656
6657            if (text == null) {
6658                text = "";
6659            }
6660
6661            this.element[innerText] = text;
6662        }
6663    });
6664
6665    binders.visible = Binder.extend({
6666        refresh: function() {
6667            if (this.bindings.visible.get()) {
6668                this.element.style.display = "";
6669            } else {
6670                this.element.style.display = "none";
6671            }
6672        }
6673    });
6674
6675    binders.invisible = Binder.extend({
6676        refresh: function() {
6677            if (!this.bindings.invisible.get()) {
6678                this.element.style.display = "";
6679            } else {
6680                this.element.style.display = "none";
6681            }
6682        }
6683    });
6684
6685    binders.html = Binder.extend({
6686        refresh: function() {
6687            this.element.innerHTML = this.bindings.html.get();
6688        }
6689    });
6690
6691    binders.value = Binder.extend({
6692        init: function(element, bindings, options) {
6693            Binder.fn.init.call(this, element, bindings, options);
6694
6695            this._change = proxy(this.change, this);
6696            this.eventName = options.valueUpdate || CHANGE;
6697
6698            $(this.element).bind(this.eventName, this._change);
6699
6700            this._initChange = false;
6701        },
6702
6703        change: function() {
6704            this._initChange = this.eventName != CHANGE;
6705            this.bindings[VALUE].set(this.element.value);
6706        },
6707
6708        refresh: function() {
6709            if (!this._initChange) {
6710                var value = this.bindings[VALUE].get();
6711
6712                if (value == null) {
6713                    value = "";
6714                }
6715
6716                this.element.value = value;
6717            }
6718
6719            this._initChange = false;
6720        },
6721
6722        destroy: function() {
6723            $(this.element).unbind(this.eventName, this._change);
6724        }
6725    });
6726
6727    binders.source = Binder.extend({
6728        init: function(element, bindings, options) {
6729            Binder.fn.init.call(this, element, bindings, options);
6730        },
6731
6732        refresh: function(e) {
6733            var that = this,
6734                source = that.bindings.source.get();
6735
6736            if (source instanceof ObservableArray) {
6737                e = e || {};
6738
6739                if (e.action == "add") {
6740                    that.add(e.index, e.items);
6741                } else if (e.action == "remove") {
6742                    that.remove(e.index, e.items);
6743                } else if (e.action != "itemchange") {
6744                    that.render();
6745                }
6746            } else {
6747                that.render();
6748            }
6749        },
6750
6751        container: function() {
6752            var element = this.element;
6753
6754            if (element.nodeName.toLowerCase() == "table") {
6755                if (!element.tBodies[0]) {
6756                    element.appendChild(document.createElement("tbody"));
6757                }
6758                element = element.tBodies[0];
6759            }
6760
6761            return element;
6762        },
6763
6764        template: function() {
6765            var options = this.options,
6766                template = options.template,
6767                nodeName = this.container().nodeName.toLowerCase();
6768
6769            if (!template) {
6770                if (nodeName == "select") {
6771                    if (options.valueField || options.textField) {
6772                        template = kendo.format('<option value="#:{0}#">#:{1}#</option>',
6773                            options.valueField || options.textField, options.textField || options.valueField);
6774                    } else {
6775                        template = "<option>#:data#</option>";
6776                    }
6777                } else if (nodeName == "tbody") {
6778                    template = "<tr><td>#:data#</td></tr>";
6779                } else if (nodeName == "ul" || nodeName == "ol") {
6780                    template = "<li>#:data#</li>";
6781                } else {
6782                    template = "#:data#";
6783                }
6784
6785                template = kendo.template(template);
6786            }
6787
6788            return template;
6789        },
6790
6791        destroy: function() {
6792            var source = this.bindings.source.get();
6793
6794            source.unbind(CHANGE, this._change);
6795        },
6796
6797        add: function(index, items) {
6798            var element = this.container(),
6799                idx,
6800                length,
6801                child,
6802                clone = element.cloneNode(false),
6803                reference = element.children[index];
6804
6805            $(clone).html(kendo.render(this.template(), items));
6806
6807            if (clone.children.length) {
6808                for (idx = 0, length = items.length; idx < length; idx++) {
6809                    child = clone.children[0];
6810                    element.insertBefore(child, reference || null);
6811                    bindElement(child, items[idx]);
6812                }
6813            }
6814        },
6815
6816        remove: function(index, items) {
6817            var idx,
6818            element = this.container();
6819
6820            for (idx = 0; idx < items.length; idx++) {
6821                element.removeChild(element.children[index]);
6822            }
6823        },
6824
6825        render: function() {
6826            var source = this.bindings.source.get(),
6827                 idx,
6828                 length,
6829                 element = this.container(),
6830                 template = this.template(),
6831                 parent;
6832
6833            if (!(source instanceof ObservableArray) && toString.call(source) !== "[object Array]") {
6834                if (source.parent) {
6835                    parent = source.parent;
6836                }
6837
6838                source = new ObservableArray([source]);
6839
6840                if (source.parent) {
6841                    source.parent = parent;
6842                }
6843            }
6844
6845            if (this.bindings.template) {
6846                $(element).html(this.bindings.template.render(source));
6847
6848                if (element.children.length) {
6849                    for (idx = 0, length = source.length; idx < length; idx++) {
6850                        bindElement(element.children[idx], source[idx]);
6851                    }
6852                }
6853            }
6854            else {
6855                $(element).html(kendo.render(template, source));
6856            }
6857        }
6858    });
6859
6860    binders.input = {
6861        checked: Binder.extend({
6862            init: function(element, bindings, options) {
6863                Binder.fn.init.call(this, element, bindings, options);
6864                this._change = proxy(this.change, this);
6865
6866                $(this.element).change(this._change);
6867            },
6868            change: function() {
6869                var element = this.element;
6870                var value = this.value();
6871
6872                if (element.type == "radio") {
6873                    this.bindings[CHECKED].set(value);
6874                } else if (element.type == "checkbox") {
6875                    var source = this.bindings[CHECKED].get();
6876                    var index;
6877
6878                    if (source instanceof ObservableArray) {
6879                        if (value !== false && value !== true) {
6880                            index = source.indexOf(value);
6881                            if (index > -1) {
6882                                source.splice(index, 1);
6883                            } else {
6884                                source.push(value);
6885                            }
6886                        }
6887                    } else {
6888                        this.bindings[CHECKED].set(value);
6889                    }
6890                }
6891            },
6892
6893            refresh: function() {
6894                var value = this.bindings[CHECKED].get();
6895                var element = this.element;
6896
6897                if (element.type == "checkbox") {
6898                    if (value instanceof ObservableArray) {
6899                        if (value.indexOf(this.value(element)) >= 0) {
6900                            value = true;
6901                        }
6902                    }
6903
6904                    element.checked = value === true;
6905                } else if (element.type == "radio" && value != null) {
6906                    if (element.value === value.toString()) {
6907                        element.checked = true;
6908                    }
6909                }
6910            },
6911
6912            value: function() {
6913                var element = this.element,
6914                    value = element.value;
6915
6916                if (element.type == "checkbox") {
6917                    if (value == "on" || value == "off") {
6918                        value = element.checked;
6919                    }
6920                }
6921
6922                return value;
6923            },
6924            destroy: function() {
6925                $(this.element).unbind(CHANGE, this._change);
6926            }
6927        })
6928    };
6929
6930    binders.select = {
6931        value: Binder.extend({
6932            init: function(target, bindings, options) {
6933                Binder.fn.init.call(this, target, bindings, options);
6934
6935                this._change = proxy(this.change, this);
6936                $(this.element).change(this._change);
6937            },
6938
6939            change: function() {
6940                var values = [],
6941                    element = this.element,
6942                    source,
6943                    field = this.options.valueField || this.options.textField,
6944                    option,
6945                    valueIndex,
6946                    value,
6947                    idx,
6948                    length;
6949
6950                for (idx = 0, length = element.options.length; idx < length; idx++) {
6951                    option = element.options[idx];
6952
6953                    if (option.selected) {
6954                        value = option.attributes.value;
6955
6956                        if (value && value.specified) {
6957                            value = option.value;
6958                        } else {
6959                            value = option.text;
6960                        }
6961
6962                        values.push(value);
6963                    }
6964                }
6965
6966                if (field) {
6967                    source = this.bindings.source.get();
6968                    for (valueIndex = 0; valueIndex < values.length; valueIndex++) {
6969                        for (idx = 0, length = source.length; idx < length; idx++) {
6970                            if (source[idx].get(field) == values[valueIndex]) {
6971                                values[valueIndex] = source[idx];
6972                                break;
6973                            }
6974                        }
6975                    }
6976                }
6977
6978                value = this.bindings[VALUE].get();
6979                if (value instanceof ObservableArray) {
6980                    value.splice.apply(value, [0, value.length].concat(values));
6981                } else if (value instanceof ObservableObject || !field) {
6982                    this.bindings[VALUE].set(values[0]);
6983                } else {
6984                    this.bindings[VALUE].set(values[0].get(field));
6985                }
6986            },
6987            refresh: function() {
6988                var optionIndex,
6989                    element = this.element,
6990                    options = element.options,
6991                    value = this.bindings[VALUE].get(),
6992                    values = value,
6993                    field = this.options.valueField || this.options.textField,
6994                    optionValue;
6995
6996                if (!(values instanceof ObservableArray)) {
6997                    values = new ObservableArray([value]);
6998                }
6999
7000                for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
7001                    value = values[valueIndex];
7002
7003                    if (field && value instanceof ObservableObject) {
7004                        value = value.get(field);
7005                    }
7006
7007                    for (optionIndex = 0; optionIndex < options.length; optionIndex++) {
7008                        optionValue = options[optionIndex].value;
7009                        if (optionValue === "" && value !== "") {
7010                            optionValue = options[optionIndex].text;
7011                        }
7012
7013                        if (optionValue == value) {
7014                            options[optionIndex].selected = true;
7015                        }
7016                    }
7017                }
7018            },
7019            destroy: function() {
7020                $(this.element).unbind(CHANGE, this._change);
7021            }
7022        })
7023    };
7024
7025    binders.widget = {
7026        events : Binder.extend({
7027            init: function(widget, bindings, options) {
7028                Binder.fn.init.call(this, widget.element[0], bindings, options);
7029                this.widget = widget;
7030                this.handlers = {};
7031            },
7032
7033            refresh: function(key) {
7034                var binding = this.bindings.events[key],
7035                    handler = binding.get();
7036
7037                this.handlers[key] = function(e) {
7038                    e.data = binding.source;
7039
7040                    handler(e);
7041                };
7042
7043                this.widget.bind(key, this.handlers[key]);
7044            },
7045
7046            destroy: function() {
7047                var handler;
7048
7049                for (handler in this.handlers) {
7050                    this.widget.unbind(handler, this.handlers[handler]);
7051                }
7052            }
7053        }),
7054
7055        checked: Binder.extend({
7056            init: function(widget, bindings, options) {
7057                Binder.fn.init.call(this, widget.element[0], bindings, options);
7058
7059                this.widget = widget;
7060                this._change = proxy(this.change, this);
7061                this.widget.bind(CHANGE, this._change);
7062            },
7063            change: function() {
7064                this.bindings[CHECKED].set(this.value());
7065            },
7066
7067            refresh: function() {
7068                this.widget.check(this.bindings[CHECKED].get() === true);
7069            },
7070
7071            value: function() {
7072                var element = this.element,
7073                    value = element.value;
7074
7075                if (value == "on" || value == "off") {
7076                    value = element.checked;
7077                }
7078
7079                return value;
7080            },
7081
7082            destroy: function() {
7083                this.widget.unbind(CHANGE, this._change);
7084            }
7085        }),
7086
7087        visible: Binder.extend({
7088            init: function(widget, bindings, options) {
7089                Binder.fn.init.call(this, widget.element[0], bindings, options);
7090
7091                this.widget = widget;
7092            },
7093
7094            refresh: function() {
7095                var visible = this.bindings.visible.get();
7096                this.widget.wrapper[0].style.display = visible ? "" : "none";
7097            }
7098        }),
7099
7100        invisible: Binder.extend({
7101            init: function(widget, bindings, options) {
7102                Binder.fn.init.call(this, widget.element[0], bindings, options);
7103
7104                this.widget = widget;
7105            },
7106
7107            refresh: function() {
7108                var invisible = this.bindings.invisible.get();
7109                this.widget.wrapper[0].style.display = invisible ? "none" : "";
7110            }
7111        }),
7112
7113        enabled: Binder.extend({
7114            init: function(widget, bindings, options) {
7115                Binder.fn.init.call(this, widget.element[0], bindings, options);
7116
7117                this.widget = widget;
7118            },
7119
7120            refresh: function() {
7121                if (this.widget.enable) {
7122                    this.widget.enable(this.bindings.enabled.get());
7123                }
7124            }
7125        }),
7126
7127        disabled: Binder.extend({
7128            init: function(widget, bindings, options) {
7129                Binder.fn.init.call(this, widget.element[0], bindings, options);
7130
7131                this.widget = widget;
7132            },
7133
7134            refresh: function() {
7135                if (this.widget.enable) {
7136                    this.widget.enable(!this.bindings.disabled.get());
7137                }
7138            }
7139        }),
7140
7141        source: Binder.extend({
7142            init: function(widget, bindings, options) {
7143                var that = this;
7144
7145                Binder.fn.init.call(that, widget.element[0], bindings, options);
7146
7147                that.widget = widget;
7148                that._dataBinding = proxy(that.dataBinding, that);
7149                that._dataBound = proxy(that.dataBound, that);
7150                that._itemChange = proxy(that.itemChange, that);
7151            },
7152
7153            itemChange: function(e) {
7154                bindElement(e.item[0], e.data, e.ns || kendo.ui);
7155            },
7156
7157            dataBinding: function() {
7158                var idx,
7159                    length,
7160                    widget = this.widget,
7161                    items = widget.items();
7162
7163                for (idx = 0, length = items.length; idx < length; idx++) {
7164                    unbindElementTree(items[idx]);
7165                }
7166            },
7167
7168            dataBound: function(e) {
7169                var idx,
7170                    length,
7171                    widget = this.widget,
7172                    items = widget.items(),
7173                    dataSource = widget.dataSource,
7174                    view = dataSource.view(),
7175                    ns = e.ns || kendo.ui,
7176                    groups = dataSource.group() || [];
7177
7178                if (items.length) {
7179                    if (groups.length) {
7180                        view = flattenGroups(view);
7181                    }
7182
7183                    for (idx = 0, length = view.length; idx < length; idx++) {
7184                        bindElement(items[idx], view[idx], ns);
7185                    }
7186                }
7187            },
7188
7189            refresh: function(e) {
7190                var that = this,
7191                    source,
7192                    widget = that.widget;
7193
7194                e = e || {};
7195
7196                if (!e.action) {
7197                    that.destroy();
7198
7199                    widget.bind("dataBinding", that._dataBinding);
7200                    widget.bind("dataBound", that._dataBound);
7201                    widget.bind("itemChange", that._itemChange);
7202
7203                    if (widget.dataSource instanceof kendo.data.DataSource) {
7204                        source = that.bindings.source.get();
7205                        if (source instanceof kendo.data.DataSource) {
7206                            widget.setDataSource(source);
7207                        } else {
7208                            widget.dataSource.data(source);
7209                        }
7210                    }
7211                }
7212            },
7213
7214            destroy: function() {
7215                var widget = this.widget;
7216
7217                widget.unbind("dataBinding", this._dataBinding);
7218                widget.unbind("dataBound", this._dataBound);
7219                widget.unbind("itemChange", this._itemChange);
7220            }
7221        }),
7222
7223        value: Binder.extend({
7224            init: function(widget, bindings, options) {
7225                Binder.fn.init.call(this, widget.element[0], bindings, options);
7226
7227                this.widget = widget;
7228                this._change = $.proxy(this.change, this);
7229                this.widget.first(CHANGE, this._change);
7230
7231                var value = this.bindings.value.get();
7232                this._valueIsObservableObject = value == null || value instanceof ObservableObject;
7233            },
7234
7235            change: function() {
7236                var value = this.widget.value();
7237                var idx, length;
7238
7239                var field = this.options.dataValueField || this.options.dataTextField;
7240
7241                if (field) {
7242                    var source,
7243                        isObservableObject = this._valueIsObservableObject;
7244
7245                    if (this.bindings.source) {
7246                        source = this.bindings.source.get();
7247                    }
7248
7249                    if (value === "" && isObservableObject) {
7250                        value = null;
7251                    } else {
7252                        if (!source || source instanceof kendo.data.DataSource) {
7253                            source = this.widget.dataSource.view();
7254                        }
7255
7256                        for (idx = 0, length = source.length; idx < length; idx++) {
7257                            if (source[idx].get(field) == value) {
7258                                if (isObservableObject) {
7259                                    value = source[idx];
7260                                } else {
7261                                    value = source[idx].get(field);
7262                                }
7263                                break;
7264                            }
7265                        }
7266                    }
7267                }
7268
7269                this.bindings.value.set(value);
7270            },
7271
7272            refresh: function() {
7273                var field = this.options.dataValueField || this.options.dataTextField;
7274                var value = this.bindings.value.get();
7275
7276                if (field && value instanceof ObservableObject) {
7277                    value = value.get(field);
7278                }
7279
7280                this.widget.value(value);
7281            },
7282
7283            destroy: function() {
7284                this.widget.unbind(CHANGE, this._change);
7285            }
7286        })
7287    };
7288
7289    var BindingTarget = Class.extend( {
7290        init: function(target, options) {
7291            this.target = target;
7292            this.options = options;
7293            this.toDestroy = [];
7294        },
7295
7296        bind: function(bindings) {
7297            var nodeName = this.target.nodeName.toLowerCase(),
7298                key,
7299                specificBinders = binders[nodeName] || {};
7300
7301            for (key in bindings) {
7302                this.applyBinding(key, bindings, specificBinders);
7303            }
7304        },
7305
7306        applyBinding: function(name, bindings, specificBinders) {
7307            var binder = specificBinders[name] || binders[name],
7308                toDestroy = this.toDestroy,
7309                attribute,
7310                binding = bindings[name];
7311
7312            if (binder) {
7313                binder = new binder(this.target, bindings, this.options);
7314
7315                toDestroy.push(binder);
7316
7317                if (binding instanceof Binding) {
7318                    binder.bind(binding);
7319                    toDestroy.push(binding);
7320                } else {
7321                    for (attribute in binding) {
7322                        binder.bind(binding, attribute);
7323                        toDestroy.push(binding[attribute]);
7324                    }
7325                }
7326            } else if (name !== "template") {
7327                throw new Error("The " + name + " binding is not supported by the " + this.target.nodeName.toLowerCase() + " element");
7328            }
7329        },
7330
7331        destroy: function() {
7332            var idx,
7333                length,
7334                toDestroy = this.toDestroy;
7335
7336            for (idx = 0, length = toDestroy.length; idx < length; idx++) {
7337                toDestroy[idx].destroy();
7338            }
7339        }
7340    });
7341
7342    var WidgetBindingTarget = BindingTarget.extend( {
7343        bind: function(bindings) {
7344            var that = this,
7345                binding,
7346                hasValue = false,
7347                hasSource = false;
7348
7349            for (binding in bindings) {
7350                if (binding == VALUE) {
7351                    hasValue = true;
7352                } else if (binding == "source") {
7353                    hasSource = true;
7354                } else {
7355                    that.applyBinding(binding, bindings);
7356                }
7357            }
7358
7359            if (hasSource) {
7360                that.applyBinding("source", bindings);
7361            }
7362
7363            if (hasValue) {
7364                that.applyBinding(VALUE, bindings);
7365            }
7366        },
7367
7368        applyBinding: function(name, bindings) {
7369            var binder = binders.widget[name],
7370                toDestroy = this.toDestroy,
7371                attribute,
7372                binding = bindings[name];
7373
7374            if (binder) {
7375                binder = new binder(this.target, bindings, this.target.options);
7376
7377                toDestroy.push(binder);
7378
7379
7380                if (binding instanceof Binding) {
7381                    binder.bind(binding);
7382                    toDestroy.push(binding);
7383                } else {
7384                    for (attribute in binding) {
7385                        binder.bind(binding, attribute);
7386                        toDestroy.push(binding[attribute]);
7387                    }
7388                }
7389            } else {
7390                throw new Error("The " + name + " binding is not supported by the " + this.target.options.name + " widget");
7391            }
7392        }
7393    });
7394
7395    function flattenGroups(data) {
7396        var idx, length, result = [];
7397
7398        for (idx = 0, length = data.length; idx < length; idx++) {
7399            if (data[idx].hasSubgroups) {
7400                result = result.concat(flattenGroups(data[idx].items));
7401            } else {
7402                result = result.concat(data[idx].items);
7403            }
7404        }
7405        return result;
7406    }
7407
7408    function bindingTargetForRole(role, element, namespace) {
7409        var type = namespace.roles[role];
7410
7411        if (type) {
7412            return new WidgetBindingTarget(kendo.initWidget(element, type.options, namespace));
7413        }
7414    }
7415
7416    var keyValueRegExp = /[A-Za-z0-9_\-]+:(\{([^}]*)\}|[^,}]+)/g,
7417        whiteSpaceRegExp = /\s/g;
7418
7419    function parseBindings(bind) {
7420        var result = {},
7421            idx,
7422            length,
7423            token,
7424            colonIndex,
7425            key,
7426            value,
7427            tokens;
7428
7429        tokens = bind.match(keyValueRegExp);
7430
7431        for (idx = 0, length = tokens.length; idx < length; idx++) {
7432            token = tokens[idx];
7433            colonIndex = token.indexOf(":");
7434
7435            key = token.substring(0, colonIndex);
7436            value = token.substring(colonIndex + 1);
7437
7438            if (value.charAt(0) == "{") {
7439                value = parseBindings(value);
7440            }
7441
7442            result[key] = value;
7443        }
7444
7445        return result;
7446    }
7447
7448    function createBindings(bindings, source, type) {
7449        var binding,
7450            result = {};
7451
7452        for (binding in bindings) {
7453            result[binding] = new type(source, bindings[binding]);
7454        }
7455
7456        return result;
7457    }
7458
7459    function bindElement(element, source, namespace) {
7460        var role = element.getAttribute("data-" + kendo.ns + "role"),
7461            idx,
7462            bind = element.getAttribute("data-" + kendo.ns + "bind"),
7463            children = element.children,
7464            deep = true,
7465            bindings,
7466            options = {},
7467            target;
7468
7469        if (!namespace) {
7470            namespace = kendo.ui;
7471        }
7472
7473        if (role || bind) {
7474            unbindElement(element);
7475        }
7476
7477        if (role) {
7478            target = bindingTargetForRole(role, element, namespace);
7479        }
7480
7481        if (bind) {
7482            bind = parseBindings(bind.replace(whiteSpaceRegExp, ""));
7483
7484            if (!target) {
7485                options = kendo.parseOptions(element, { textField: "", valueField: "", template: "", valueUpdate: CHANGE});
7486                target = new BindingTarget(element, options);
7487            }
7488
7489            target.source = source;
7490
7491            bindings = createBindings(bind, source, Binding);
7492
7493            if (options.template) {
7494                bindings.template = new TemplateBinding(source, "", options.template);
7495            }
7496
7497            if (bindings.click) {
7498                bind.events = bind.events || {};
7499                bind.events.click = bind.click;
7500                delete bindings.click;
7501            }
7502
7503            if (bindings.source) {
7504                deep = false;
7505            }
7506
7507            if (bind.attr) {
7508                bindings.attr = createBindings(bind.attr, source, Binding);
7509            }
7510
7511            if (bind.style) {
7512                bindings.style = createBindings(bind.style, source, Binding);
7513            }
7514
7515            if (bind.events) {
7516                bindings.events = createBindings(bind.events, source, EventBinding);
7517            }
7518
7519            target.bind(bindings);
7520        }
7521
7522        if (target) {
7523            element.kendoBindingTarget = target;
7524        }
7525
7526        if (deep && children) {
7527            for (idx = 0; idx < children.length; idx++) {
7528                bindElement(children[idx], source, namespace);
7529            }
7530        }
7531    }
7532
7533    function bind(dom, object, namespace) {
7534        var idx, length;
7535
7536        object = kendo.observable(object);
7537        dom = $(dom);
7538
7539        for (idx = 0, length = dom.length; idx < length; idx++ ) {
7540            bindElement(dom[idx], object, namespace);
7541        }
7542    }
7543
7544    function unbindElement(element) {
7545        var bindingTarget = element.kendoBindingTarget;
7546
7547        if (bindingTarget) {
7548            bindingTarget.destroy();
7549
7550            if ($.support.deleteExpando) {
7551                delete element.kendoBindingTarget;
7552            } else if (element.removeAttribute) {
7553                element.removeAttribute("kendoBindingTarget");
7554            } else {
7555                element.kendoBindingTarget = null;
7556            }
7557        }
7558    }
7559
7560    function unbindElementTree(element) {
7561        var idx,
7562            length,
7563            children = element.children;
7564
7565        unbindElement(element);
7566
7567        if (children) {
7568            for (idx = 0, length = children.length; idx < length; idx++) {
7569                unbindElementTree(children[idx]);
7570            }
7571        }
7572    }
7573
7574    function unbind(dom) {
7575        var idx, length;
7576
7577        dom = $(dom);
7578
7579        for (idx = 0, length = dom.length; idx < length; idx++ ) {
7580            unbindElementTree(dom[idx]);
7581        }
7582    }
7583
7584    function notify(widget, namespace) {
7585        var element = widget.element,
7586            bindingTarget = element[0].kendoBindingTarget;
7587
7588        if (bindingTarget) {
7589            bind(element, bindingTarget.source, namespace);
7590        }
7591    }
7592
7593    kendo.unbind = unbind;
7594    kendo.bind = bind;
7595    kendo.data.binders = binders;
7596    kendo.data.Binder = Binder;
7597    kendo.notify = notify;
7598
7599    kendo.observable = function(object) {
7600        if (!(object instanceof ObservableObject)) {
7601            object = new ObservableObject(object);
7602        }
7603
7604        return object;
7605    };
7606
7607})(jQuery);
7608;(function($, undefined) {
7609    var kendo = window.kendo,
7610        Widget = kendo.ui.Widget,
7611        INVALIDMSG = "k-invalid-msg",
7612        INVALIDINPUT = "k-invalid",
7613        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,
7614        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,
7615        INPUTSELECTOR = ":input:not(:button,[type=submit],[type=reset])",
7616        NUMBERINPUTSELECTOR = "[type=number],[type=range]",
7617        BLUR = "blur",
7618        NAME = "name",
7619        FORM = "form",
7620        NOVALIDATE = "novalidate",
7621        proxy = $.proxy,
7622        patternMatcher = function(value, pattern) {
7623            if (typeof pattern === "string") {
7624                pattern = new RegExp('^(?:' + pattern + ')$');
7625            }
7626            return pattern.test(value);
7627        },
7628        matcher = function(input, selector, pattern) {
7629            var value = input.val();
7630
7631            if (input.filter(selector).length && value !== "") {
7632                return patternMatcher(value, pattern);
7633            }
7634            return true;
7635        },
7636        hasAttribute = function(input, name) {
7637            if (input.length)  {
7638                return input[0].attributes[name] !== undefined;
7639            }
7640            return false;
7641        },
7642        nameSpecialCharRegExp = /(\[|\]|\$|\.|\:|\+)/g;
7643
7644    /**
7645     *  @name kendo.ui.Validator.Description
7646     *
7647     *  @section
7648     *  <p>
7649     *     Validator offers an easy way to do client-side form validation.
7650     *     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.
7651     *  </p>
7652     *  @exampleTitle <b>Validator</b> initialization to validate input elements inside a container
7653     *  @example
7654     *  <div id="myform">
7655     *   <input type="text" name="firstName" required />
7656     *   <input type="text" name="lastName" required />
7657     *   <button id="save" type="button">Save</button>
7658     *  </div>
7659     *
7660     *  <script>
7661     *   $(document).ready(function(){
7662     *       var validatable = $("#myform").kendoValidator().data("kendoValidator");
7663     *       $("#save").click(function() {
7664     *          if (validatable.validate()) {
7665     *              save();
7666     *          }
7667     *       });
7668     *   });
7669     *   </script>
7670     *  @section <h4>Validation Rules</h4>
7671     *
7672     *  @exampleTitle <strong>required</strong>- element should have a value
7673     *  @example
7674     *  <input type="text" name="firstName" required />
7675     *
7676     *  @exampleTitle <strong>pattern</strong>- constrains the value to match a specific regular expression
7677     *  @example
7678     *  <input type="text" name="twitter" pattern="https?://(?:www\.)?twitter\.com/.+i" />
7679     *
7680     *  @exampleTitle <strong>max/min</strong>- constrain the minimum and/or maximum numeric values that can be entered
7681     *  @example
7682     *  <input type="number" name="age" min="1" max="42" />
7683     *
7684     *  @exampleTitle <strong>step</strong>- when used in combination with the min and max attributes, constrains the granularity of values that can be entered
7685     *  @example
7686     *  <input type="number" name="age" min="1" max="100" step="2" />
7687     *
7688     *  @exampleTitle <strong>url</strong>- constrain the value to being a valid URL
7689     *  @example
7690     *  <input type="url" name="url" />
7691     *
7692     *  @exampleTitle <strong>email</strong>- constrain the value to being a valid email
7693     *  @example
7694     *  <input type="email" name="email" />
7695     *
7696     *  @section
7697     *  <p>Beside the built-in validation rules, KendoUI Validator also provides a convenient way for setting custom rules through its rules configuration option. </p>
7698     *
7699     *  @exampleTitle
7700     *  @example
7701     *  $("#myform").kendoValidator({
7702     *      rules: {
7703     *        custom: function(input) {
7704     *          // Only Tom will be a valid value for FirstName input
7705     *          return input.is("[name=firstname]") && input.val() === "Tom";
7706     *        }
7707     *      }
7708     * });
7709     *
7710     *  @section <h4>Validation Messages</h4>
7711     *  <p>There are several ways to control the messages which appears if validation fails:</p>
7712     *
7713     *  @exampleTitle Set the validation messages for all input elements, through configuration options
7714     *  @example
7715     *   $("#myform").kendoValidator({
7716     *      rules: {
7717     *          custom: function(input) {
7718     *                  //...
7719     *          }
7720     *      },
7721     *      messages: {
7722     *        // defines message for the 'custom' validation rule
7723     *        custom: "Please enter valid value for my custom rule",
7724     *        // overrides the built-in message for required rule
7725     *        required: "My custom required message",
7726     *        // overrides the built-in email rule message with a custom function which return the actual message
7727     *        email: function(input) {
7728     *          return getMessage(input);
7729     *        }
7730     *     }
7731     *  });
7732     *  @exampleTitle Use the title and validationMessage attributes to set per input element messages
7733     *  @example
7734     *     <input type="tel" pattern="\d{10}" validationMessage="Plase enter a ten digit phone number" />
7735     *
7736     *  @section <h4>Triggering validation</h4>
7737     *  <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>
7738     *  <p>
7739     *  Note that if a HTML form element is set as validation container, the form submits will be automatically prevented if validation fails.
7740     *  </p>
7741     *  @section <h4>Initialize Kendo Validator with specific tooltip position</h4>
7742     *
7743     *  <p>
7744     *      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
7745     *      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
7746     *      adding a span with data-for attribute set to the validated input name and a class .k-invalid-msg. Check the example below:
7747     *  </p>
7748     *
7749     *  @exampleTitle <b>Validator</b> initialization with specific tooltip placement (the tooltip will remain outside of the AutoComplete widget after enhancement)
7750     *  @example
7751     *  <div id="myform">
7752     *      <input type="text" id="name" name="name" required />
7753     *      <span class="k-invalid-msg" data-for="name"></span>
7754     *  </div>
7755     *
7756     *  <script>
7757     *      $("#name").kendoAutoComplete({
7758     *                     dataSource: data,
7759     *                     separator: ", "
7760     *                 });
7761     *
7762     *      $("#myform").kendoValidator();
7763     *  </script>
7764     */
7765    var Validator = Widget.extend(/** @lends kendo.ui.Validator.prototype */{ /**
7766         * @constructs
7767         * @extends kendo.ui.Widget
7768         * @param {DomElement} element DOM element
7769         * @param {Object} options Configuration options.
7770         * @option {Object} [rules] Set of validation rules. Those rules will extend the built-in ones.
7771         * _example
7772         * $("#myform").kendoValidator({
7773         *      rules: {
7774         *          custom: function(input) {
7775         *              return input.is("[name=firstname]") && input.val() === "Tom"; // Only Tom will be a valid value for FirstName input
7776         *          }
7777         *      }
7778         * });
7779         * @option {Object} [messages] Set of messages (either strings or functions) which will be shown when given validation rule fails.
7780         *  By setting already existing key the appropriate built-in message will be overridden.
7781         * _example
7782         * $("#myform").kendoValidator({
7783         *      rules: {
7784         *          custom: function(input) {
7785         *             //...
7786         *          }
7787         *      },
7788         *      messages: {
7789         *          custom: "Please enter valid value for my custom rule",// defines message for the 'custom' validation rule
7790         *          required: "My custom required message", // overrides the built-in message for required rule
7791         *          email: function(input) { // overrides the built-in email rule message with a custom function which return the actual message
7792         *              return getMessage(input);
7793         *          }
7794         *      }
7795         * });
7796         * @option {Boolean} [validateOnBlur] Determines if validation will be triggered when element loses focus. Default value is true.
7797         */
7798        init: function(element, options) {
7799            var that = this;
7800
7801            Widget.fn.init.call(that, element, options);
7802
7803            that._errorTemplate = kendo.template(that.options.errorTemplate);
7804
7805            if (that.element.is(FORM)) {
7806                that.element.attr(NOVALIDATE, NOVALIDATE);
7807            }
7808
7809            that._errors = {};
7810            that._attachEvents();
7811        },
7812
7813        options: {
7814            name: "Validator",
7815            errorTemplate: '<span class="k-widget k-tooltip k-tooltip-validation">' +
7816                '<span class="k-icon k-warning"> </span> ${message}</span>',
7817            messages: {
7818                required: "{0} is required",
7819                pattern: "{0} is not valid",
7820                min: "{0} should be greater than or equal to {1}",
7821                max: "{0} should be smaller than or equal to {1}",
7822                step: "{0} is not valid",
7823                email: "{0} is not valid email",
7824                url: "{0} is not valid URL",
7825                date: "{0} is not valid date"
7826            },
7827            rules: {
7828                required: function(input) {
7829                    var checkbox = input.filter("[type=checkbox]").length && input.attr("checked") !== "checked",
7830                        value = input.val();
7831
7832                    return !(hasAttribute(input, "required") && (value === "" || !value  || checkbox));
7833
7834                },
7835                pattern: function(input) {
7836                    if (input.filter("[type=text],[type=email],[type=url],[type=tel],[type=search]").filter("[pattern]").length && input.val() !== "") {
7837                        return patternMatcher(input.val(), input.attr("pattern"));
7838                    }
7839                    return true;
7840                },
7841                min: function(input) {
7842                    if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[min]").length && input.val() !== "") {
7843                        var min = parseFloat(input.attr("min")) || 0,
7844                            val = parseFloat(input.val());
7845
7846                        return min <= val;
7847                    }
7848                    return true;
7849                },
7850                max: function(input) {
7851                    if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[max]").length && input.val() !== "") {
7852                        var max = parseFloat(input.attr("max")) || 0,
7853                            val = parseFloat(input.val());
7854
7855                        return max >= val;
7856                    }
7857                    return true;
7858                },
7859                step: function(input) {
7860                    if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[step]").length && input.val() !== "") {
7861                        var min = parseFloat(input.attr("min")) || 0,
7862                            step = parseFloat(input.attr("step")) || 0,
7863                            val = parseFloat(input.val());
7864
7865                        return (((val-min)*10)%(step*10)) / 100 === 0;
7866                    }
7867                    return true;
7868                },
7869                email: function(input) {
7870                    return matcher(input, "[type=email],[" + kendo.attr("type") + "=email]", emailRegExp);
7871                },
7872                url: function(input) {
7873                    return matcher(input, "[type=url],[" + kendo.attr("type") + "=url]", urlRegExp);
7874                },
7875                date: function(input) {
7876                    if (input.filter("[type^=date],[" + kendo.attr("type") + "=date]").length && input.val() !== "") {
7877                        return kendo.parseDate(input.val(), input.attr(kendo.attr("format"))) !== null;
7878                    }
7879                    return true;
7880                }
7881            },
7882            validateOnBlur: true
7883        },
7884
7885        _submit: function(e) {
7886            if (!this.validate()) {
7887                e.stopPropagation();
7888                e.stopImmediatePropagation();
7889                e.preventDefault();
7890                return false;
7891            }
7892            return true;
7893        },
7894
7895        _attachEvents: function() {
7896            var that = this;
7897
7898            if (that.element.is(FORM)) {
7899                that.element.submit(proxy(that._submit, that));
7900            }
7901
7902            if (that.options.validateOnBlur) {
7903                if (!that.element.is(INPUTSELECTOR)) {
7904                    that.element.delegate(INPUTSELECTOR, BLUR, function() {
7905                        that.validateInput($(this));
7906                    });
7907                } else {
7908                    that.element.bind(BLUR, function() {
7909                        that.validateInput(that.element);
7910                    });
7911                }
7912            }
7913        },
7914
7915        /**
7916         * Validates the input element(s) against the declared validation rules.
7917         * @returns {Boolean} If all rules are passed successfully.
7918         * @example
7919         * // get a reference to the validatable form
7920         * var validatable = $("#myform").kendoValidator().data("kendoValidator");
7921         * // check validation on save button click
7922         * $("#save").click(function() {
7923         *     if (validatable.validate()) {
7924         *         save();
7925         *     }
7926         * });
7927         */
7928        validate: function() {
7929            var that = this,
7930                inputs,
7931                idx,
7932                invalid = false,
7933                length;
7934
7935            that._errors = {};
7936
7937            if (!that.element.is(INPUTSELECTOR)) {
7938                inputs = that.element.find(INPUTSELECTOR);
7939
7940                for (idx = 0, length = inputs.length; idx < length; idx++) {
7941                    if (!that.validateInput(inputs.eq(idx))) {
7942                        invalid = true;
7943                    }
7944                }
7945                return !invalid;
7946            }
7947            return that.validateInput(that.element);
7948        },
7949
7950        /**
7951         * Validates the input element against the declared validation rules.
7952         * @param {DomElement} input Input element to be validated.
7953         * @returns {Boolean} If all rules are passed successfully.
7954         */
7955        validateInput: function(input) {
7956            input = $(input);
7957
7958            var that = this,
7959                template = that._errorTemplate,
7960                result = that._checkValidity(input),
7961                valid = result.valid,
7962                className = "." + INVALIDMSG,
7963                fieldName = (input.attr(NAME) || ""),
7964                DATAFOR = kendo.attr("for"),
7965                lbl = that.element.find(className + "[" + DATAFOR +"=" + fieldName.replace(nameSpecialCharRegExp, "\\$1") + "]").add(input.next(className)).hide(),
7966                messageText;
7967
7968            if (!valid) {
7969                messageText = that._extractMessage(input, result.key);
7970                that._errors[fieldName] = messageText;
7971                var messageLabel = $(template({ message: messageText })).addClass(INVALIDMSG).attr(DATAFOR, fieldName || "");
7972
7973                if (!lbl.replaceWith(messageLabel).length) {
7974                    messageLabel.insertAfter(input);
7975                }
7976                messageLabel.show();
7977            }
7978
7979            input.toggleClass(INVALIDINPUT, !valid);
7980
7981            return valid;
7982        },
7983
7984        _extractMessage: function(input, ruleKey) {
7985            var that = this,
7986                customMessage = that.options.messages[ruleKey],
7987                fieldName = input.attr(NAME);
7988
7989            customMessage = $.isFunction(customMessage) ? customMessage(input) : customMessage;
7990
7991            return kendo.format(input.attr(kendo.attr(ruleKey + "-msg")) || input.attr("validationMessage") || input.attr("title") || customMessage || "", fieldName, input.attr(ruleKey));
7992        },
7993
7994        _checkValidity: function(input) {
7995            var rules = this.options.rules,
7996                rule;
7997
7998            for (rule in rules) {
7999                if (!rules[rule](input)) {
8000                    return { valid: false, key: rule };
8001                }
8002            }
8003
8004            return { valid: true };
8005        },
8006
8007        /**
8008         * Get the error messages if any.
8009         * @returns {Array} Messages for the failed validation rules.
8010         * @example
8011         * // get a reference to the validatable form
8012         * var validatable = $("#myform").kendoValidator().data("kendoValidator");
8013         * $("#save").click(function() {
8014         *     if (validatable.validate() === false) {
8015         *         // get the errors and write them out to the "errors" html container
8016         *         var errors = validatable.errors();
8017         *         $(errors).each(function() {
8018         *             $("#errors").html(this);
8019         *         });
8020         *     }
8021         * });
8022         */
8023        errors: function() {
8024            var results = [],
8025                errors = this._errors,
8026                error;
8027
8028            for (error in errors) {
8029                results.push(errors[error]);
8030            }
8031            return results;
8032        }
8033    });
8034
8035    kendo.ui.plugin(Validator);
8036})(jQuery);
8037(function ($, undefined) {
8038    var kendo = window.kendo,
8039        support = kendo.support,
8040        pointers = support.pointers,
8041        document = window.document,
8042        SURFACE = $(document.documentElement),
8043        Class = kendo.Class,
8044        Widget = kendo.ui.Widget,
8045        Observable = kendo.Observable,
8046        proxy = $.proxy,
8047        extend = $.extend,
8048        getOffset = kendo.getOffset,
8049        draggables = {},
8050        dropTargets = {},
8051        lastDropTarget,
8052        RESIZE_EVENT = "resize",
8053        invalidZeroEvents = support.mobileOS && support.mobileOS.android,
8054        START_EVENTS = "mousedown",
8055        MOVE_EVENTS = "mousemove",
8056        END_EVENTS = "mouseup mouseleave",
8057        KEYUP = "keyup",
8058        CHANGE = "change",
8059
8060        // Draggable events
8061        DRAGSTART = "dragstart",
8062        DRAG = "drag",
8063        DRAGEND = "dragend",
8064        DRAGCANCEL = "dragcancel",
8065
8066        // DropTarget events
8067        DRAGENTER = "dragenter",
8068        DRAGLEAVE = "dragleave",
8069        DROP = "drop",
8070
8071        // Drag events
8072        START = "start",
8073        MOVE = "move",
8074        END = "end",
8075        CANCEL = "cancel",
8076        TAP = "tap";
8077
8078    if (support.touch) {
8079        RESIZE_EVENT = "orientationchange";
8080        START_EVENTS = "touchstart";
8081        MOVE_EVENTS = "touchmove";
8082        END_EVENTS = "touchend touchcancel";
8083    }
8084
8085    if(pointers) {
8086        RESIZE_EVENT = "orientationchange resize";
8087        START_EVENTS = "MSPointerDown";
8088        MOVE_EVENTS = "MSPointerMove";
8089        END_EVENTS = "MSPointerUp MSPointerCancel";
8090    }
8091
8092    function contains(parent, child) {
8093        try {
8094            return $.contains(parent, child) || parent == child;
8095        } catch (e) {
8096            return false;
8097        }
8098    }
8099
8100    function elementUnderCursor(e) {
8101        return document.elementFromPoint(e.x.client, e.y.client);
8102    }
8103
8104    function addNS(events, ns) {
8105        return events.replace(/ /g, ns + " ");
8106    }
8107
8108    function preventTrigger(e) {
8109        e.preventDefault();
8110        $(document.body).trigger(e.type);
8111    }
8112
8113    /**
8114     * @name kendo.DragAxis.Description
8115     *
8116     * @section <h4>DragAxis</h4>
8117     * The DragAxis is used internally by the kendo.Drag component to store and calculate event data.
8118     * The Drag component contains two DragAxis instances: <code>x</code> for the horizontal coordinates, and <code>y</code> for the vertical.
8119     * The two DragAxis instances are available in each Drag event parameter.
8120     * @exampleTitle Access DragAxis information in Drag start event
8121     * @example
8122     * new kendo.Drag($("#foo"), {
8123     *  start: function(e) {
8124     *      console.log(x); // Horizontal axis
8125     *      console.log(y); // Vertical axis
8126     *  }
8127     * });
8128     *
8129     * @section Each axis instance contains the following fields:
8130     * <ul>
8131     *   <li><b>location</b> - the offset of the mouse/touch relative to the entire document (pageX/Y);</li>
8132     *   <li><b>startLocation</b> - the offset of the mouse/touch relative to the document when the drag started;</li>
8133     *   <li><b>client</b> - the offset of the mouse/touch relative to the viewport (clientX/Y);</li>
8134     *   <li><b>delta</b> - the change from the previous event location</li>
8135     *   <li><b>velocity</b> - the pixels per millisecond speed of the current move.</li>
8136     * </ul>
8137     */
8138    var DragAxis = Class.extend(/** @lends kendo.DragAxis.prototype */{
8139        /**
8140         * @constructs
8141         */
8142        init: function(axis) {
8143            this.axis = axis;
8144        },
8145
8146        start: function(location, timeStamp) {
8147            var that = this,
8148                offset = location["page" + that.axis];
8149
8150            that.startLocation = that.location = offset;
8151            that.client = location["client" + that.axis];
8152            that.velocity = that.delta = 0;
8153            that.timeStamp = timeStamp;
8154        },
8155
8156        move: function(location, timeStamp) {
8157            var that = this,
8158                offset = location["page" + that.axis];
8159
8160            if (!offset && invalidZeroEvents) {
8161                return;
8162            }
8163
8164            that.delta = offset - that.location;
8165            that.location = offset;
8166            that.client = location["client" + that.axis];
8167            that.initialDelta = offset - that.startLocation;
8168            that.velocity = that.delta / (timeStamp - that.timeStamp);
8169            that.timeStamp = timeStamp;
8170        }
8171    });
8172
8173    /**
8174     * @name kendo.Drag.Description
8175     * @section <h4>Drag</h4> The kendo Drag component provides a cross-browser, touch-friendly way to handle mouse and touch drag events.
8176     * @exampleTitle <b>Drag</b> initialization
8177     * @example
8178     * var drag = new kendo.Drag($("#draggable"));
8179     */
8180    var Drag = Observable.extend(/** @lends kendo.Drag.prototype */{
8181        /**
8182         * @constructs
8183         * @extends kendo.Observable
8184         * @param {DomElement} element the DOM element from which the drag event starts.
8185         * @param {Object} options Configuration options.
8186         * @option {Integer} [threshold] <0> The minimum distance the mouse/touch should move before the event is triggered.
8187         * @option {Boolean} [global] <false> If set to true, the drag event will be tracked beyond the element boundaries.
8188         * @option {Boolean} [allowSelection] <false> If set to true, the mousedown and selectstart events will not be prevented.
8189         * @option {Boolean} [stopPropagation] <false> If set to true, the mousedown event propagation will stopped, disabling
8190         * drag capturing at parent elements.
8191         * If set to false, dragging outside of the element boundaries will trigger the <code>end</code> event.
8192         * @option {Selector} [filter] If passed, the filter limits the child elements that will trigger the event sequence.
8193         */
8194        init: function(element, options) {
8195            var that = this,
8196                eventMap = {},
8197                filter,
8198                preventIfMoving,
8199                ns = "." + kendo.guid();
8200
8201            options = options || {};
8202            filter = that.filter = options.filter;
8203            that.threshold = options.threshold || 0;
8204
8205            element = $(element);
8206            Observable.fn.init.call(that);
8207
8208            eventMap = {};
8209
8210            eventMap[addNS(MOVE_EVENTS, ns)] = proxy(that._move, that);
8211            eventMap[addNS(END_EVENTS, ns)] = proxy(that._end, that);
8212
8213            extend(that, {
8214                x: new DragAxis("X"),
8215                y: new DragAxis("Y"),
8216                element: element,
8217                surface: options.global ? SURFACE : element,
8218                stopPropagation: options.stopPropagation,
8219                pressed: false,
8220                eventMap: eventMap,
8221                ns: ns
8222            });
8223
8224            element
8225                .on(START_EVENTS, filter, proxy(that._start, that))
8226                .on("dragstart", filter, kendo.preventDefault);
8227
8228            if (!options.allowSelection) {
8229                var args = ["mousedown selectstart", filter, preventTrigger];
8230
8231                if (filter instanceof $) {
8232                    args.splice(2, 0, null);
8233                }
8234
8235                element.on.apply(element, args);
8236            }
8237
8238            if (support.eventCapture) {
8239                preventIfMoving = function(e) {
8240                    if (that.moved) {
8241                        e.preventDefault();
8242                    }
8243                };
8244
8245                that.surface[0].addEventListener(support.mouseup, preventIfMoving, true);
8246            }
8247
8248            that.bind([
8249            /**
8250             * Fires when the user presses and releases the element without any movement or with a movement below the <code>threshold</code> specified.
8251             * @name kendo.Drag#tap
8252             * @event
8253             * @param {Event} e
8254             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
8255             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
8256             * @param {jQueryEvent} e.event Reference to the jQuery event object.
8257             * @param {Element} e.target Reference to the DOM element from which the Drag started.
8258             * It is different from the element only if <code>filter</code> option is specified.
8259             */
8260            TAP,
8261            /**
8262             * Fires when the user starts dragging the element.
8263             * @name kendo.Drag#start
8264             * @event
8265             * @param {Event} e
8266             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
8267             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
8268             * @param {jQueryEvent} e.event Reference to the jQuery event object.
8269             * @param {Element} e.target Reference to the DOM element from which the Drag started.
8270             * It is different from the element only if <code>filter</code> option is specified.
8271             */
8272            START,
8273            /**
8274             * Fires while dragging.
8275             * @name kendo.Drag#move
8276             * @event
8277             * @param {Event} e
8278             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
8279             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
8280             * @param {jQueryEvent} e.event Reference to the jQuery event object.
8281             * @param {Element} e.target Reference to the DOM element from which the Drag started.
8282             * It is different from the element only if <code>filter</code> option is specified.
8283             */
8284            MOVE,
8285            /**
8286             * Fires when the drag ends.
8287             * @name kendo.Drag#end
8288             * @event
8289             * @param {Event} e
8290             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
8291             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
8292             * @param {jQueryEvent} e.event Reference to the jQuery event object.
8293             * @param {Element} e.target Reference to the DOM element from which the Drag started.
8294             * It is different from the element only if <code>filter</code> option is specified.
8295             */
8296            END,
8297            /**
8298             * Fires when the drag is canceled. This  when the <code>cancel</code> method is called.
8299             * @name kendo.Drag#cancel
8300             * @event
8301             * @param {Event} e
8302             * @param {DragAxis} e.x Reference to the horizontal drag axis instance.
8303             * @param {DragAxis} e.y Reference to the vertical drag axis instance.
8304             * @param {jQueryEvent} e.event Reference to the jQuery event object.
8305             * @param {Element} e.target Reference to the DOM element from which the Drag started.
8306             * It is different from the element only if <code>filter</code> option is specified.
8307             */
8308            CANCEL], options);
8309        },
8310
8311        /**
8312         * Capture the current drag, so that Drag listeners bound to parent elements will not trigger.
8313         * This method will not have any effect if the current drag instance is instantiated with the <code>global</code> option set to true.
8314         */
8315        capture: function() {
8316            Drag.captured = true;
8317        },
8318
8319        /**
8320         * Discard the current drag. Calling the <code>cancel</code> method will trigger the <code>cancel</code> event.
8321         * The correct moment to call this method would be in the <code>start</code> event handler.
8322         * @exampleTitle Cancel the drag event sequence
8323         * @example
8324         * new kendo.Drag($("#foo"), {
8325         *  start: function(e) {
8326         *      e.cancel();
8327         *  }
8328         * });
8329         */
8330        cancel: function() {
8331            this._cancel();
8332            this.trigger(CANCEL);
8333        },
8334
8335        skip: function() {
8336            this._cancel();
8337        },
8338
8339        _cancel: function() {
8340            var that = this;
8341            that.moved = that.pressed = false;
8342            that.surface.off(that.ns);
8343        },
8344
8345        _start: function(e) {
8346            var that = this,
8347                filter = that.filter,
8348                originalEvent = e.originalEvent,
8349                touch,
8350                location = e;
8351
8352            if (that.pressed) { return; }
8353
8354            if (filter) {
8355                that.target = $(e.target).is(filter) ? $(e.target) : $(e.target).closest(filter);
8356            } else {
8357                that.target = that.element;
8358            }
8359
8360            if (!that.target.length) {
8361                return;
8362            }
8363
8364            if (that.stopPropagation) {
8365              e.stopPropagation();
8366            }
8367
8368            that.pressed = true;
8369            that.moved = false;
8370
8371            if (support.touch) {
8372                touch = originalEvent.changedTouches[0];
8373                that.touchID = touch.identifier;
8374                location = touch;
8375            }
8376
8377            if (pointers) {
8378                that.touchID = originalEvent.pointerId;
8379                location = originalEvent;
8380            }
8381
8382            that._perAxis(START, location, e.timeStamp);
8383            that.surface.off(that.eventMap).on(that.eventMap);
8384            Drag.captured = false;
8385        },
8386
8387        _move: function(e) {
8388            var that = this,
8389                xDelta,
8390                yDelta,
8391                delta;
8392
8393            if (!that.pressed) { return; }
8394
8395            that._withEvent(e, function(location) {
8396
8397                that._perAxis(MOVE, location, e.timeStamp);
8398
8399                if (!that.moved) {
8400                    xDelta = that.x.initialDelta;
8401                    yDelta = that.y.initialDelta;
8402
8403                    delta = Math.sqrt(xDelta * xDelta + yDelta * yDelta);
8404
8405                    if (delta <= that.threshold) {
8406                        return;
8407                    }
8408
8409                    if (!Drag.captured) {
8410                        that._trigger(START, e);
8411                        that.moved = true;
8412                    } else {
8413                        return that._cancel();
8414                    }
8415                }
8416
8417                // Event handlers may cancel the swipe in the START event handler, hence the double check for pressed.
8418                if (that.pressed) {
8419                    that._trigger(MOVE, e);
8420                }
8421            });
8422        },
8423
8424        _end: function(e) {
8425            var that = this;
8426
8427            if (!that.pressed) { return; }
8428
8429            that._withEvent(e, function() {
8430                if (that.moved) {
8431                    that._trigger(END, e);
8432                    that.moved = false;
8433                } else {
8434                    that._trigger(TAP, e);
8435                }
8436
8437                that._cancel();
8438            });
8439        },
8440
8441        _perAxis: function(method, location, timeStamp) {
8442            this.x[method](location, timeStamp);
8443            this.y[method](location, timeStamp);
8444        },
8445
8446        _trigger: function(name, e) {
8447            var data = {
8448                x: this.x,
8449                y: this.y,
8450                target: this.target,
8451                event: e
8452            };
8453
8454            if(this.trigger(name, data)) {
8455                e.preventDefault();
8456            }
8457        },
8458
8459        _withEvent: function(e, callback) {
8460            var that = this,
8461                touchID = that.touchID,
8462                originalEvent = e.originalEvent,
8463                touches,
8464                idx;
8465
8466            if (support.touch) {
8467                touches = originalEvent.changedTouches;
8468                idx = touches.length;
8469
8470                while (idx) {
8471                    idx --;
8472                    if (touches[idx].identifier === touchID) {
8473                        return callback(touches[idx]);
8474                    }
8475                }
8476            }
8477            else if (pointers) {
8478                if (touchID === originalEvent.pointerId) {
8479                    return callback(originalEvent);
8480                }
8481            } else {
8482                return callback(e);
8483            }
8484        }
8485    });
8486
8487    var Tap = Observable.extend({
8488        init: function(element, options) {
8489            var that = this,
8490                domElement = element[0];
8491
8492            that.capture = false;
8493            domElement.addEventListener(START_EVENTS, proxy(that._press, that), true);
8494            $.each(END_EVENTS.split(" "), function() {
8495                domElement.addEventListener(this, proxy(that._release, that), true);
8496            });
8497
8498            Observable.fn.init.call(that);
8499
8500            that.bind(["press", "release"], options || {});
8501        },
8502
8503        _press: function(e) {
8504            var that = this;
8505            that.trigger("press");
8506            if (that.capture) {
8507                e.preventDefault();
8508            }
8509        },
8510
8511        _release: function(e) {
8512            var that = this;
8513            that.trigger("release");
8514
8515            if (that.capture) {
8516                e.preventDefault();
8517                that.cancelCapture();
8518            }
8519        },
8520
8521        captureNext: function() {
8522            this.capture = true;
8523        },
8524
8525        cancelCapture: function() {
8526            this.capture = false;
8527        }
8528    });
8529
8530    var PaneDimension = Observable.extend({
8531        init: function(options) {
8532            var that = this;
8533            Observable.fn.init.call(that);
8534
8535            $.extend(that, options);
8536
8537            that.max = 0;
8538
8539            if (that.horizontal) {
8540                that.measure = "width";
8541                that.scrollSize = "scrollWidth";
8542                that.axis = "x";
8543            } else {
8544                that.measure = "height";
8545                that.scrollSize = "scrollHeight";
8546                that.axis = "y";
8547            }
8548        },
8549
8550        outOfBounds: function(offset) {
8551            return  offset > this.max || offset < this.min;
8552        },
8553
8554        present: function() {
8555            return this.max - this.min;
8556        },
8557
8558        update: function(silent) {
8559            var that = this;
8560
8561            that.size = that.container[that.measure]();
8562            that.total = that.element[0][that.scrollSize];
8563            that.min = Math.min(that.max, that.size - that.total);
8564            if (!silent) {
8565                that.trigger(CHANGE, that);
8566            }
8567        }
8568    });
8569
8570    var PaneDimensions = Observable.extend({
8571        init: function(options) {
8572            var that = this,
8573                refresh = proxy(that.refresh, that),
8574                resizeHandler = refresh;
8575
8576            Observable.fn.init.call(that);
8577
8578            if (support.mobileOS.android) {
8579                resizeHandler = function() { setTimeout(refresh, 200); };
8580            }
8581
8582            that.x = new PaneDimension(extend({horizontal: true}, options));
8583            that.y = new PaneDimension(extend({horizontal: false}, options));
8584
8585            that.bind(CHANGE, options);
8586
8587            $(window).bind(RESIZE_EVENT, resizeHandler);
8588        },
8589
8590        refresh: function() {
8591            this.x.update();
8592            this.y.update();
8593            this.trigger(CHANGE);
8594        }
8595    });
8596
8597    var PaneAxis = Observable.extend({
8598        init: function(options) {
8599            var that = this;
8600            extend(that, options);
8601            Observable.fn.init.call(that);
8602        },
8603
8604        dragMove: function(delta) {
8605            var that = this,
8606                dimension = that.dimension,
8607                axis = that.axis,
8608                movable = that.movable,
8609                position = movable[axis] + delta;
8610
8611            if (!dimension.present()) {
8612                return;
8613            }
8614
8615            if ((position < dimension.min && delta < 0) || (position > dimension.max && delta > 0)) {
8616                delta *= that.resistance;
8617            }
8618
8619            movable.translateAxis(axis, delta);
8620            that.trigger(CHANGE, that);
8621        }
8622    });
8623
8624    var Pane = Class.extend({
8625        init: function(options) {
8626            var that = this,
8627                x,
8628                y,
8629                resistance;
8630
8631            extend(that, {elastic: true}, options);
8632
8633            resistance = that.elastic ? 0.5 : 0;
8634
8635            that.x = x = new PaneAxis({
8636                axis: "x",
8637                dimension: that.dimensions.x,
8638                resistance: resistance,
8639                movable: that.movable
8640            });
8641
8642            that.y = y = new PaneAxis({
8643                axis: "y",
8644                dimension: that.dimensions.y,
8645                resistance: resistance,
8646                movable: that.movable
8647            });
8648
8649            that.drag.bind(["move", "end"], {
8650                move: function(e) {
8651                    if (x.dimension.present() || y.dimension.present()) {
8652                        x.dragMove(e.x.delta);
8653                        y.dragMove(e.y.delta);
8654                        e.preventDefault();
8655                    } else {
8656                        that.drag.skip();
8657                    }
8658                },
8659
8660                end: function(e) {
8661                    e.preventDefault();
8662                }
8663            });
8664        }
8665    });
8666
8667    var TRANSFORM_STYLE = support.transitions.prefix + "Transform",
8668        round = Math.round,
8669        translate;
8670
8671    if (support.hasHW3D) {
8672        translate = function(x, y) {
8673            return "translate3d(" + round(x) + "px," + round(y) +"px,0)";
8674        };
8675    } else {
8676        translate = function(x, y) {
8677            return "translate(" + round(x) + "px," + round(y) +"px)";
8678        };
8679    }
8680
8681    var Movable = Observable.extend({
8682        init: function(element) {
8683            var that = this;
8684
8685            Observable.fn.init.call(that);
8686
8687            that.element = $(element);
8688            that.x = 0;
8689            that.y = 0;
8690            that._saveCoordinates(translate(that.x, that.y));
8691        },
8692
8693        translateAxis: function(axis, by) {
8694            this[axis] += by;
8695            this.refresh();
8696        },
8697
8698        translate: function(coordinates) {
8699            this.x += coordinates.x;
8700            this.y += coordinates.y;
8701            this.refresh();
8702        },
8703
8704        moveAxis: function(axis, value) {
8705            this[axis] = value;
8706            this.refresh();
8707        },
8708
8709        moveTo: function(coordinates) {
8710            extend(this, coordinates);
8711            this.refresh();
8712        },
8713
8714        refresh: function() {
8715            var that = this,
8716                newCoordinates = translate(that.x, that.y);
8717
8718            if (newCoordinates != that.coordinates) {
8719                that.element[0].style[TRANSFORM_STYLE] = newCoordinates;
8720                that._saveCoordinates(newCoordinates);
8721                that.trigger(CHANGE);
8722            }
8723        },
8724
8725        _saveCoordinates: function(coordinates) {
8726            this.coordinates = coordinates;
8727        }
8728    });
8729
8730    var DropTarget = Widget.extend(/** @lends kendo.ui.DropTarget.prototype */ {
8731        /**
8732         * @constructs
8733         * @extends kendo.ui.Widget
8734         * @param {DomElement} element DOM element
8735         * @param {Object} options Configuration options.
8736         * @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.
8737         */
8738        init: function(element, options) {
8739            var that = this;
8740
8741            Widget.fn.init.call(that, element, options);
8742
8743            var group = that.options.group;
8744
8745            if (!(group in dropTargets)) {
8746                dropTargets[group] = [ that ];
8747            } else {
8748                dropTargets[group].push( that );
8749            }
8750        },
8751
8752        events: [
8753            /**
8754             * Fires when draggable moves over the drop target.
8755             * @name kendo.ui.DropTarget#dragenter
8756             * @event
8757             * @param {Event} e
8758             * @param {jQueryObject} e.draggable Reference to the draggable that enters the drop target.
8759             */
8760            DRAGENTER,
8761            /**
8762             * Fires when draggable moves out of the drop target.
8763             * @name kendo.ui.DropTarget#dragleave
8764             * @event
8765             * @param {Event} e
8766             * @param {jQueryObject} e.draggable Reference to the draggable that leaves the drop target.
8767             */
8768            DRAGLEAVE,
8769            /**
8770             * Fires when draggable is dropped over the drop target.
8771             * @name kendo.ui.DropTarget#drop
8772             * @event
8773             * @param {Event} e
8774             * @param {jQueryObject} e.draggable Reference to the draggable that is dropped over the drop target.
8775             * @param {jQueryObject} e.draggable.currentTarget The element that the drag and drop operation started from.
8776             */
8777            DROP
8778        ],
8779
8780        options: {
8781            name: "DropTarget",
8782            group: "default"
8783        },
8784
8785        _trigger: function(eventName, e) {
8786            var that = this,
8787                draggable = draggables[that.options.group];
8788
8789            if (draggable) {
8790                return that.trigger(eventName, extend({}, e.event, {
8791                           draggable: draggable
8792                       }));
8793            }
8794        },
8795
8796        _over: function(e) {
8797            this._trigger(DRAGENTER, e);
8798        },
8799
8800        _out: function(e) {
8801            this._trigger(DRAGLEAVE, e);
8802        },
8803
8804        _drop: function(e) {
8805            var that = this,
8806                draggable = draggables[that.options.group];
8807
8808            if (draggable) {
8809                draggable.dropped = !that._trigger(DROP, e);
8810            }
8811        }
8812    });
8813
8814    /**
8815     * @name kendo.ui.Draggable.Description
8816     *
8817     * @section <h4>Draggable</h4>
8818     * Enable draggable functionality on any DOM element.
8819     *
8820     * @exampleTitle <b>Draggable</b> initialization
8821     * @example
8822     * var draggable = $("#draggable").kendoDraggable();
8823     *
8824     * @name kendo.ui.DropTarget.Description
8825     *
8826     * @section <h4>DropTarget</h4>
8827     * Enable any DOM element to be a target for draggable elements.
8828     *
8829     * @exampleTitle <b>DropTarget</b> initialization
8830     * @example
8831     * var dropTarget = $("#dropTarget").kendoDropTarget();
8832     */
8833    var Draggable = Widget.extend(/** @lends kendo.ui.Draggable.prototype */{
8834        /**
8835         * @constructs
8836         * @extends kendo.ui.Widget
8837         * @param {DomElement} element DOM element
8838         * @param {Object} options Configuration options.
8839         * @option {Integer} [distance] <5> The required distance that the mouse should travel in order to initiate a drag.
8840         * @option {Selector} [filter] Selects child elements that are draggable if a widget is attached to a container.
8841         * @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.
8842         * @option {Object} [cursorOffset] <null> If set, specifies the offset of the hint relative to the mouse cursor/finger.
8843         * 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>.
8844         * _exampleTitle Initialize Draggable with cursorOffset
8845         * _example
8846         * $("#draggable").kendoDraggable({cursorOffset: {top: 10, left: 10});
8847         * @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.
8848         * _example
8849         *  //hint as a function
8850         *  $("#draggable").kendoDraggable({
8851         *      hint: function(element) {
8852         *          return $("#draggable").clone();
8853         *          // same as
8854         *          //  return element.clone();
8855         *      }
8856         *  });
8857         *
8858         * //hint as jQuery object
8859         *  $("#draggable").kendoDraggable({
8860         *      hint: $("#draggableHint");
8861         *  });
8862         */
8863        init: function (element, options) {
8864            var that = this;
8865
8866            Widget.fn.init.call(that, element, options);
8867
8868            that.drag = new Drag(that.element, {
8869                global: true,
8870                stopPropagation: true,
8871                filter: that.options.filter,
8872                threshold: that.options.distance,
8873                start: proxy(that._start, that),
8874                move: proxy(that._drag, that),
8875                end: proxy(that._end, that),
8876                cancel: proxy(that._cancel, that)
8877            });
8878
8879            that.destroy = proxy(that._destroy, that);
8880            that.captureEscape = function(e) {
8881                if (e.keyCode === kendo.keys.ESC) {
8882                    that._trigger(DRAGCANCEL, {event: e});
8883                    that.drag.cancel();
8884                }
8885            };
8886        },
8887
8888        events: [
8889            /**
8890             * Fires when item drag starts.
8891             * @name kendo.ui.Draggable#dragstart
8892             * @event
8893             * @param {Event} e
8894             */
8895            DRAGSTART,
8896             /**
8897             * Fires while dragging.
8898             * @name kendo.ui.Draggable#drag
8899             * @event
8900             * @param {Event} e
8901             */
8902            DRAG,
8903             /**
8904             * Fires when item drag ends.
8905             * @name kendo.ui.Draggable#dragend
8906             * @event
8907             * @param {Event} e
8908             */
8909            DRAGEND,
8910             /**
8911             * Fires when item drag is canceled by pressing the Escape key.
8912             * @name kendo.ui.Draggable#dragcancel
8913             * @event
8914             * @param {Event} e
8915             */
8916            DRAGCANCEL
8917        ],
8918
8919        options: {
8920            name: "Draggable",
8921            distance: 5,
8922            group: "default",
8923            cursorOffset: null,
8924            dropped: false
8925        },
8926
8927        _start: function(e) {
8928            var that = this,
8929                options = that.options,
8930                hint = options.hint;
8931
8932            that.currentTarget = that.drag.target;
8933            that.currentTargetOffset = getOffset(that.currentTarget);
8934
8935            if (hint) {
8936                that.hint = $.isFunction(hint) ? $(hint(that.currentTarget)) : hint;
8937
8938                var offset = getOffset(that.currentTarget);
8939                that.hintOffset = offset;
8940                that.hint.css( {
8941                    position: "absolute",
8942                    zIndex: 20000, // the Window's z-index is 10000 and can be raised because of z-stacking
8943                    left: offset.x,
8944                    top: offset.y
8945                })
8946                .appendTo(document.body);
8947            }
8948
8949            draggables[options.group] = that;
8950
8951            that.dropped = false;
8952
8953            if (that._trigger(DRAGSTART, e)) {
8954                that.drag.cancel();
8955                that.destroy();
8956            }
8957
8958            $(document).on(KEYUP, that.captureEscape);
8959        },
8960
8961        updateHint: function(e) {
8962            var that = this,
8963                coordinates,
8964                cursorOffset = that.options.cursorOffset;
8965
8966            if (cursorOffset) {
8967               coordinates = { left: e.x.location + cursorOffset.left, top: e.y.location + cursorOffset.top };
8968            } else {
8969               that.hintOffset.left += e.x.delta;
8970               that.hintOffset.top += e.y.delta;
8971               coordinates = that.hintOffset;
8972            }
8973
8974            that.hint.css(coordinates);
8975        },
8976
8977        _drag: function(e) {
8978            var that = this;
8979
8980            e.preventDefault();
8981
8982            that._withDropTarget(e, function(target) {
8983                if (!target) {
8984                    if (lastDropTarget) {
8985                        lastDropTarget._trigger(DRAGLEAVE, e);
8986                        lastDropTarget = null;
8987                    }
8988                    return;
8989                }
8990
8991                if (lastDropTarget) {
8992                    if (target.element[0] === lastDropTarget.element[0]) {
8993                        return;
8994                    }
8995
8996                    lastDropTarget._trigger(DRAGLEAVE, e);
8997                }
8998
8999                target._trigger(DRAGENTER, e);
9000                lastDropTarget = target;
9001            });
9002
9003            that._trigger(DRAG, e);
9004
9005            if (that.hint) {
9006                that.updateHint(e);
9007            }
9008        },
9009
9010        _end: function(e) {
9011            var that = this;
9012
9013            that._withDropTarget(e, function(target) {
9014                if (target) {
9015                    target._drop(e);
9016                    lastDropTarget = null;
9017                }
9018            });
9019
9020            that._trigger(DRAGEND, e);
9021            that._cancel(e.event);
9022        },
9023
9024        _cancel: function(e) {
9025            var that = this;
9026
9027            if (that.hint && !that.dropped) {
9028                that.hint.animate(that.currentTargetOffset, "fast", that.destroy);
9029            } else {
9030                that.destroy();
9031            }
9032        },
9033
9034        _trigger: function(eventName, e) {
9035            var that = this;
9036
9037            return that.trigger(
9038            eventName, extend(
9039            {},
9040            e.event,
9041            {
9042                x: e.x,
9043                y: e.y,
9044                currentTarget: that.currentTarget
9045            }));
9046        },
9047
9048        _withDropTarget: function(e, callback) {
9049            var that = this,
9050                target,
9051                result,
9052                options = that.options,
9053                targets = dropTargets[options.group];
9054
9055            if (targets && targets.length) {
9056                target = elementUnderCursor(e);
9057
9058                if (that.hint && contains(that.hint, target)) {
9059                    that.hint.hide();
9060                    target = elementUnderCursor(e);
9061                    that.hint.show();
9062                }
9063
9064                $.each(targets, function() {
9065                    var that = this,
9066                        element = that.element[0];
9067
9068                    if (contains(element, target)) {
9069                        result = that;
9070                        return false;
9071                    }
9072                });
9073
9074                callback(result);
9075            }
9076        },
9077
9078        _destroy: function() {
9079            var that = this;
9080
9081            if (that.hint) {
9082                that.hint.remove();
9083            }
9084
9085            delete draggables[that.options.group];
9086
9087            that.trigger("destroy");
9088            $(document).off(KEYUP, that.captureEscape);
9089        }
9090    });
9091
9092    kendo.ui.plugin(DropTarget);
9093    kendo.ui.plugin(Draggable);
9094    kendo.Drag = Drag;
9095    kendo.Tap = Tap;
9096
9097    extend(kendo.ui, {
9098        Pane: Pane,
9099        PaneDimensions: PaneDimensions,
9100        Movable: Movable
9101    });
9102
9103 })(jQuery);
9104(function($, undefined) {
9105    var kendo = window.kendo,
9106        mobile = kendo.mobile,
9107        fx = kendo.fx,
9108        ui = mobile.ui,
9109        proxy = $.proxy,
9110        extend = $.extend,
9111        Widget = ui.Widget,
9112        Class = kendo.Class,
9113        Movable = kendo.ui.Movable,
9114        Pane = kendo.ui.Pane,
9115        PaneDimensions = kendo.ui.PaneDimensions,
9116        Transition = fx.Transition,
9117        Animation = fx.Animation,
9118        SNAPBACK_DURATION = 500,
9119        SCROLLBAR_OPACITY = 0.7,
9120        FRICTION = 0.93,
9121        OUT_OF_BOUNDS_FRICTION = 0.5,
9122        RELEASECLASS = "km-scroller-release",
9123        REFRESHCLASS = "km-scroller-refresh",
9124        PULL = "pull",
9125        CHANGE = "change",
9126        RESIZE = "resize",
9127        SCROLL = "scroll";
9128
9129    var DragInertia = Animation.extend({
9130        init: function(options) {
9131            var that = this;
9132
9133            Animation.fn.init.call(that);
9134
9135            extend(that, options, {
9136                transition: new Transition({
9137                    axis: options.axis,
9138                    movable: options.movable,
9139                    onEnd: function() { that._end(); }
9140                })
9141            });
9142
9143            that.tap.bind("press", function() { that.cancel(); });
9144            that.drag.bind("end", proxy(that.start, that));
9145            that.drag.bind("tap", proxy(that.onEnd, that));
9146        },
9147
9148        onCancel: function() {
9149            this.transition.cancel();
9150        },
9151
9152        freeze: function(location) {
9153            var that = this;
9154            that.cancel();
9155            that._moveTo(location);
9156        },
9157
9158        onEnd: function() {
9159            var that = this;
9160            if (that._outOfBounds()) {
9161                that._snapBack();
9162            } else {
9163                that._end();
9164            }
9165        },
9166
9167        done: function() {
9168            return Math.abs(this.velocity) < 1;
9169        },
9170
9171        start: function() {
9172            var that = this;
9173
9174            if (!that.dimension.present()) { return; }
9175
9176            if (that._outOfBounds()) {
9177                that._snapBack();
9178            } else {
9179                that.velocity = that.drag[that.axis].velocity * 16;
9180                if (that.velocity) {
9181                    that.tap.captureNext();
9182                    Animation.fn.start.call(that);
9183                }
9184            }
9185        },
9186
9187        tick: function() {
9188            var that = this,
9189                dimension = that.dimension,
9190                friction = that._outOfBounds() ? OUT_OF_BOUNDS_FRICTION : FRICTION,
9191                delta = (that.velocity *= friction),
9192                location = that.movable[that.axis] + delta;
9193
9194                if (!that.elastic && dimension.outOfBounds(location)) {
9195                    location = Math.max(Math.min(location, dimension.max), dimension.min);
9196                    that.velocity = 0;
9197                }
9198
9199            that.movable.moveAxis(that.axis, location);
9200        },
9201
9202        _end: function() {
9203            this.tap.cancelCapture();
9204            this.end();
9205        },
9206
9207        _outOfBounds: function() {
9208            return this.dimension.outOfBounds(this.movable[this.axis]);
9209        },
9210
9211        _snapBack: function() {
9212            var that = this,
9213                dimension = that.dimension,
9214                snapBack = that.movable[that.axis] > dimension.max ? dimension.max : dimension.min;
9215            that._moveTo(snapBack);
9216        },
9217
9218        _moveTo: function(location) {
9219            this.transition.moveTo({ location: location, duration: SNAPBACK_DURATION, ease: Transition.easeOutExpo });
9220        }
9221    });
9222
9223    var ScrollBar = Class.extend({
9224        init: function(options) {
9225            var that = this,
9226                horizontal = options.axis === "x",
9227                element = $('<div class="km-touch-scrollbar km-' + (horizontal ? "horizontal" : "vertical") + '-scrollbar" />');
9228
9229            extend(that, options, {
9230                element: element,
9231                elementSize: 0,
9232                movable: new Movable(element),
9233                scrollMovable: options.movable,
9234                size: horizontal ? "width" : "height"
9235            });
9236
9237            that.scrollMovable.bind(CHANGE, proxy(that._move, that));
9238            that.container.append(element);
9239        },
9240
9241        _move: function() {
9242            var that = this,
9243                axis = that.axis,
9244                dimension = that.dimension,
9245                paneSize = dimension.size,
9246                scrollMovable = that.scrollMovable,
9247                sizeRatio = paneSize / dimension.total,
9248                position = Math.round(-scrollMovable[axis] * sizeRatio),
9249                size = Math.round(paneSize * sizeRatio);
9250
9251                if (position + size > paneSize) {
9252                    size = paneSize - position;
9253                } else if (position < 0) {
9254                    size += position;
9255                    position = 0;
9256                }
9257
9258            if (that.elementSize != size) {
9259                that.element.css(that.size, size + "px");
9260                that.elementSize = size;
9261            }
9262
9263            that.movable.moveAxis(axis, position);
9264        },
9265
9266        show: function() {
9267            this.element.css({opacity: SCROLLBAR_OPACITY, visibility: "visible"});
9268        },
9269
9270        hide: function() {
9271            this.element.css({opacity: 0});
9272        }
9273    });
9274
9275    /**
9276     * @name kendo.mobile.ui.Scroller.Description
9277     * @section
9278     * <p>The Kendo Mobile Scroller widget enables touch friendly kinetic scrolling for the contents of a given DOM element.  </p>
9279     *
9280     * <h3>Getting Started</h3>
9281     * <p>Each mobile View initializes a scroller for its content element. In addition to that, a scroller will be initialized for every element with a
9282     * <code>role</code> data attribute set to <code>scroller</code>. Alternatively, it can be initialized using jQuery selector.</p>
9283     * @exampleTitle Initialize mobile Scroller using a role data attribute.
9284     * @example
9285     * <div data-role="scroller">
9286     *   Foo
9287     * </div>
9288     *
9289     * @exampleTitle Initialize mobile Scroller using a jQuery selector.
9290     * @example
9291     * <div id="scroller"></div>
9292     * <script>
9293     * var scroller = $("#scroller").kendoMobileScroller();
9294     * </script>
9295     *
9296     * @exampleTitle Obtain the current mobile view scroller
9297     * @example
9298     * <div data-role="view" data-init="getScroller">
9299     *   Foo
9300     * </div>
9301     * <script>
9302     *  function getScroller(e) {
9303     *     var scroller = e.view.scroller;
9304     *  }
9305     * </script>
9306     *
9307     * @section
9308     * <p>The scroller exposes the following fields:</p>
9309     * <ul>
9310     * <li><strong>scrollTop</strong> - the number of pixels that are hidden from view above the scrollable area.</li>
9311     * <li><strong>scrollLeft</strong> - the number of pixels that are hidden from view to the left of the scrollable area.</li>
9312     * </ul>
9313     *
9314     */
9315    var Scroller = Widget.extend(/** @lends kendo.mobile.ui.Scroller.prototype */{
9316        /**
9317         * @constructs
9318         * @extends kendo.mobile.ui.Widget
9319         * @param {DomElement} element DOM element
9320         * @param {Object} options
9321         * @option {Boolean} [elastic] <true> Weather or not to allow out of bounds dragging and easing.
9322         * @option {Number} [pullOffset] <140> The threshold bellow which a releasing the scroller will trigger the pull event.
9323         * Has effect only when the pullToRefresh option is set to true.
9324         * @option {String} [pullTemplate] <Pull to refresh> The message template displayed when the user pulls the scroller.
9325         * Has effect only when the pullToRefresh option is set to true.
9326         * @option {Boolean} [pullToRefresh] <false> If set to true, the scroller will display a hint when the user pulls the container beyond its top limit.
9327         * If a pull beyond the specified pullOffset occurs, a pull event will be triggered.
9328         * @option {String} [releaseTemplate] <Release to refresh> The message template displayed when the user pulls the scroller below the
9329         * pullOffset, indicating that pullToRefresh will occur.
9330         * Has effect only when the pullToRefresh option is set to true.
9331         * @option {String} [refreshTemplate] <Refreshing> The message template displayed during the refresh.
9332         * Has effect only when the pullToRefresh option is set to true.
9333         */
9334        init: function(element, options) {
9335            var that = this;
9336            Widget.fn.init.call(that, element, options);
9337
9338            element = that.element;
9339
9340            element
9341                .css("overflow", "hidden")
9342                .addClass("km-scroll-wrapper")
9343                .wrapInner('<div class="km-scroll-container"/>');
9344
9345            var inner = element.children().first(),
9346
9347                tap = new kendo.Tap(element),
9348
9349                movable = new Movable(inner),
9350
9351                dimensions = new PaneDimensions({
9352                    element: inner,
9353                    container: element,
9354                    change: function() {
9355                        that.trigger(RESIZE);
9356                    }
9357                }),
9358
9359                drag = new kendo.Drag(element, {
9360                    allowSelection: true,
9361                    start: function(e) {
9362                        dimensions.refresh();
9363                        drag.capture();
9364                    }
9365                }),
9366
9367                pane = new Pane({
9368                    movable: movable,
9369                    dimensions: dimensions,
9370                    drag: drag,
9371                    elastic: that.options.elastic
9372                });
9373
9374            movable.bind(CHANGE, function() {
9375                that.scrollTop = - movable.y;
9376                that.scrollLeft = - movable.x;
9377
9378                that.trigger(SCROLL, {
9379                    scrollTop: that.scrollTop,
9380                    scrollLeft: that.scrollLeft
9381                });
9382            });
9383
9384            extend(that, {
9385                movable: movable,
9386                dimensions: dimensions,
9387                drag: drag,
9388                pane: pane,
9389                tap: tap,
9390                pulled: false,
9391                scrollElement: inner
9392            });
9393
9394            that._initAxis("x");
9395            that._initAxis("y");
9396
9397            dimensions.refresh();
9398
9399            if (that.options.pullToRefresh) {
9400                that._initPullToRefresh();
9401            }
9402        },
9403
9404        /**
9405         * Returns the height in pixels of the scroller content.
9406         */
9407        scrollHeight: function() {
9408            return this.scrollElement[0].scrollHeight;
9409        },
9410
9411        /**
9412         * Returns the width in pixels of the scroller content.
9413         */
9414        scrollWidth: function() {
9415            return this.scrollElement[0].scrollWidth;
9416        },
9417
9418        options: {
9419            name: "Scroller",
9420            pullOffset: 140,
9421            elastic: true,
9422            pullTemplate: "Pull to refresh",
9423            releaseTemplate: "Release to refresh",
9424            refreshTemplate: "Refreshing"
9425        },
9426
9427        events: [
9428            /**
9429             * Fires when the pull option is set to true, and the user pulls the scrolling container beyond the specified pullThreshold.
9430             * @name kendo.mobile.ui.Scroller#pull
9431             * @event
9432             * @param {Event} e
9433             */
9434            PULL,
9435            /**
9436             * Fires when the pull option is set to true, and the user pulls the scrolling container beyond the specified pullThreshold.
9437             * @name kendo.mobile.ui.Scroller#scroll
9438             * @event
9439             * @param {Event} e
9440             * @param {Number} e.scrollTop The number of pixels that are hidden from view above the scrollable area.
9441             * @param {Number} e.scrollLeft The number of pixels that are hidden from view to the left of the scrollable area.
9442             * @exampleTitle Bind to scroller scroll event in view init
9443             * @example
9444             * <div data-role="view" data-init="attachToScroller"> ... </div>
9445             *  <script>
9446             *     function attachToScroller(e) {
9447             *       var scroller = e.view.scroller;
9448             *       scroller.bind("scroll", function(e) {
9449             *          console.log(e.scrollTop);
9450             *          console.log(e.scrollLeft);
9451             *       });
9452             *     }
9453             *  </script>
9454             */
9455            SCROLL,
9456            /**
9457             * Fires when the scroller dimensions change (e.g. orientation change or resize)
9458             * @name kendo.mobile.ui.Scroller#resize
9459             * @event
9460             * @param {Event} e
9461             */
9462            RESIZE
9463        ],
9464
9465        setOptions: function(options) {
9466            var that = this;
9467            Widget.fn.setOptions.call(that, options);
9468            if (options.pullToRefresh) {
9469                that._initPullToRefresh();
9470            }
9471        },
9472
9473        /**
9474         * Scrolls the container to the top.
9475         */
9476        reset: function() {
9477            this.movable.moveTo({x: 0, y: 0});
9478        },
9479
9480        /**
9481         * Scrolls the container to the specified location
9482         * @param {Number} x The horizontal offset in pixels to scroll to.
9483         * @param {Number} y The vertical offset in pixels to scroll to.
9484         */
9485        scrollTo: function(x, y) {
9486            this.movable.moveTo({x: x, y: y});
9487        },
9488
9489        /**
9490         * Indicate that the pull event is handled (i.e. data from the server has been retrieved).
9491         */
9492        pullHandled: function() {
9493            var that = this;
9494            that.refreshHint.removeClass(REFRESHCLASS);
9495            that.hintContainer.html(that.pullTemplate({}));
9496            that.yinertia.onEnd();
9497            that.xinertia.onEnd();
9498        },
9499
9500        _initPullToRefresh: function() {
9501            var that = this;
9502
9503            that.pullTemplate = kendo.template(that.options.pullTemplate);
9504            that.releaseTemplate = kendo.template(that.options.releaseTemplate);
9505            that.refreshTemplate = kendo.template(that.options.refreshTemplate);
9506
9507            that.scrollElement.prepend('<span class="km-scroller-pull"><span class="km-icon"></span><span class="km-template">' + that.pullTemplate({}) + '</span></span>');
9508            that.refreshHint = that.scrollElement.children().first();
9509            that.hintContainer = that.refreshHint.children(".km-template");
9510
9511            that.pane.y.bind("change", proxy(that._paneChange, that));
9512            that.drag.bind("end", proxy(that._dragEnd, that));
9513        },
9514
9515        _dragEnd: function() {
9516            var that = this;
9517
9518            if(!that.pulled) {
9519                return;
9520            }
9521
9522            that.pulled = false;
9523            that.refreshHint.removeClass(RELEASECLASS).addClass(REFRESHCLASS);
9524            that.hintContainer.html(that.refreshTemplate({}));
9525            that.trigger("pull");
9526            that.yinertia.freeze(that.options.pullOffset / 2);
9527        },
9528
9529        _paneChange: function() {
9530            var that = this;
9531
9532            if (that.movable.y / OUT_OF_BOUNDS_FRICTION > that.options.pullOffset) {
9533                if (!that.pulled) {
9534                    that.pulled = true;
9535                    that.refreshHint.removeClass(REFRESHCLASS).addClass(RELEASECLASS);
9536                    that.hintContainer.html(that.releaseTemplate({}));
9537                }
9538            } else if (that.pulled) {
9539                that.pulled = false;
9540                that.refreshHint.removeClass(RELEASECLASS);
9541                that.hintContainer.html(that.pullTemplate({}));
9542            }
9543        },
9544
9545        _initAxis: function(axis) {
9546            var that = this,
9547            movable = that.movable,
9548            dimension = that.dimensions[axis],
9549            tap = that.tap,
9550
9551            scrollBar = new ScrollBar({
9552                axis: axis,
9553                movable: movable,
9554                dimension: dimension,
9555                container: that.element
9556            }),
9557
9558            inertia = new DragInertia({
9559                axis: axis,
9560                movable: movable,
9561                tap: tap,
9562                drag: that.drag,
9563                dimension: dimension,
9564                elastic: that.options.elastic,
9565                end: function() { scrollBar.hide(); }
9566            });
9567
9568            that[axis + "inertia"] = inertia;
9569
9570            that.pane[axis].bind(CHANGE, function() {
9571                scrollBar.show();
9572            });
9573        }
9574    });
9575
9576    ui.plugin(Scroller);
9577})(jQuery);
9578(function ($, undefined) {
9579    var kendo = window.kendo,
9580        Widget = kendo.ui.Widget,
9581        proxy = $.proxy,
9582        CONTAINER_EMPTY_TEXT = "Drag a column header and drop it here to group by that column",
9583        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"}">' +
9584                '<a href="\\#" class="k-link">' +
9585                    '<span class="k-icon k-arrow-${(data.dir || "asc") == "asc" ? "up" : "down"}-small">(sorted ${(data.dir || "asc") == "asc" ? "ascending": "descending"})</span>' +
9586                    '${data.title ? data.title: data.field}' +
9587                '</a>' +
9588                '<a class="k-button k-button-icon k-button-bare">' +
9589                    '<span class="k-icon k-group-delete"></span>' +
9590                '</a>' +
9591             '</div>',  { useWithBlock:false }),
9592        hint = function(target) {
9593            return $('<div class="k-header k-drag-clue" />')
9594                .css({
9595                    width: target.width(),
9596                    paddingLeft: target.css("paddingLeft"),
9597                    paddingRight: target.css("paddingRight"),
9598                    lineHeight: target.height() + "px",
9599                    paddingTop: target.css("paddingTop"),
9600                    paddingBottom: target.css("paddingBottom")
9601                })
9602                .html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field")))
9603                .prepend('<span class="k-icon k-drag-status k-denied" />');
9604        },
9605        dropCue = $('<div class="k-grouping-dropclue"/>'),
9606        nameSpecialCharRegExp = /(\[|\]|\$|\.|\:|\+)/g;
9607
9608    function dropCueOffsetTop(element) {
9609        return element.position().top + 3;
9610    }
9611
9612    var Groupable = Widget.extend({
9613        init: function(element, options) {
9614            var that = this,
9615                groupContainer,
9616                group = kendo.guid(),
9617                intializePositions = proxy(that._intializePositions, that),
9618                draggable,
9619                dropCuePositions = that._dropCuePositions = [];
9620
9621            Widget.fn.init.call(that, element, options);
9622
9623            draggable = that.options.draggable || new kendo.ui.Draggable(that.element, {
9624                filter: that.options.filter,
9625                hint: hint,
9626                group: group
9627            });
9628
9629            groupContainer = that.groupContainer = $(that.options.groupContainer, that.element)
9630                .kendoDropTarget({
9631                    group: draggable.options.group,
9632                    dragenter: function(e) {
9633                        if (that._canDrag(e.draggable.currentTarget)) {
9634                            e.draggable.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
9635                            dropCue.css({top: dropCueOffsetTop(groupContainer), left: 0}).appendTo(groupContainer);
9636                        }
9637                    },
9638                    dragleave: function(e) {
9639                        e.draggable.hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
9640                        dropCue.remove();
9641                    },
9642                    drop: function(e) {
9643                        var targetElement = e.draggable.currentTarget,
9644                            field = targetElement.attr(kendo.attr("field")),
9645                            title = targetElement.attr(kendo.attr("title")),
9646                            sourceIndicator = that.indicator(field),
9647                            dropCuePositions = that._dropCuePositions,
9648                            lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
9649                            position;
9650
9651                        if (!targetElement.hasClass("k-group-indicator") && !that._canDrag(targetElement)) {
9652                            return;
9653                        }
9654                        if(lastCuePosition) {
9655                            position = that._dropCuePosition(dropCue.offset().left + parseInt(lastCuePosition.element.css("marginLeft"), 10) + parseInt(lastCuePosition.element.css("marginRight"), 10));
9656                            if(position && that._canDrop($(sourceIndicator), position.element, position.left)) {
9657                                if(position.before) {
9658                                    position.element.before(sourceIndicator || that.buildIndicator(field, title));
9659                                } else {
9660                                    position.element.after(sourceIndicator || that.buildIndicator(field, title));
9661                                }
9662
9663                                that._change();
9664                            }
9665                        } else {
9666                            that.groupContainer.append(that.buildIndicator(field, title));
9667                            that._change();
9668                        }
9669                    }
9670                })
9671                .kendoDraggable({
9672                    filter: "div.k-group-indicator",
9673                    hint: hint,
9674                    group: draggable.options.group,
9675                    dragcancel: proxy(that._dragCancel, that),
9676                    dragstart: function(e) {
9677                        var element = e.currentTarget,
9678                            marginLeft = parseInt(element.css("marginLeft"), 10),
9679                            left = element.position().left - marginLeft;
9680
9681                        intializePositions();
9682                        dropCue.css({top: dropCueOffsetTop(groupContainer), left: left}).appendTo(groupContainer);
9683                        this.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
9684                    },
9685                    dragend: function() {
9686                        that._dragEnd(this);
9687                    },
9688                    drag: proxy(that._drag, that)
9689                })
9690                .delegate(".k-button", "click", function(e) {
9691                    e.preventDefault();
9692                    that._removeIndicator($(this).parent());
9693                })
9694                .delegate(".k-link", "click", function(e) {
9695                    var current = $(this).parent(),
9696                        newIndicator = that.buildIndicator(current.attr(kendo.attr("field")), current.attr(kendo.attr("title")), current.attr(kendo.attr("dir")) == "asc" ? "desc" : "asc");
9697
9698                    current.before(newIndicator).remove();
9699                    that._change();
9700                    e.preventDefault();
9701                });
9702
9703            draggable.bind([ "dragend", "dragcancel", "dragstart", "drag" ],
9704            {
9705                dragend: function() {
9706                    that._dragEnd(this);
9707                },
9708                dragcancel: proxy(that._dragCancel, that),
9709                dragstart: function(e) {
9710                    var element, marginRight, left;
9711
9712                    if (!that.options.allowDrag && !that._canDrag(e.currentTarget)) {
9713                        e.preventDefault();
9714                        return;
9715                    }
9716
9717                    intializePositions();
9718                    if(dropCuePositions.length) {
9719                        element = dropCuePositions[dropCuePositions.length - 1].element;
9720                        marginRight = parseInt(element.css("marginRight"), 10);
9721                        left = element.position().left + element.outerWidth() + marginRight;
9722                    } else {
9723                        left = 0;
9724                    }
9725                },
9726                drag: proxy(that._drag, that)
9727            });
9728
9729            that.dataSource = that.options.dataSource;
9730
9731            if(that.dataSource) {
9732                that._refreshHandler = proxy(that.refresh, that);
9733                that.dataSource.bind("change", that._refreshHandler);
9734            }
9735        },
9736
9737        refresh: function() {
9738            var that = this,
9739                dataSource = that.dataSource;
9740
9741            that.groupContainer.empty().append(
9742                $.map(dataSource.group() || [], function(item) {
9743                    var fieldName = item.field.replace(nameSpecialCharRegExp, "\\$1");
9744                    var element = that.element.find(that.options.filter).filter("[" + kendo.attr("field") + "=" + fieldName + "]");
9745                    return that.buildIndicator(item.field, element.attr(kendo.attr("title")), item.dir);
9746                }).join("")
9747            );
9748            that._invalidateGroupContainer();
9749        },
9750
9751        destroy: function() {
9752            var that = this;
9753            if (that.dataSource && that._refreshHandler) {
9754                that.dataSource.unbind("change", that._refreshHandler);
9755            }
9756        },
9757
9758        options: {
9759            name: "Groupable",
9760            filter: "th"
9761        },
9762
9763        indicator: function(field) {
9764            var indicators = $(".k-group-indicator", this.groupContainer);
9765            return $.grep(indicators, function (item)
9766                {
9767                    return $(item).attr(kendo.attr("field")) === field;
9768                })[0];
9769        },
9770        buildIndicator: function(field, title, dir) {
9771            return indicatorTmpl({ field: field, dir: dir, title: title, ns: kendo.ns });
9772        },
9773        descriptors: function() {
9774            var that = this,
9775                indicators = $(".k-group-indicator", that.groupContainer),
9776                aggregates,
9777                names,
9778                field,
9779                idx,
9780                length;
9781
9782            aggregates = that.element.find(that.options.filter).map(function() {
9783                var cell = $(this),
9784                    aggregate = cell.attr(kendo.attr("aggregates")),
9785                    member = cell.attr(kendo.attr("field"));
9786
9787                if (aggregate && aggregate !== "") {
9788                    names = aggregate.split(",");
9789                    aggregate = [];
9790                    for (idx = 0, length = names.length; idx < length; idx++) {
9791                        aggregate.push({ field: member, aggregate: names[idx] });
9792                    }
9793                }
9794                return aggregate;
9795            }).toArray();
9796
9797            return $.map(indicators, function(item) {
9798                item = $(item);
9799                field = item.attr(kendo.attr("field"));
9800
9801                return {
9802                    field: field,
9803                    dir: item.attr(kendo.attr("dir")),
9804                    aggregates: aggregates || []
9805                };
9806            });
9807        },
9808        _removeIndicator: function(indicator) {
9809            var that = this;
9810            indicator.remove();
9811            that._invalidateGroupContainer();
9812            that._change();
9813        },
9814        _change: function() {
9815            var that = this;
9816            if(that.dataSource) {
9817                that.dataSource.group(that.descriptors());
9818            }
9819        },
9820        _dropCuePosition: function(position) {
9821            var dropCuePositions = this._dropCuePositions;
9822            if(!dropCue.is(":visible") || dropCuePositions.length === 0) {
9823                return;
9824            }
9825
9826            position = Math.ceil(position);
9827
9828            var lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
9829                right = lastCuePosition.right,
9830                marginLeft = parseInt(lastCuePosition.element.css("marginLeft"), 10),
9831                marginRight = parseInt(lastCuePosition.element.css("marginRight"), 10);
9832
9833            if(position >= right) {
9834                position = {
9835                    left: lastCuePosition.element.position().left + lastCuePosition.element.outerWidth() + marginRight,
9836                    element: lastCuePosition.element,
9837                    before: false
9838                };
9839            } else {
9840                position = $.grep(dropCuePositions, function(item) {
9841                    return item.left <= position && position <= item.right;
9842                })[0];
9843
9844                if(position) {
9845                    position = {
9846                        left: position.element.position().left - marginLeft,
9847                        element: position.element,
9848                        before: true
9849                    };
9850                }
9851            }
9852
9853            return position;
9854        },
9855        _drag: function(event) {
9856            var location = kendo.touchLocation(event),
9857                position = this._dropCuePosition(location.x);
9858            if(position) {
9859                dropCue.css({ left: position.left });
9860            }
9861        },
9862        _canDrag: function(element) {
9863            return element.attr(kendo.attr("groupable")) != "false" && !this.indicator(element.attr(kendo.attr("field")));
9864        },
9865        _canDrop: function(source, target, position) {
9866            var next = source.next();
9867            return source[0] !== target[0] && (!next[0] || target[0] !== next[0] || position > next.position().left);
9868        },
9869        _dragEnd: function(draggable) {
9870            var that = this,
9871                field = draggable.currentTarget.attr(kendo.attr("field")),
9872                sourceIndicator = that.indicator(field);
9873
9874            if (draggable !== that.options.draggable && !draggable.dropped && sourceIndicator) {
9875                that._removeIndicator($(sourceIndicator));
9876            }
9877
9878            that._dragCancel();
9879        },
9880        _dragCancel: function() {
9881            dropCue.remove();
9882            this._dropCuePositions = [];
9883        },
9884        _intializePositions: function() {
9885            var that = this,
9886                indicators = $(".k-group-indicator", that.groupContainer),
9887                left;
9888            that._dropCuePositions = $.map(indicators, function(item) {
9889                item = $(item);
9890                left = item.offset().left;
9891                return {
9892                    left: parseInt(left, 10),
9893                    right: parseInt(left + item.outerWidth(), 10),
9894                    element: item
9895                };
9896            });
9897        },
9898        _invalidateGroupContainer: function() {
9899            var groupContainer = this.groupContainer;
9900            if(groupContainer.is(":empty")) {
9901                groupContainer.html(CONTAINER_EMPTY_TEXT);
9902            }
9903        }
9904    });
9905
9906    kendo.ui.plugin(Groupable);
9907
9908})(jQuery);
9909(function ($, undefined) {
9910    var kendo = window.kendo,
9911        Widget = kendo.ui.Widget,
9912        CHANGE =  "change",
9913        KREORDERABLE = "k-reorderable";
9914
9915    function toggleHintClass(hint, denied) {
9916        hint = $(hint);
9917
9918        if (denied) {
9919            hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
9920        } else {
9921            hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
9922        }
9923    }
9924
9925    var Reorderable = Widget.extend({
9926        init: function(element, options) {
9927            var that = this,
9928                draggable,
9929                group = kendo.guid() + "-reorderable";
9930
9931            Widget.fn.init.call(that, element, options);
9932
9933            element = that.element.addClass(KREORDERABLE);
9934            options = that.options;
9935            draggable = options.draggable || new kendo.ui.Draggable(element, {
9936                group: group,
9937                filter: options.filter,
9938                hint: options.hint
9939            });
9940
9941            that.reorderDropCue = $('<div class="k-reorder-cue"><div class="k-icon k-arrow-down"></div><div class="k-icon k-arrow-up"></div></div>');
9942
9943            element.find(draggable.options.filter).kendoDropTarget({
9944                group: draggable.options.group,
9945                dragenter: function(e) {
9946                    if (!that._draggable) {
9947                        return;
9948                    }
9949
9950                    var dropTarget = this.element,
9951                        same = dropTarget[0] === that._draggable[0];
9952
9953                    toggleHintClass(e.draggable.hint, same);
9954                    if (!same) {
9955                        that.reorderDropCue.css({
9956                             height: dropTarget.outerHeight(),
9957                             top: element.offset().top,
9958                             left: dropTarget.offset().left + (dropTarget.index() > that._draggable.index() ? dropTarget.outerWidth() : 0)
9959                        })
9960                        .appendTo(document.body);
9961                    }
9962                },
9963                dragleave: function(e) {
9964                    toggleHintClass(e.draggable.hint, true);
9965                    that.reorderDropCue.remove();
9966                },
9967                drop: function() {
9968                    if (!that._draggable) {
9969                        return;
9970                    }
9971
9972                    var draggableElement = that._draggable[0],
9973                        dropTarget = this.element[0],
9974                        container;
9975
9976                    if (draggableElement !== dropTarget) {
9977                        container = element.find(draggable.options.filter);
9978                        that.trigger(CHANGE, {
9979                            element: that._draggable,
9980                            oldIndex: container.index(draggableElement),
9981                            newIndex: container.index(dropTarget)
9982                        });
9983                    }
9984                }
9985            });
9986
9987            draggable.bind([ "dragcancel", "dragend", "dragstart" ],
9988                {
9989                    dragcancel: function() {
9990                        that.reorderDropCue.remove();
9991                        that._draggable = null;
9992                    },
9993                    dragend: function() {
9994                        that.reorderDropCue.remove();
9995                        that._draggable = null;
9996                    },
9997                    dragstart: function(e) {
9998                        that._draggable = e.currentTarget;
9999                    }
10000                }
10001            );
10002        },
10003
10004        options: {
10005            name: "Reorderable",
10006            filter: "*"
10007        },
10008
10009        events: [
10010            CHANGE
10011        ]
10012
10013    });
10014
10015    kendo.ui.plugin(Reorderable);
10016
10017})(jQuery);
10018(function($, undefined) {
10019    var kendo = window.kendo,
10020        ui = kendo.ui,
10021        Widget = ui.Widget,
10022        proxy = $.proxy,
10023        isFunction = $.isFunction,
10024        extend = $.extend,
10025        HORIZONTAL = "horizontal",
10026        VERTICAL = "vertical",
10027        START = "start",
10028        RESIZE = "resize",
10029        RESIZEEND = "resizeend";
10030
10031    var Resizable = Widget.extend({
10032        init: function(element, options) {
10033            var that = this;
10034
10035            Widget.fn.init.call(that, element, options);
10036
10037            that.orientation = that.options.orientation.toLowerCase() != VERTICAL ? HORIZONTAL : VERTICAL;
10038            that._positionMouse = that.orientation == HORIZONTAL ? "pageX" : "pageY";
10039            that._position = that.orientation == HORIZONTAL ? "left" : "top";
10040            that._sizingDom = that.orientation == HORIZONTAL ? "outerWidth" : "outerHeight";
10041
10042            new ui.Draggable(element, {
10043                distance: 0,
10044                filter: options.handle,
10045                drag: proxy(that._resize, that),
10046                dragstart: proxy(that._start, that),
10047                dragend: proxy(that._stop, that)
10048            });
10049        },
10050
10051        events: [
10052            RESIZE,
10053            RESIZEEND,
10054            START
10055        ],
10056
10057        options: {
10058            name: "Resizable",
10059            orientation: HORIZONTAL
10060        },
10061        _max: function(e) {
10062            var that = this,
10063                hintSize = that.hint ? that.hint[that._sizingDom]() : 0,
10064                size = that.options.max;
10065
10066            return isFunction(size) ? size(e) : size !== undefined ? (that._initialElementPosition + size) - hintSize : size;
10067        },
10068        _min: function(e) {
10069            var that = this,
10070                size = that.options.min;
10071
10072            return isFunction(size) ? size(e) : size !== undefined ? that._initialElementPosition + size : size;
10073        },
10074        _start: function(e) {
10075            var that = this,
10076                hint = that.options.hint,
10077                el = $(e.currentTarget);
10078
10079            that._initialMousePosition = e[that._positionMouse];
10080            that._initialElementPosition = el.position()[that._position];
10081
10082            if (hint) {
10083                that.hint = isFunction(hint) ? $(hint(el)) : hint;
10084
10085                that.hint.css({
10086                    position: "absolute"
10087                })
10088                .css(that._position, that._initialElementPosition)
10089                .appendTo(that.element);
10090            }
10091
10092            that.trigger(START, e);
10093
10094            that._maxPosition = that._max(e);
10095            that._minPosition = that._min(e);
10096
10097            $(document.body).css("cursor", el.css("cursor"));
10098        },
10099        _resize: function(e) {
10100            var that = this,
10101                handle = $(e.currentTarget),
10102                maxPosition = that._maxPosition,
10103                minPosition = that._minPosition,
10104                currentPosition = that._initialElementPosition + (e[that._positionMouse] - that._initialMousePosition),
10105                position;
10106
10107            position = minPosition !== undefined ? Math.max(minPosition, currentPosition) : currentPosition;
10108            that.position = position =  maxPosition !== undefined ? Math.min(maxPosition, position) : position;
10109
10110            if(that.hint) {
10111                that.hint.toggleClass(that.options.invalidClass || "", position == maxPosition || position == minPosition)
10112                         .css(that._position, position);
10113            }
10114
10115            that.trigger(RESIZE, extend(e, { position: position }));
10116        },
10117        _stop: function(e) {
10118            var that = this;
10119
10120            if(that.hint) {
10121                that.hint.remove();
10122            }
10123
10124            that.trigger(RESIZEEND, extend(e, { position: that.position }));
10125            $(document.body).css("cursor", "");
10126        }
10127    });
10128
10129    kendo.ui.plugin(Resizable);
10130
10131})(jQuery);
10132(function($, undefined) {
10133    var kendo = window.kendo,
10134        proxy = $.proxy,
10135        DIR = "dir",
10136        ASC = "asc",
10137        SINGLE = "single",
10138        FIELD = "field",
10139        DESC = "desc",
10140        TLINK = ".k-link",
10141        Widget = kendo.ui.Widget;
10142
10143    var Sortable = Widget.extend({
10144        init: function(element, options) {
10145            var that = this, link;
10146
10147            Widget.fn.init.call(that, element, options);
10148
10149            that.dataSource = that.options.dataSource.bind("change", proxy(that.refresh, that));
10150            link = that.element.find(TLINK);
10151
10152            if (!link[0]) {
10153                link = that.element.wrapInner('<a class="k-link" href="#"/>').find(TLINK);
10154            }
10155
10156            that.link = link;
10157            that.element.click(proxy(that._click, that));
10158        },
10159
10160        options: {
10161            name: "Sortable",
10162            mode: SINGLE,
10163            allowUnsort: true
10164        },
10165
10166        refresh: function() {
10167            var that = this,
10168                sort = that.dataSource.sort() || [],
10169                idx,
10170                length,
10171                descriptor,
10172                dir,
10173                element = that.element,
10174                field = element.data(FIELD);
10175
10176            element.removeData(DIR);
10177
10178            for (idx = 0, length = sort.length; idx < length; idx++) {
10179               descriptor = sort[idx];
10180
10181               if (field == descriptor.field) {
10182                   element.data(DIR, descriptor.dir);
10183               }
10184            }
10185
10186            dir = element.data(DIR);
10187
10188            element.find(".k-arrow-up,.k-arrow-down").remove();
10189
10190            if (dir === ASC) {
10191                $('<span class="k-icon k-arrow-up" />').appendTo(that.link);
10192            } else if (dir === DESC) {
10193                $('<span class="k-icon k-arrow-down" />').appendTo(that.link);
10194            }
10195        },
10196
10197        _click: function(e) {
10198            var that = this,
10199                element = that.element,
10200                field = element.data(FIELD),
10201                dir = element.data(DIR),
10202                options = that.options,
10203                sort = that.dataSource.sort() || [],
10204                idx,
10205                length;
10206
10207            if (dir === ASC) {
10208                dir = DESC;
10209            } else if (dir === DESC && options.allowUnsort) {
10210                dir = undefined;
10211            } else {
10212                dir = ASC;
10213            }
10214
10215            if (options.mode === SINGLE) {
10216                sort = [ { field: field, dir: dir } ];
10217            } else if (options.mode === "multiple") {
10218                for (idx = 0, length = sort.length; idx < length; idx++) {
10219                    if (sort[idx].field === field) {
10220                        sort.splice(idx, 1);
10221                        break;
10222                    }
10223                }
10224                sort.push({ field: field, dir: dir });
10225            }
10226
10227            e.preventDefault();
10228
10229            that.dataSource.sort(sort);
10230        }
10231    });
10232
10233    kendo.ui.plugin(Sortable);
10234})(jQuery);
10235(function ($, undefined) {
10236    var kendo = window.kendo,
10237        touch = kendo.support.touch,
10238        Widget = kendo.ui.Widget,
10239        proxy = $.proxy,
10240        MOUSEUP = touch? "touchend" : "mouseup",
10241        MOUSEDOWN = touch? "touchstart" : "mousedown",
10242        MOUSEMOVE = touch? "touchmove" : "mousemove",
10243        SELECTED = "k-state-selected",
10244        ACTIVE = "k-state-selecting",
10245        SELECTABLE = "k-selectable",
10246        SELECTSTART = "selectstart",
10247        DOCUMENT = $(document),
10248        CHANGE = "change",
10249        UNSELECTING = "k-state-unselecting";
10250
10251    var Selectable = Widget.extend({
10252        init: function(element, options) {
10253            var that = this;
10254
10255            Widget.fn.init.call(that, element, options);
10256
10257            that._marquee = $("<div class='k-marquee'></div>");
10258            that._lastActive = null;
10259
10260            that._moveDelegate = proxy(that._move, that);
10261            that._upDelegate = proxy(that._up, that);
10262
10263            that.element.addClass(SELECTABLE);
10264            that.element.delegate("." + SELECTABLE + " " + that.options.filter, MOUSEDOWN, proxy(that._down, that));
10265        },
10266
10267        events: [CHANGE],
10268
10269        options: {
10270            name: "Selectable",
10271            filter: ">*",
10272            multiple: false
10273        },
10274        _collide: function(element, marqueePos) {
10275            var pos = element.offset(),
10276                selectee = {
10277                    left: pos.left,
10278                    top: pos.top,
10279                    right: pos.left + element.outerWidth(),
10280                    bottom: pos.top + element.outerHeight()
10281                };
10282
10283            return (!(selectee.left > marqueePos.right ||
10284                selectee.right < marqueePos.left ||
10285                selectee.top > marqueePos.bottom ||
10286                selectee.bottom < marqueePos.top));
10287        },
10288        _position: function(event) {
10289            var pos = this._originalPosition,
10290                left = pos.x,
10291                top = pos.y,
10292                right = event.pageX,
10293                bottom = event.pageY,
10294                tmp;
10295
10296            if (left > right) {
10297                tmp = right;
10298                right = left;
10299                left = tmp;
10300            }
10301
10302            if (top > bottom) {
10303                tmp = bottom;
10304                bottom = top;
10305                top = tmp;
10306            }
10307
10308            return {
10309                top: top,
10310                right: right,
10311                left: left,
10312                bottom: bottom
10313            };
10314        },
10315        _down: function (event) {
10316            var that = this,
10317                selected,
10318                ctrlKey = event.ctrlKey,
10319                shiftKey = event.shiftKey,
10320                single = !that.options.multiple;
10321
10322            that._downTarget = $(event.currentTarget);
10323            that._shiftPressed = shiftKey;
10324
10325            if (that._downTarget.closest("." + SELECTABLE)[0] !== that.element[0]) {
10326                return;
10327            }
10328
10329            DOCUMENT
10330                .unbind(MOUSEUP, that._upDelegate) // more cancel friendly
10331                .bind(MOUSEUP, that._upDelegate);
10332
10333            that._originalPosition = {
10334                x: event.pageX,
10335                y: event.pageY
10336            };
10337
10338            if(!single && $(event.target).is(":not(:input)")) {
10339                DOCUMENT
10340                    .unbind(MOUSEMOVE, that._moveDelegate)
10341                    .bind(MOUSEMOVE, that._moveDelegate)
10342                    .unbind(SELECTSTART, false)
10343                    .bind(SELECTSTART, false);
10344
10345                if (!kendo.support.touch) {
10346                    event.preventDefault();
10347                }
10348            }
10349
10350            if (!single) {
10351                $("body").append(that._marquee);
10352                that._marquee.css({
10353                    left: event.clientX + 1,
10354                    top: event.clientY + 1,
10355                    width: 0,
10356                    height: 0
10357                });
10358            }
10359
10360            selected = that._downTarget.hasClass(SELECTED);
10361            if(single || !(ctrlKey || shiftKey)) {
10362                that.element
10363                .find(that.options.filter + "." + SELECTED)
10364                .not(that._downTarget)
10365                .removeClass(SELECTED);
10366            }
10367            if(ctrlKey) {
10368                that._lastActive = that._downTarget;
10369            }
10370
10371            if(selected && (ctrlKey || shiftKey)) {
10372                that._downTarget.addClass(SELECTED);
10373                if(!shiftKey) {
10374                    that._downTarget.addClass(UNSELECTING);
10375                }
10376            }
10377            else {
10378                if (!(kendo.support.touch && single)) {
10379                    that._downTarget.addClass(ACTIVE);
10380                }
10381            }
10382        },
10383        _move: function (event) {
10384            var that = this,
10385                pos = that._position(event),
10386                ctrlKey = event.ctrlKey,
10387                selectee, collide;
10388
10389                that._marquee.css({
10390                    left: pos.left,
10391                    top: pos.top,
10392                    width: pos.right - pos.left,
10393                    height: pos.bottom - pos.top
10394                });
10395
10396            that.element.find(that.options.filter).each(function () {
10397                selectee = $(this);
10398                collide = that._collide(selectee, pos);
10399
10400                if (collide) {
10401                    if(selectee.hasClass(SELECTED)) {
10402                        if(that._downTarget[0] !== selectee[0] && ctrlKey) {
10403                            selectee
10404                                .removeClass(SELECTED)
10405                                .addClass(UNSELECTING);
10406                        }
10407                    } else if (!selectee.hasClass(ACTIVE) && !selectee.hasClass(UNSELECTING)) {
10408                        selectee.addClass(ACTIVE);
10409                    }
10410                }
10411                else {
10412                    if (selectee.hasClass(ACTIVE)) {
10413                        selectee.removeClass(ACTIVE);
10414                    }
10415                    else if(ctrlKey && selectee.hasClass(UNSELECTING)) {
10416                        selectee
10417                            .removeClass(UNSELECTING)
10418                            .addClass(SELECTED);
10419                    }
10420                }
10421            });
10422        },
10423        _up: function (event) {
10424            var that = this,
10425                options = that.options,
10426                single = !options.multiple;
10427
10428            DOCUMENT
10429                .unbind(SELECTSTART, false)
10430                .unbind(MOUSEMOVE, that._moveDelegate)
10431                .unbind(MOUSEUP, that._upDelegate);
10432
10433            if (!single) {
10434                that._marquee.remove();
10435            }
10436
10437            if (kendo.support.touch && single) {
10438                that._downTarget.addClass(ACTIVE);
10439            }
10440
10441            if(!single && that._shiftPressed === true) {
10442                that.selectRange(that._firstSelectee(), that._downTarget);
10443            }
10444            else {
10445                that.element
10446                    .find(options.filter + "." + UNSELECTING)
10447                    .removeClass(UNSELECTING)
10448                    .removeClass(SELECTED);
10449
10450                that.value(that.element.find(options.filter + "." + ACTIVE));
10451            }
10452            if(!that._shiftPressed) {
10453                that._lastActive = that._downTarget;
10454            }
10455            that._downTarget = null;
10456            that._shiftPressed = false;
10457        },
10458        value: function(val) {
10459            var that = this,
10460                selectElement = proxy(that._selectElement, that);
10461
10462            if(val) {
10463                val.each(function() {
10464                    selectElement(this);
10465                });
10466
10467                that.trigger(CHANGE, {});
10468                return;
10469            }
10470
10471            return that.element
10472                    .find(that.options.filter + "." + SELECTED);
10473        },
10474        _firstSelectee: function() {
10475            var that = this, selected;
10476            if(that._lastActive !== null) {
10477                return that._lastActive;
10478            }
10479
10480            selected = that.value();
10481            return selected.length > 0 ?
10482                    selected[0] :
10483                    that.element.find(that.options.filter);
10484        },
10485        _selectElement: function(el) {
10486            var selecee = $(el),
10487                isPrevented = this.trigger("select", { element: el });
10488
10489            selecee.removeClass(ACTIVE);
10490            if(!isPrevented) {
10491                selecee.addClass(SELECTED);
10492            }
10493        },
10494        clear: function() {
10495            var that = this;
10496            that.element
10497                .find(that.options.filter + "." + SELECTED)
10498                .removeClass(SELECTED);
10499        },
10500        selectRange: function(start, end) {
10501            var that = this,
10502                found = false,
10503                selectElement = proxy(that._selectElement, that),
10504                selectee;
10505            start = $(start)[0];
10506            end = $(end)[0];
10507            that.element.find(that.options.filter).each(function () {
10508                selectee = $(this);
10509                if(found) {
10510                    selectElement(this);
10511                    found = this !== end;
10512                }
10513                else if(this === start) {
10514                    found = start !== end;
10515                    selectElement(this);
10516                }
10517                else if(this === end) {
10518                    var tmp = start;
10519                    start = end;
10520                    end = tmp;
10521                    found = true;
10522                    selectElement(this);
10523                }
10524                else {
10525                    selectee.removeClass(SELECTED);
10526                }
10527            });
10528            that.trigger(CHANGE, {});
10529        }
10530    });
10531
10532    kendo.ui.plugin(Selectable);
10533
10534})(jQuery);
10535(function($, undefined) {
10536    var kendo = window.kendo,
10537        ui = kendo.ui,
10538        Widget = ui.Widget,
10539        proxy = $.proxy;
10540
10541    function button(template, idx, text, numeric) {
10542        return template( {
10543            idx: idx,
10544            text: text,
10545            ns: kendo.ns,
10546            numeric: numeric
10547        });
10548    }
10549
10550    var Pager = Widget.extend( {
10551        init: function(element, options) {
10552            var that = this;
10553
10554            Widget.fn.init.call(that, element, options);
10555
10556            options = that.options;
10557            that.dataSource = options.dataSource;
10558            that.linkTemplate = kendo.template(that.options.linkTemplate);
10559            that.selectTemplate = kendo.template(that.options.selectTemplate);
10560
10561            that._refreshHandler = proxy(that.refresh, that);
10562
10563            that.dataSource.bind("change", that._refreshHandler);
10564
10565            that.list = $('<ul class="k-pager k-reset k-numeric" />').appendTo(that.element);//.html(that.selectTemplate({ text: 1 }));
10566            that._clickHandler = proxy(that._click, that);
10567
10568            that.element.delegate("a", "click", that._clickHandler);
10569
10570            that.refresh();
10571        },
10572
10573        destroy: function() {
10574            var that = this;
10575
10576            that.element.undelegate("a", "click", that._clickHandler);
10577            that.dataSource.unbind("change", that._refreshHandler);
10578        },
10579
10580        events: [
10581            "change"
10582        ],
10583
10584        options: {
10585            name: "Pager",
10586            selectTemplate: '<li><span class="k-state-active">#=text#</span></li>',
10587            linkTemplate: '<li><a href="\\#" class="k-link" data-#=ns#page="#=idx#">#=text#</a></li>',
10588            buttonCount: 10
10589        },
10590
10591        refresh: function() {
10592            var that = this,
10593                idx,
10594                end,
10595                start = 1,
10596                html = "",
10597                reminder,
10598                page = that.page(),
10599                totalPages = that.totalPages(),
10600                linkTemplate = that.linkTemplate,
10601                buttonCount = that.options.buttonCount;
10602
10603            if (page > buttonCount) {
10604                reminder = (page % buttonCount);
10605
10606                start = (reminder === 0) ? (page - buttonCount) + 1 : (page - reminder) + 1;
10607            }
10608
10609            end = Math.min((start + buttonCount) - 1, totalPages);
10610
10611            if(start > 1) {
10612                html += button(linkTemplate, start - 1, "...", false);
10613            }
10614
10615            for(idx = start; idx <= end; idx++) {
10616                html += button(idx == page ? that.selectTemplate : linkTemplate, idx, idx, true);
10617            }
10618
10619            if(end < totalPages) {
10620                html += button(linkTemplate, idx, "...", false);
10621            }
10622
10623            if (html === "") {
10624                html = that.selectTemplate({ text: 1 });
10625            }
10626
10627            that.list.empty().append(html);
10628        },
10629
10630        _click: function(e) {
10631            var page = $(e.currentTarget).attr(kendo.attr("page"));
10632            e.preventDefault();
10633
10634            this.dataSource.page(page);
10635
10636            this.trigger("change", { index: page });
10637        },
10638
10639        totalPages: function() {
10640            return Math.ceil((this.dataSource.total() || 0) / this.pageSize());
10641        },
10642
10643        pageSize: function() {
10644            return this.dataSource.pageSize() || this.dataSource.total();
10645        },
10646
10647        page: function() {
10648            return this.dataSource.page() || 1;
10649        }
10650    });
10651
10652    ui.plugin(Pager);
10653})(jQuery);
10654(function($, undefined) {
10655    var kendo = window.kendo,
10656        ui = kendo.ui,
10657        Widget = ui.Widget,
10658        support = kendo.support,
10659        touch = support.touch,
10660        getOffset = kendo.getOffset,
10661        appendingToBodyTriggersResize = $.browser.msie && $.browser.version < 9,
10662        OPEN = "open",
10663        CLOSE = "close",
10664        CLOSED = "closed",
10665        CENTER = "center",
10666        LEFT = "left",
10667        RIGHT = "right",
10668        TOP = "top",
10669        BOTTOM = "bottom",
10670        ABSOLUTE = "absolute",
10671        HIDDEN = "hidden",
10672        BODY = "body",
10673        LOCATION = "location",
10674        POSITION = "position",
10675        VISIBLE = "visible",
10676        FITTED = "fitted",
10677        EFFECTS = "effects",
10678        ACTIVE = "k-state-active",
10679        ACTIVEBORDER = "k-state-border",
10680        ACTIVECHILDREN = ".k-picker-wrap, .k-dropdown-wrap, .k-link",
10681        MOUSEDOWN = touch ? "touchstart" : "mousedown",
10682        DOCUMENT= $(document),
10683        WINDOW = $(window),
10684        DOCUMENT_ELEMENT = $(document.documentElement),
10685        RESIZE_SCROLL = "resize scroll",
10686        cssPrefix = support.transitions.css,
10687        TRANSFORM = cssPrefix + "transform",
10688        extend = $.extend,
10689        styles = ["font-family",
10690                   "font-size",
10691                   "font-stretch",
10692                   "font-style",
10693                   "font-weight",
10694                   "line-height"];
10695
10696    function contains(container, target) {
10697        return container === target || $.contains(container, target);
10698    }
10699
10700    var Popup = Widget.extend({
10701        init: function(element, options) {
10702            var that = this;
10703
10704            Widget.fn.init.call(that, element, options);
10705
10706            element = that.element;
10707            options = that.options;
10708
10709            that.collisions = options.collision ? options.collision.split(" ") : [];
10710
10711            if (that.collisions.length === 1) {
10712                that.collisions.push(that.collisions[0]);
10713            }
10714
10715            that.element.hide()
10716                .addClass("k-popup k-group k-reset")
10717                .css({ position : ABSOLUTE })
10718                .appendTo($(options.appendTo))
10719                .bind("mouseenter mouseleave", function(e) {
10720                    that._hovered = e.type === "mouseenter";
10721                });
10722
10723            that.wrapper = $();
10724
10725            if (options.animation === false) {
10726                options.animation = { open: { show: true, effects: {} }, close: { hide: true, effects: {} } };
10727            }
10728
10729            extend(options.animation.open, {
10730                complete: function() {
10731                    that.wrapper.css({ overflow: VISIBLE }); // Forcing refresh causes flickering in mobile.
10732                }
10733            });
10734
10735            extend(options.animation.close, {
10736                complete: function() {
10737                    that.wrapper.hide();
10738
10739                    var location = that.wrapper.data(LOCATION),
10740                        anchor = $(options.anchor),
10741                        direction, dirClass;
10742
10743                    if (location) {
10744                        that.wrapper.css(location);
10745                    }
10746
10747                    if (options.anchor != BODY) {
10748                        direction = anchor.hasClass(ACTIVEBORDER + "-down") ? "down" : "up";
10749                        dirClass = ACTIVEBORDER + "-" + direction;
10750
10751                        anchor
10752                            .removeClass(dirClass)
10753                            .children(ACTIVECHILDREN)
10754                            .removeClass(ACTIVE)
10755                            .removeClass(dirClass);
10756
10757                        element.removeClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
10758                    }
10759
10760                    that._closing = false;
10761                    that.trigger(CLOSED);
10762                }
10763            });
10764
10765            that._mousedownProxy = function(e) {
10766                that._mousedown(e);
10767            };
10768
10769            that._currentWidth = DOCUMENT.width();
10770
10771            that._resizeProxy = function(e) {
10772                that._resize(e);
10773            };
10774
10775            if (options.toggleTarget) {
10776                $(options.toggleTarget).bind(options.toggleEvent, $.proxy(that.toggle, that));
10777            }
10778        },
10779
10780        events: [
10781            OPEN,
10782            CLOSE,
10783            CLOSED
10784        ],
10785
10786        options: {
10787            name: "Popup",
10788            toggleEvent: "click",
10789            origin: BOTTOM + " " + LEFT,
10790            position: TOP + " " + LEFT,
10791            anchor: BODY,
10792            appendTo: BODY,
10793            collision: "flip fit",
10794            animation: {
10795                open: {
10796                    effects: "slideIn:down",
10797                    transition: true,
10798                    duration: 200,
10799                    show: true
10800                },
10801                close: { // if close animation effects are defined, they will be used instead of open.reverse
10802                    duration: 100,
10803                    show: false,
10804                    hide: true
10805                }
10806            }
10807        },
10808
10809        open: function(x, y) {
10810            var that = this,
10811                fixed = { isFixed: !isNaN(parseInt(y,10)), x: x, y: y },
10812                element = that.element,
10813                options = that.options,
10814                direction = "down",
10815                animation, wrapper,
10816                anchor = $(options.anchor),
10817                style,
10818                idx;
10819
10820            if (!that.visible()) {
10821                for (idx = 0; idx < styles.length; idx++) {
10822                    style = styles[idx];
10823                    element.css(style, anchor.css(style));
10824                }
10825
10826                if (element.data("animating") || that.trigger(OPEN)) {
10827                    return;
10828                }
10829
10830                DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy)
10831                                .bind(MOUSEDOWN, that._mousedownProxy);
10832                if (!touch) {
10833                    WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy)
10834                          .bind(RESIZE_SCROLL, that._resizeProxy);
10835                }
10836
10837                that.wrapper = wrapper = kendo.wrap(element)
10838                                        .css({
10839                                            overflow: HIDDEN,
10840                                            display: "block",
10841                                            position: ABSOLUTE
10842                                        });
10843
10844                if (support.mobileOS.android) {
10845                    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.
10846                }
10847
10848                wrapper.css(POSITION);
10849
10850                if (options.appendTo == BODY) {
10851                    wrapper.css(TOP, "-10000px");
10852                }
10853
10854                animation = extend(true, {}, options.animation.open);
10855                animation.effects = kendo.parseEffects(animation.effects, that._position(window, fixed));
10856                direction = animation.effects.slideIn ? animation.effects.slideIn.direction : direction;
10857
10858                if (options.anchor != BODY) {
10859                    var dirClass = ACTIVEBORDER + "-" + direction;
10860
10861                    element.addClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
10862
10863                    anchor
10864                        .addClass(dirClass)
10865                        .children(ACTIVECHILDREN)
10866                        .addClass(ACTIVE)
10867                        .addClass(dirClass);
10868                }
10869
10870                element.data(EFFECTS, animation.effects)
10871                       .kendoStop(true)
10872                       .kendoAnimate(animation);
10873            }
10874        },
10875
10876        toggle: function() {
10877            var that = this;
10878
10879            that[that.visible() ? CLOSE : OPEN]();
10880        },
10881
10882        visible: function() {
10883            return this.element.is(":" + VISIBLE);
10884        },
10885
10886        close: function() {
10887            var that = this,
10888                options = that.options,
10889                animation, openEffects, closeEffects;
10890
10891            if (that.visible()) {
10892                if (that._closing || that.trigger(CLOSE)) {
10893                    return;
10894                }
10895
10896                DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy);
10897                WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);
10898
10899                animation = extend(true, {}, options.animation.close);
10900                openEffects = that.element.data(EFFECTS);
10901                closeEffects = animation.effects;
10902
10903                that.wrapper = kendo.wrap(that.element).css({ overflow: HIDDEN });
10904
10905                if (!closeEffects && !kendo.size(closeEffects) && openEffects && kendo.size(openEffects)) {
10906                    animation.effects = openEffects;
10907                    animation.reverse = true;
10908                }
10909
10910                that._closing = true;
10911
10912                that.element.kendoStop(true).kendoAnimate(animation);
10913            }
10914        },
10915
10916        _resize: function(e) {
10917            var that = this;
10918
10919            if (appendingToBodyTriggersResize) {
10920                var width = DOCUMENT.width();
10921                if (width == that._currentWidth) {
10922                    return;
10923                }
10924                that._currentWidth = width;
10925            }
10926
10927            if (!that._hovered) {
10928                that.close();
10929            }
10930        },
10931
10932        _mousedown: function(e) {
10933            var that = this,
10934                container = that.element[0],
10935                options = that.options,
10936                anchor = $(options.anchor)[0],
10937                toggleTarget = options.toggleTarget,
10938                target = kendo.eventTarget(e),
10939                popup = $(target).closest(".k-popup")[0];
10940
10941            if (popup && popup !== that.element[0] ){
10942                return;
10943            }
10944
10945            if (!contains(container, target) && !contains(anchor, target) && !(toggleTarget && contains($(toggleTarget)[0], target))) {
10946                that.close();
10947            }
10948        },
10949
10950        _fit: function(position, size, viewPortSize) {
10951            var output = 0;
10952
10953            if (position + size > viewPortSize) {
10954                output = viewPortSize - (position + size);
10955            }
10956
10957            if (position < 0) {
10958                output = -position;
10959            }
10960
10961            return output;
10962        },
10963
10964        _flip: function(offset, size, anchorSize, viewPortSize, origin, position, boxSize) {
10965            var output = 0;
10966                boxSize = boxSize || size;
10967
10968            if (position !== origin && position !== CENTER && origin !== CENTER) {
10969                if (offset + boxSize > viewPortSize) {
10970                    output += -(anchorSize + size);
10971                }
10972
10973                if (offset + output < 0) {
10974                    output += anchorSize + size;
10975                }
10976            }
10977            return output;
10978        },
10979
10980        _position: function(viewport, fixed) {
10981            viewport = $(viewport);
10982
10983            var that = this,
10984                element = that.element,
10985                wrapper = that.wrapper,
10986                options = that.options,
10987                anchor = $(options.anchor),
10988                origins = options.origin.toLowerCase().split(" "),
10989                positions = options.position.toLowerCase().split(" "),
10990                collisions = that.collisions,
10991                zoomLevel = support.zoomLevel(),
10992                zIndex = 10002;
10993
10994            var siblingContainer = anchor.parents().filter(wrapper.siblings());
10995
10996            if (siblingContainer[0]) {
10997                var parentZIndex = Number($(siblingContainer).css("zIndex"));
10998                if (parentZIndex) {
10999                    zIndex = parentZIndex + 1;
11000                }
11001            }
11002
11003            wrapper.css("zIndex", zIndex);
11004
11005            if (fixed && fixed.isFixed) {
11006                wrapper.css({ left: fixed.x, top: fixed.y });
11007            } else {
11008                wrapper.css(that._align(origins, positions));
11009            }
11010
11011            var pos = getOffset(wrapper, POSITION),
11012                offset = getOffset(wrapper),
11013                anchorParent = anchor.offsetParent().parent(".k-animation-container"); // If the parent is positioned, get the current positions
11014
11015            if (anchorParent.length && anchorParent.data(FITTED)) {
11016                pos = getOffset(wrapper, POSITION);
11017                offset = getOffset(wrapper);
11018            }
11019
11020            offset = {
11021                top: offset.top - (window.pageYOffset || document.documentElement.scrollTop || 0),
11022                left: offset.left - (window.pageXOffset || document.documentElement.scrollLeft || 0)
11023            };
11024
11025            if (!that.wrapper.data(LOCATION)) { // Needed to reset the popup location after every closure - fixes the resize bugs.
11026                wrapper.data(LOCATION, extend({}, pos));
11027            }
11028
11029            var offsets = extend({}, offset),
11030                location = extend({}, pos);
11031
11032            if (collisions[0] === "fit") {
11033                location.top += that._fit(offsets.top, wrapper.outerHeight(), viewport.height() / zoomLevel);
11034            }
11035
11036            if (collisions[1] === "fit") {
11037                location.left += that._fit(offsets.left, wrapper.outerWidth(), viewport.width() / zoomLevel);
11038            }
11039
11040            if (location.left != pos.left || location.top != pos.top) {
11041                wrapper.data(FITTED, true);
11042            } else {
11043                wrapper.removeData(FITTED);
11044            }
11045
11046            var flipPos = extend({}, location);
11047
11048            if (collisions[0] === "flip") {
11049                location.top += that._flip(offsets.top, element.outerHeight(), anchor.outerHeight(), viewport.height() / zoomLevel, origins[0], positions[0], wrapper.outerHeight());
11050            }
11051
11052            if (collisions[1] === "flip") {
11053                location.left += that._flip(offsets.left, element.outerWidth(), anchor.outerWidth(), viewport.width() / zoomLevel, origins[1], positions[1], wrapper.outerWidth());
11054            }
11055
11056            wrapper.css(location);
11057
11058            return (location.left != flipPos.left || location.top != flipPos.top);
11059        },
11060
11061        _align: function(origin, position) {
11062            var that = this,
11063                element = that.wrapper,
11064                anchor = $(that.options.anchor),
11065                verticalOrigin = origin[0],
11066                horizontalOrigin = origin[1],
11067                verticalPosition = position[0],
11068                horizontalPosition = position[1],
11069                anchorOffset = getOffset(anchor),
11070                appendTo = $(that.options.appendTo),
11071                appendToOffset,
11072                width = element.outerWidth(),
11073                height = element.outerHeight(),
11074                anchorWidth = anchor.outerWidth(),
11075                anchorHeight = anchor.outerHeight(),
11076                top = anchorOffset.top,
11077                left = anchorOffset.left,
11078                round = Math.round;
11079
11080            if (appendTo[0] != document.body) {
11081                appendToOffset = getOffset(appendTo);
11082                top -= appendToOffset.top;
11083                left -= appendToOffset.left;
11084            }
11085
11086
11087            if (verticalOrigin === BOTTOM) {
11088                top += anchorHeight;
11089            }
11090
11091            if (verticalOrigin === CENTER) {
11092                top += round(anchorHeight / 2);
11093            }
11094
11095            if (verticalPosition === BOTTOM) {
11096                top -= height;
11097            }
11098
11099            if (verticalPosition === CENTER) {
11100                top -= round(height / 2);
11101            }
11102
11103            if (horizontalOrigin === RIGHT) {
11104                left += anchorWidth;
11105            }
11106
11107            if (horizontalOrigin === CENTER) {
11108                left += round(anchorWidth / 2);
11109            }
11110
11111            if (horizontalPosition === RIGHT) {
11112                left -= width;
11113            }
11114
11115            if (horizontalPosition === CENTER) {
11116                left -= round(width / 2);
11117            }
11118
11119            return {
11120                top: top,
11121                left: left
11122            };
11123        }
11124    });
11125
11126    ui.plugin(Popup);
11127})(jQuery);
11128(function($, undefined) {
11129    /**
11130    * @name kendo.ui.List.Description
11131    *
11132    * @section Common class for ComboBox, DropDownList and AutoComplete widgets.
11133    */
11134    var kendo = window.kendo,
11135        ui = kendo.ui,
11136        Widget = ui.Widget,
11137        keys = kendo.keys,
11138        touch = kendo.support.touch,
11139        ID = "id",
11140        LI = "li",
11141        CLICK = touch ? "touchend" : "click",
11142        CHANGE = "change",
11143        CHARACTER = "character",
11144        FOCUSED = "k-state-focused",
11145        HOVER = "k-state-hover",
11146        LOADING = "k-loading",
11147        OPEN = "open",
11148        CLOSE = "close",
11149        SELECT = "select",
11150        extend = $.extend,
11151        proxy = $.proxy,
11152        isIE8 = $.browser.msie && parseInt($.browser.version, 10) < 9,
11153        quotRegExp = /"/g;
11154
11155    var List = Widget.extend(/** @lends kendo.ui.List */{
11156        /**
11157         * Creates a List instance.
11158         * @constructs
11159         * @extends kendo.ui.Widget
11160         */
11161        init: function(element, options) {
11162            var that = this, id, list;
11163
11164            Widget.fn.init.call(that, element, options);
11165
11166            that._template();
11167
11168            that.ul = $('<ul unselectable="on" class="k-list k-reset"/>')
11169                        .css({ overflow: kendo.support.touch ? "": "auto" })
11170                        .delegate(LI, "mouseenter", function() { $(this).addClass(HOVER); })
11171                        .delegate(LI, "mouseleave", function() { $(this).removeClass(HOVER); })
11172                        .delegate(LI, CLICK, proxy(that._click, that));
11173
11174            that.list = list = $("<div class='k-list-container'/>")
11175                        .append(that.ul)
11176                        .mousedown(function(e) {
11177                            e.preventDefault();
11178                        });
11179
11180            id = that.element.attr(ID);
11181            if (id) {
11182                list.attr(ID, id + "-list");
11183            }
11184
11185            $(document.documentElement).bind("mousedown", proxy(that._mousedown, that));
11186        },
11187
11188        items: function() {
11189            return this.ul[0].children;
11190        },
11191
11192        current: function(candidate) {
11193            var that = this;
11194
11195            if (candidate !== undefined) {
11196                if (that._current) {
11197                    that._current.removeClass(FOCUSED);
11198                }
11199
11200                if (candidate) {
11201                    candidate.addClass(FOCUSED);
11202                    that._scroll(candidate);
11203                }
11204
11205                that._current = candidate;
11206            } else {
11207                return that._current;
11208            }
11209        },
11210
11211        dataItem: function(index) {
11212            var that = this;
11213
11214            if (index === undefined) {
11215                index = that.selectedIndex;
11216            }
11217
11218            return that._data()[index];
11219        },
11220
11221        _accessors: function() {
11222            var that = this,
11223                element = that.element,
11224                options = that.options,
11225                getter = kendo.getter,
11226                textField = element.attr(kendo.attr("text-field")),
11227                valueField = element.attr(kendo.attr("value-field"));
11228
11229            if (textField) {
11230                options.dataTextField = textField;
11231            }
11232
11233            if (valueField) {
11234                options.dataValueField = valueField;
11235            }
11236
11237            that._text = getter(options.dataTextField);
11238            that._value = getter(options.dataValueField);
11239        },
11240
11241        _blur: function() {
11242            var that = this;
11243
11244            that._change();
11245            that.close();
11246        },
11247
11248        _change: function() {
11249            var that = this,
11250                index = that.selectedIndex,
11251                value = that.value(),
11252                trigger;
11253
11254            if (value !== that._old) {
11255                trigger = true;
11256            } else if (index !== undefined && index !== that._oldIndex) {
11257                trigger = true;
11258            }
11259
11260            if (trigger) {
11261                that._old = value;
11262                that._oldIndex = index;
11263
11264                that.trigger(CHANGE);
11265
11266                // trigger the DOM change event so any subscriber gets notified
11267                that.element.trigger(CHANGE);
11268            }
11269        },
11270
11271        _click: function(e) {
11272            if (!e.isDefaultPrevented()) {
11273                this._accept($(e.currentTarget));
11274            }
11275        },
11276
11277        _data: function() {
11278            return this.dataSource.view();
11279        },
11280
11281        _enable: function() {
11282            var that = this,
11283                options = that.options;
11284
11285            if (that.element.prop("disabled")) {
11286                options.enable = false;
11287            }
11288
11289            that.enable(options.enable);
11290        },
11291
11292        _focus: function(li) {
11293            var that = this;
11294
11295            if (that.popup.visible() && that.trigger(SELECT, {item: li})) {
11296                that.close();
11297                return;
11298            }
11299
11300            that._select(li);
11301            that._blur();
11302        },
11303
11304        _height: function(length) {
11305            if (length) {
11306                var that = this,
11307                    list = that.list,
11308                    visible = that.popup.visible(),
11309                    height = that.options.height;
11310
11311                list = list.add(list.parent(".k-animation-container")).show()
11312                           .height(that.ul[0].scrollHeight > height ? height : "auto");
11313
11314                if (!visible) {
11315                    list.hide();
11316                }
11317            }
11318        },
11319
11320        _adjustListWidth: function() {
11321            var list = this.list,
11322                width = list[0].style.width,
11323                wrapper = this.wrapper,
11324                computedStyle, computedWidth;
11325
11326            if (!width) {
11327                computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
11328                computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();
11329
11330                if (computedStyle && ($.browser.mozilla || $.browser.msie)) { // getComputedStyle returns different box in FF and IE.
11331                    computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
11332                }
11333
11334                width = computedWidth - (list.outerWidth() - list.width());
11335            }
11336
11337            list.css({
11338                fontFamily: wrapper.css("font-family"),
11339                width: width
11340            });
11341
11342            return true;
11343        },
11344
11345        _popup: function() {
11346            var that = this,
11347                list = that.list,
11348                options = that.options,
11349                wrapper = that.wrapper,
11350                opened = false;
11351
11352            that.popup = new ui.Popup(list, extend({}, options.popup, {
11353                anchor: wrapper,
11354                open: function(e) {
11355                    if (!opened) {
11356                        opened = that._adjustListWidth();
11357                    }
11358
11359                    if (that.trigger(OPEN)) {
11360                        e.preventDefault();
11361                    }
11362                },
11363                close: function(e) {
11364                    if (that.trigger(CLOSE)) {
11365                        e.preventDefault();
11366                    }
11367                },
11368                animation: options.animation
11369            }));
11370
11371            that._touchScroller = kendo.touchScroller(that.popup.element);
11372        },
11373
11374        _makeUnselectable: function(element) {
11375            if (isIE8) {
11376                this.list.find("*").attr("unselectable", "on");
11377            }
11378        },
11379
11380        _toggleHover: function(e) {
11381            if (!touch) {
11382                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
11383            }
11384        },
11385
11386        _toggle: function(open) {
11387            var that = this;
11388            open = open !== undefined? open : !that.popup.visible();
11389
11390            if (!touch && that._focused[0] !== document.activeElement) {
11391                that._focused.focus();
11392            }
11393
11394            that[open ? OPEN : CLOSE]();
11395        },
11396
11397        _scroll: function (item) {
11398
11399            if (!item) {
11400                return;
11401            }
11402
11403            if (item[0]) {
11404                item = item[0];
11405            }
11406
11407            var ul = this.ul[0],
11408                itemOffsetTop = item.offsetTop,
11409                itemOffsetHeight = item.offsetHeight,
11410                ulScrollTop = ul.scrollTop,
11411                ulOffsetHeight = ul.clientHeight,
11412                bottomDistance = itemOffsetTop + itemOffsetHeight;
11413
11414            ul.scrollTop = ulScrollTop > itemOffsetTop ?
11415                           itemOffsetTop : bottomDistance > (ulScrollTop + ulOffsetHeight) ?
11416                           bottomDistance - ulOffsetHeight : ulScrollTop;
11417        },
11418
11419        _template: function() {
11420            var that = this,
11421                options = that.options,
11422                template = options.template,
11423                hasDataSource = options.dataSource;
11424
11425            if (that.element.is(SELECT) && that.element[0].length) {
11426                if (!hasDataSource) {
11427                    options.dataTextField = options.dataTextField || "text";
11428                    options.dataValueField = options.dataValueField || "value";
11429                }
11430            }
11431
11432            if (!template) {
11433                that.template = kendo.template('<li unselectable="on" class="k-item">${data' + (options.dataTextField ? "." : "") + options.dataTextField + "}</li>", { useWithBlock: false });
11434            } else {
11435                template = kendo.template(template);
11436                that.template = function(data) {
11437                    return '<li unselectable="on" class="k-item">' + template(data) + "</li>";
11438                };
11439            }
11440        }
11441    });
11442
11443    extend(List, {
11444        caret: function(element) {
11445            var caret,
11446                selection = element.ownerDocument.selection;
11447
11448            if (selection) {
11449                caret = Math.abs(selection.createRange().moveStart(CHARACTER, -element.value.length));
11450            } else {
11451                caret = element.selectionStart;
11452            }
11453
11454            return caret;
11455        },
11456
11457        selectText: function (element, selectionStart, selectionEnd) {
11458            if (element.createTextRange) {
11459                var textRange = element.createTextRange();
11460                textRange.collapse(true);
11461                textRange.moveStart(CHARACTER, selectionStart);
11462                textRange.moveEnd(CHARACTER, selectionEnd - selectionStart);
11463                textRange.select();
11464            } else {
11465                element.setSelectionRange(selectionStart, selectionEnd);
11466            }
11467        },
11468        inArray: function(node, parentNode) {
11469            var idx, length, siblings = parentNode.children;
11470
11471            if (!node || node.parentNode !== parentNode) {
11472                return -1;
11473            }
11474
11475            for (idx = 0, length = siblings.length; idx < length; idx++) {
11476                if (node === siblings[idx]) {
11477                    return idx;
11478                }
11479            }
11480
11481            return -1;
11482        }
11483    });
11484
11485    kendo.ui.List = List;
11486
11487    /**
11488    * @name kendo.ui.Select.Description
11489    *
11490    * @section Common class for ComboBox and DropDownList widgets.
11491    */
11492    ui.Select = List.extend(/** @lends kendo.ui.Select */{
11493        /**
11494         * @extends kendo.ui.List
11495         * @constructs
11496         */
11497        init: function(element, options) {
11498            List.fn.init.call(this, element, options);
11499        },
11500
11501        setDataSource: function(dataSource) {
11502            this.options.dataSource = dataSource;
11503
11504            this._dataSource();
11505
11506            if (this.options.autoBind) {
11507                this._selectItem();
11508            }
11509        },
11510
11511        /**
11512        * Closes the drop-down list.
11513        * @example
11514        * dropdownlist.close();
11515        *
11516        * @example
11517        * combobox.close();
11518        */
11519        close: function() {
11520            this.popup.close();
11521        },
11522
11523        _accessor: function(value, idx) {
11524            var element = this.element,
11525                isSelect = element.is(SELECT),
11526                option, selectedIndex;
11527
11528            element = element[0];
11529
11530            if (value === undefined) {
11531                if (isSelect) {
11532                    selectedIndex = element.selectedIndex;
11533
11534                    if (selectedIndex > -1) {
11535                        option = element.options[selectedIndex];
11536
11537                        if (option) {
11538                            value = option.value;
11539                        }
11540                    }
11541                } else {
11542                    value = element.value;
11543                }
11544                return value;
11545            } else {
11546                if (isSelect) {
11547                    element.selectedIndex = idx;
11548                } else {
11549                    element.value = value;
11550                }
11551            }
11552        },
11553
11554        _hideBusy: function () {
11555            var that = this;
11556            clearTimeout(that._busy);
11557            that._arrow.removeClass(LOADING);
11558        },
11559
11560        _showBusy: function () {
11561            var that = this;
11562
11563            if (that._busy) {
11564                return;
11565            }
11566
11567            that._busy = setTimeout(function () {
11568                that._arrow.addClass(LOADING);
11569            }, 100);
11570        },
11571
11572        _dataSource: function() {
11573            var that = this,
11574                element = that.element,
11575                options = that.options,
11576                dataSource = options.dataSource || {},
11577                idx;
11578
11579            dataSource = $.isArray(dataSource) ? {data: dataSource} : dataSource;
11580
11581            if (element.is(SELECT)) {
11582                idx = element[0].selectedIndex;
11583                if (idx > -1) {
11584                    options.index = idx;
11585                }
11586
11587                dataSource.select = element;
11588                dataSource.fields = [{ field: options.dataTextField },
11589                                     { field: options.dataValueField }];
11590            }
11591
11592            if (that.dataSource && that._refreshHandler) {
11593                that.dataSource.unbind(CHANGE, that._refreshHandler)
11594                               .unbind("requestStart", that._requestStartHandler);
11595            } else {
11596                that._refreshHandler = proxy(that.refresh, that);
11597                that._requestStartHandler = proxy(that._showBusy, that);
11598            }
11599
11600            that.dataSource = kendo.data.DataSource.create(dataSource)
11601                                   .bind(CHANGE, that._refreshHandler)
11602                                   .bind("requestStart", that._requestStartHandler);
11603        },
11604
11605        _index: function(value) {
11606            var that = this,
11607                idx,
11608                length,
11609                data = that._data(),
11610                valueFromData;
11611
11612            for (idx = 0, length = data.length; idx < length; idx++) {
11613                valueFromData = that._value(data[idx]);
11614
11615                if (valueFromData === undefined) {
11616                    valueFromData = that._text(data[idx]);
11617                }
11618
11619                if (valueFromData == value) {
11620                    return idx;
11621                }
11622            }
11623
11624            return -1;
11625        },
11626
11627        _get: function(li) {
11628            var that = this,
11629                data = that._data(),
11630                idx, length;
11631
11632            if (typeof li === "function") {
11633                for (idx = 0, length = data.length; idx < length; idx++) {
11634                    if (li(data[idx])) {
11635                        li = idx;
11636                        break;
11637                    }
11638                }
11639            }
11640
11641            if (typeof li === "number") {
11642                if (li < 0) {
11643                    return $();
11644                }
11645
11646                li = $(that.ul[0].children[li]);
11647            }
11648
11649            if (li && li.nodeType) {
11650                li = $(li);
11651            }
11652
11653            return li;
11654        },
11655
11656        _move: function(e) {
11657            var that = this,
11658                key = e.keyCode,
11659                ul = that.ul[0],
11660                current = that._current,
11661                down = key === keys.DOWN,
11662                pressed;
11663
11664            if (key === keys.UP || down) {
11665                if (e.altKey) {
11666                    that.toggle(down);
11667                } else if (down) {
11668                    that._select(current ? current[0].nextSibling : ul.firstChild);
11669                    e.preventDefault();
11670                } else {
11671                    that._select(current ? current[0].previousSibling : ul.lastChild);
11672                    e.preventDefault();
11673                }
11674                pressed = true;
11675            } else if (key === keys.ENTER || key === keys.TAB) {
11676
11677                if (that.popup.visible()) {
11678                    e.preventDefault();
11679                }
11680
11681                that._accept(current);
11682                pressed = true;
11683            } else if (key === keys.ESC) {
11684                that.close();
11685                pressed = true;
11686            }
11687
11688            return pressed;
11689        },
11690
11691        _valueOnFetch: function(value) {
11692            var that = this;
11693
11694            if (!that.ul[0].firstChild && !that._fetch) {
11695                that.dataSource.one(CHANGE, function() {
11696                    that._fetch = true;
11697                    that.value(value);
11698                }).fetch();
11699
11700                return true;
11701            }
11702
11703            that._fetch = false;
11704        },
11705
11706        _options: function(data, optionLabel) {
11707            var that = this,
11708                element = that.element,
11709                selectedIndex = element[0].selectedIndex,
11710                length = data.length,
11711                options = "",
11712                option,
11713                dataItem,
11714                dataText,
11715                dataValue,
11716                idx = 0;
11717
11718            if (optionLabel) {
11719                options = optionLabel;
11720                idx = 1;
11721            }
11722
11723            for (; idx < length; idx++) {
11724                option = "<option";
11725                dataItem = data[idx];
11726                dataText = that._text(dataItem);
11727                dataValue = that._value(dataItem);
11728
11729                if (dataValue !== undefined) {
11730                    dataValue += "";
11731
11732                    if (dataValue.indexOf('"') !== -1) {
11733                        dataValue = dataValue.replace(quotRegExp, "&quot;");
11734                    }
11735
11736                    option += ' value="' + dataValue + '"';
11737                }
11738
11739                option += ">";
11740
11741                if (dataText !== undefined) {
11742                    option += dataText;
11743                }
11744
11745                option += "</option>";
11746                options += option;
11747            }
11748
11749            element.html(options);
11750            element[0].selectedIndex = selectedIndex;
11751        },
11752
11753        _reset: function() {
11754            var that = this,
11755                element = that.element;
11756
11757            element.closest("form")
11758                   .bind("reset", function() {
11759                       setTimeout(function() {
11760                            that.value(element[0].value);
11761                       });
11762                   });
11763        }
11764    });
11765
11766})(jQuery);
11767(function($, undefined) {
11768    /**
11769     * @name kendo.ui.Calendar.Description
11770     *
11771     * @section
11772     * <p>
11773     *  The <b>Calendar</b> renders a graphical calendar that supports
11774     *  navigation and selection. It supports custom templates for its
11775     *  "month" view, configurable options for a minimum and maximum date,
11776     *  start view and the depth of the navigation.
11777     * </p>
11778     * <h3>Getting Started</h3>
11779     *
11780     * @exampleTitle Create a div element
11781     * @example
11782     * <div id="calendar"></div>
11783     *
11784     * @exampleTitle Initialize the Calendar via a jQuery ID selector
11785     * @example
11786     * $(document).ready(function(){
11787     *  $("#calendar").kendoCalendar();
11788     * });
11789     *
11790     * @section
11791     * <p>
11792     *  When a <b>Calendar</b> is initialized, it will automatically be
11793     *  displayed near the location of the used HTML element.
11794     * </p>
11795     * <h3>Configuring Calendar Behaviors</h3>
11796     * <p>
11797     *  The <b>Calendar</b> provides many configuration options that can be
11798     *  easily set during initialization. Among the properties that can be
11799     *  controlled:
11800     * </p>
11801     * <ul>
11802     *  <li>Selected date</li>
11803     *  <li>Minimum and/or maximum date</li>
11804     *  <li>Start view</li>
11805     *  <li>
11806     *   Define the navigation depth (last view to which end user can
11807     *   navigate)
11808     *  </li>
11809     *  <li>Day template</li>
11810     *  <li>Footer template</li>
11811     * </ul>
11812     *
11813     * @exampleTitle Create Calendar with selected date and a defined minimum
11814     * and maximum date
11815     * @example
11816     * $("#calendar").kendoCalendar({
11817     *  value: new Date(),
11818     *  min: new Date(1950, 0, 1),
11819     *  max: new Date(2049, 11, 31)
11820     * });
11821     *
11822     * @section
11823     * <p>
11824     *  The <b>Calendar</b> will not navigate before than the minimum
11825     *  date specified. It will also not navigate ahead the maximum date
11826     *  specified.
11827     * </p>
11828     * @section
11829     * <h3>Define start view and navigation depth</h3>
11830     * <p>
11831     *  The first rendered view can be defined with "start" option.
11832     *  Navigation depth can be controlled with "depth" option. Predefined
11833     *  views are:
11834     *  <ul>
11835     *   <li>"month" - shows the days from the month</li>
11836     *   <li>"year" - shows the months of the year</li>
11837     *   <li>"decade" - shows the years from the decade</li>
11838     *   <li>"century" - shows the decades from the century</li>
11839     *  </ul>
11840     * </p>
11841     *
11842     * @exampleTitle Create a Calendar, which allows a user to select a month
11843     * @example
11844     * $("#calendar").kendoCalendar({
11845     *  start: "year",
11846     *  depth: "year"
11847     * });
11848     *
11849     * @section
11850     * <h3>Customize day template</h3>
11851     * <p>
11852     *  The <b>Calendar</b> allows to customize content of the rendered day
11853     *  in the "month" view.
11854     * </p>
11855     *
11856     * @exampleTitle Create a Calendar with custom template
11857     * @example
11858     * $("#calendar").kendoCalendar({
11859     *  month: {
11860     *   content: '<div class="custom"><#=data.value#></div>'
11861     *  }
11862     * });
11863     *
11864     * @section
11865     * <p>
11866     *  This templates wraps the "value" in a div HTML element. Here is an
11867     *  example of the object passed to the template function:
11868     * </p>
11869     *
11870     * @exampleTitle Structure of the data object passed to the template
11871     * @example
11872     * data = {
11873     *  date: date, // Date object corresponding to the current cell
11874     *  title: kendo.toString(date, "D"),
11875     *  value: date.getDate(),
11876     *  dateString: "2011/0/1" // formatted date using yyyy/MM/dd format and month is zero-based
11877     * };
11878     *
11879     * @section
11880     * <h3>Accessing an Existing Calendar</h3>
11881     * <p>
11882     *  You can reference an existing <b>Calendar</b> instance via
11883     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
11884     *  Once a reference has been established, you can use the API to control
11885     *  its behavior.
11886     * </p>
11887     *
11888     * @exampleTitle Accessing an existing Calendar instance
11889     * @example
11890     * var calendar = $("#calendar").data("kendoCalendar");
11891     *
11892     */
11893    var kendo = window.kendo,
11894        ui = kendo.ui,
11895        Widget = ui.Widget,
11896        parse = kendo.parseDate,
11897        template = kendo.template,
11898        touch = kendo.support.touch,
11899        transitions = kendo.support.transitions,
11900        transitionOrigin = transitions ? transitions.css + "transform-origin" : "",
11901        cellTemplate = template('<td#=data.cssClass#><a class="k-link" href="\\#" data-#=data.ns#value="#=data.dateString#">#=data.value#</a></td>', { useWithBlock: false }),
11902        emptyCellTemplate = template("<td>&nbsp;</td>", { useWithBlock: false }),
11903        isIE8 = $.browser.msie && parseInt($.browser.version, 10) < 9,
11904        CLICK = touch ? "touchend" : "click",
11905        MIN = "min",
11906        LEFT = "left",
11907        SLIDE = "slide",
11908        MONTH = "month",
11909        CENTURY = "century",
11910        CHANGE = "change",
11911        NAVIGATE = "navigate",
11912        VALUE = "value",
11913        HOVER = "k-state-hover",
11914        DISABLED = "k-state-disabled",
11915        OTHERMONTH = "k-other-month",
11916        OTHERMONTHCLASS = ' class="' + OTHERMONTH + '"',
11917        TODAY = "k-nav-today",
11918        CELLSELECTOR = "td:has(.k-link)",
11919        MOUSEENTER = touch ? "touchstart" : "mouseenter",
11920        MOUSELEAVE = touch ? "touchend" : "mouseleave",
11921        MS_PER_MINUTE = 60000,
11922        MS_PER_DAY = 86400000,
11923        PREVARROW = "_prevArrow",
11924        NEXTARROW = "_nextArrow",
11925        proxy = $.proxy,
11926        extend = $.extend,
11927        DATE = Date,
11928        views = {
11929            month: 0,
11930            year: 1,
11931            decade: 2,
11932            century: 3
11933        };
11934
11935    var Calendar = Widget.extend(/** @lends kendo.ui.Calendar.prototype */{
11936        /**
11937          * @constructs
11938          * @extends kendo.ui.Widget
11939          * @param {DomElement} element DOM element
11940          * @param {Object} options Configuration options.
11941          * @option {Date} [value] <null> Specifies the selected date.
11942          * _example
11943          * // set the selected date to Jan 1st. 2012
11944          * $("#calendar").kendoCalendar({
11945          *     value: new Date(2012, 0, 1)
11946          * });
11947          * _exampleTitle To set after initialization
11948          * _example
11949          * // get a reference to the Kendo UI calendar widget
11950          * var calendar = $("#calendar").data("kendoCalendar");
11951          * // set the selected date on the calendar to Jan 1st, 2012
11952          * calendar.value(new Date(2012, 0, 1));
11953          * @option {Date} [min] <Date(1900, 0, 1)> Specifies the minimum date, which the calendar can show.
11954          * _example
11955          * // set the min date to Jan 1st, 2011
11956          * $("#calendar").kendoCalendar({
11957          *     min = new Date(2011, 0, 1)
11958          * });
11959          * _exampleTitle To set after initialization
11960          * _example
11961          * // get a reference to the Kendo UI calendar widget
11962          * var calendar = $("#calendar").data("kendoCalendar");
11963          * // set the min date to Jan 1st, 2011
11964          * calendar.min(new Date(2011, 0, 1));
11965          * @option {Date} [max] <Date(2099, 11, 31)> Specifies the maximum date, which the calendar can show.
11966          * _example
11967          * $("#calendar").kendoCalendar({
11968          *     max = new Date(2013, 0, 1);
11969          * });
11970          * _exampleTitle To set after initialization
11971          * _example
11972          * // get a reference to the Kendo UI calendar widget
11973          * var calendar = $("#calendar").data("kendoCalendar");
11974          * // set the max date to Jan 1st, 2013
11975          * calendar.max(new Date(2013, 0, 1));
11976          * @option {String} [footer] <> Specifies the content of the footer. If false, the footer will not be rendered.
11977          * _example
11978          * // change the footer text from the default current date
11979          * $("#calendar").kendoCalendar({
11980          *     footer = "My Custom Footer"
11981          * });
11982          * _exampleTitle Hide the footer
11983          * _example
11984          * $("#calendar").kendoCalendar({
11985          *     footer = false;
11986          * });
11987          * @option {String} [format] <MM/dd/yyyy> Specifies the format, which is used to parse value set with value() method.
11988          * _example
11989          * $("#calendar").kendoCalendar({
11990          *     format: "yyyy/MM/dd"
11991          * });
11992          * @option {String} [start] <month> Specifies the start view.
11993          * _example
11994          * $("#calendar").kendoCalendar({
11995          *     start: "year"
11996          * });
11997          * @option {String} [depth] Specifies the navigation depth.
11998          * _example
11999          * $("#calendar").kendoCalendar({
12000          *     depth: "year"
12001          * });
12002          * @option {Function} [footer] <> Template to be used for rendering the footer. If false, the footer will not be rendered.
12003          * _example
12004          *
12005          *  //calendar intialization
12006          *  &lt;script&gt;
12007          *      $("#calendar").kendoCalendar({
12008          *          footer: kendo.template("Today - #=kendo.toString(data, 'd') #")
12009          *      });
12010          *  &lt;/script&gt;
12011          * @option {Object} [month] <> Templates for the cells rendered in the "month" view.
12012          * @option {Function} [month.content] <> Template to be used for rendering the cells in the "month" view, which are in range.
12013          * _example
12014          *  //template
12015          *  &lt;script id="cellTemplate" type="text/x-kendo-tmpl"&gt;
12016          *      &lt;div class="${ data.value < 10 ? exhibition : party }"&gt;
12017          *      &lt;/div&gt;
12018          *      ${ data.value }
12019          *  &lt;/script&gt;
12020          *
12021          *  //calendar intialization
12022          *  &lt;script&gt;
12023          *      $("#calendar").kendoCalendar({
12024          *          month: {
12025          *             content:  kendo.template($("#cellTemplate").html()),
12026          *          }
12027          *      });
12028          *  &lt;/script&gt;
12029          * @option {Function} [month.empty] <> Template to be used for rendering the cells in the "month" view, which are not in the min/max range.
12030          */
12031        init: function(element, options) {
12032            var that = this, value;
12033
12034            Widget.fn.init.call(that, element, options);
12035
12036            element = that.wrapper = that.element;
12037            options = that.options;
12038
12039            element.addClass("k-widget k-calendar");
12040
12041            that._templates();
12042
12043            that._header();
12044
12045            that._footer(that.footer);
12046
12047            element
12048                .delegate(CELLSELECTOR, MOUSEENTER, mouseenter)
12049                .delegate(CELLSELECTOR, MOUSELEAVE, mouseleave)
12050                .delegate(CELLSELECTOR, CLICK, proxy(that._click, that));
12051
12052            value = options.value;
12053            validate(options);
12054
12055            that._index = views[options.start];
12056            that._current = new DATE(restrictValue(value, options.min, options.max));
12057
12058            that.value(value);
12059
12060            kendo.notify(that);
12061        },
12062
12063        options: {
12064            name: "Calendar",
12065            value: null,
12066            min: new DATE(1900, 0, 1),
12067            max: new DATE(2099, 11, 31),
12068            footer : '#= kendo.toString(data,"D") #',
12069            format : "",
12070            month : {},
12071            start: MONTH,
12072            depth: MONTH,
12073            animation: {
12074                horizontal: {
12075                    effects: SLIDE,
12076                    duration: 500,
12077                    divisor: 2
12078                },
12079                vertical: {
12080                    effects: "zoomIn",
12081                    duration: 400
12082                }
12083            }
12084        },
12085
12086        events: [
12087            /**
12088            * Fires when the selected date is changed
12089            * @name kendo.ui.Calendar#change
12090            * @event
12091            * @param {Event} e
12092            * @example
12093            * $("#calendar").kendoCalendar({
12094            *     change: function(e) {
12095            *         // handle event
12096            *     });
12097            * });
12098            * @exampleTitle To set after initialization
12099            * @example
12100            * // get a reference to the Kendo UI calendar widget
12101            * var calendar = $("#calendar").data("kendoCalendar");
12102            * // bind to the change event
12103            * calendar.bind("change", function(e) {
12104            *      // handle event
12105            * });
12106            */
12107            /**
12108            * Fires when navigate
12109            * @name kendo.ui.Calendar#navigate
12110            * @event
12111            * @param {Event} e
12112            * @example
12113            * $("#calendar").kendoCalendar({
12114            *     navigate: function(e) {
12115            *          // handle event
12116            *     }
12117            * });
12118            * @exampleTitle To set after initialization
12119            * @example
12120            * // get a reference to the Kendo UI calendar widget
12121            * var calendar = $("#calendar").data("kendoCalendar");
12122            * // bind to the change event
12123            * calendar.bind("navigate", function(e) {
12124            *      // handle event
12125            * });
12126            */
12127            CHANGE,
12128            NAVIGATE
12129        ],
12130
12131        setOptions: function(options) {
12132            validate(options);
12133
12134            Widget.fn.setOptions.call(this, options);
12135        },
12136
12137        /**
12138        * Gets/Sets the min value of the calendar.
12139        * @param {Date|String} value The min date to set.
12140        * @returns {Date} The min value of the calendar.
12141        * @example
12142        * // get a reference to the calendar widget
12143        * var calendar = $("#calendar").data("kendoCalendar");
12144        *
12145        * // get the min value of the calendar.
12146        * var min = calendar.min();
12147        *
12148        * // set the min value of the calendar.
12149        * calendar.min(new Date(1900, 0, 1));
12150        */
12151        min: function(value) {
12152            return this._option(MIN, value);
12153        },
12154
12155        /**
12156        * Gets/Sets the max value of the calendar.
12157        * @param {Date | String} value The max date to set.
12158        * @returns {Date} The max value of the calendar.
12159        * @example
12160        * // get a reference to the calendar widget
12161        * var calendar = $("#calendar").data("kendoCalendar");
12162        *
12163        * // get the max value of the calendar.
12164        * var max = calendar.max();
12165        *
12166        * // set the max value of the calendar.
12167        * calendar.max(new Date(2100, 0, 1));
12168        */
12169        max: function(value) {
12170            return this._option("max", value);
12171        },
12172
12173        /**
12174        * Navigates to the past
12175        * @example
12176        * // get a reference to the calendar widget
12177        * var calendar = $("#calendar").data("kendoCalendar");
12178        * // navigate to past
12179        * calendar.navigateToPast();
12180        */
12181        navigateToPast: function() {
12182            this._navigate(PREVARROW, -1);
12183        },
12184
12185        /**
12186        * Navigates to the future
12187        * @example
12188        * // get a reference to the calendar widget
12189        * var calendar = $("#calendar").data("kendoCalendar");
12190        * // navigate to future
12191        * calendar.navigateToFuture();
12192        */
12193        navigateToFuture: function() {
12194            this._navigate(NEXTARROW, 1);
12195        },
12196
12197        /**
12198        * Navigates to the upper view
12199        * @example
12200        * // get a reference to the calendar widget
12201        * var calendar = $("#calendar").data("kendoCalendar");
12202        * // navigate up
12203        * calendar.navigateUp();
12204        */
12205        navigateUp: function() {
12206            var that = this,
12207                index = that._index;
12208
12209            if (that._title.hasClass(DISABLED)) {
12210                return;
12211            }
12212
12213            that.navigate(that._current, ++index);
12214        },
12215
12216        /**
12217        * Navigates to the lower view
12218        * @param {Date} value Desired date
12219        * @example
12220        * // get a reference to the calendar widget
12221        * var calendar = $("#calendar").data("kendoCalendar");
12222        * // navigate down
12223        * calendar.navigateDown(value);
12224        */
12225        navigateDown: function(value) {
12226            var that = this,
12227            index = that._index,
12228            depth = that.options.depth;
12229
12230            if (!value) {
12231                return;
12232            }
12233
12234            if (index === views[depth]) {
12235                if (+that._value != +value) {
12236                    that.value(value);
12237                    that.trigger(CHANGE);
12238                }
12239                return;
12240            }
12241
12242            that.navigate(value, --index);
12243        },
12244
12245        /**
12246        * Navigates to view
12247        * @param {Date} value Desired date
12248        * @param {String} view Desired view
12249        * @example
12250        * // get a reference to the calendar widget
12251        * var calendar = $("#calendar").data("kendoCalendar");
12252        * // navigate to the desired date
12253        * calendar.navigate(value, view);
12254        */
12255        navigate: function(value, view) {
12256            view = isNaN(view) ? views[view] : view;
12257
12258            var that = this,
12259                options = that.options,
12260                min = options.min,
12261                max = options.max,
12262                title = that._title,
12263                from = that._table,
12264                selectedValue = that._value,
12265                currentValue = that._current,
12266                future = value && +value > +currentValue,
12267                vertical = view !== undefined && view !== that._index,
12268                to, currentView, compare;
12269
12270            //do not navigate if the view is still animating
12271            if (from && from.parent().data("animating")) {
12272                return;
12273            }
12274
12275            if (!value) {
12276                value = currentValue;
12277            } else {
12278                that._current = value = new DATE(restrictValue(value, min, max));
12279            }
12280
12281            if (view === undefined) {
12282                view = that._index;
12283            } else {
12284                that._index = view;
12285            }
12286
12287            that._view = currentView = calendar.views[view];
12288            compare = currentView.compare;
12289
12290            title.toggleClass(DISABLED, view === views[CENTURY]);
12291            that[PREVARROW].toggleClass(DISABLED, compare(value, min) < 1);
12292            that[NEXTARROW].toggleClass(DISABLED, compare(value, max) > -1);
12293
12294            if (!from || that._changeView) {
12295                title.html(currentView.title(value));
12296
12297                that._table = to = $(currentView.content(extend({
12298                    min: min,
12299                    max: max,
12300                    date: value
12301                }, that[currentView.name])));
12302
12303                makeUnselectable(to);
12304
12305                that._animate({
12306                    from: from,
12307                    to: to,
12308                    vertical: vertical,
12309                    future: future
12310                });
12311
12312                that.trigger(NAVIGATE);
12313            }
12314
12315            if (view === views[options.depth] && selectedValue) {
12316                that._class("k-state-selected", currentView.toDateString(selectedValue));
12317            }
12318
12319            that._changeView = true;
12320        },
12321
12322        /**
12323        * Gets/Sets the value of the calendar.
12324        * @param {Date|String} value The date to set.
12325        * @returns {Date} The value of the calendar.
12326        * @example
12327        * // get a reference to the calendar widget
12328        * var calendar = $("#calendar").data("kendoCalendar");
12329        *
12330        * // get the value of the calendar.
12331        * var value = calendar.value();
12332        *
12333        * // set the value of the calendar.
12334        * calendar.value(new Date());
12335        */
12336        value: function(value) {
12337            var that = this,
12338            view = that._view,
12339            options = that.options,
12340            min = options.min,
12341            max = options.max;
12342
12343            if (value === undefined) {
12344                return that._value;
12345            }
12346
12347            value = parse(value, options.format);
12348
12349            if (value !== null) {
12350                value = new DATE(value);
12351
12352                if (!isInRange(value, min, max)) {
12353                    value = null;
12354                }
12355            }
12356
12357            that._value = value;
12358            that._changeView = !value || view && view.compare(value, that._current) !== 0;
12359
12360            that.navigate(value);
12361        },
12362
12363        _animate: function(options) {
12364            var that = this,
12365                from = options.from,
12366                to = options.to;
12367
12368            if (!from) {
12369                to.insertAfter(that.element[0].firstChild);
12370            } else if (!from.is(":visible") || that.options.animation === false) {
12371                to.insertAfter(from);
12372                from.remove();
12373            } else {
12374                that[options.vertical ? "_vertical" : "_horizontal"](from, to, options.future);
12375            }
12376        },
12377
12378        _horizontal: function(from, to, future) {
12379            var that = this,
12380                horizontal = that.options.animation.horizontal,
12381                effects = horizontal.effects,
12382                viewWidth = from.outerWidth();
12383
12384                if (effects && effects.indexOf(SLIDE) != -1) {
12385                    from.add(to).css({ width: viewWidth });
12386
12387                    from.wrap("<div/>");
12388
12389                    from.parent()
12390                    .css({
12391                        position: "relative",
12392                        width: viewWidth * 2,
12393                        "float": LEFT,
12394                        left: future ? 0 : -viewWidth
12395                    });
12396
12397                    to[future ? "insertAfter" : "insertBefore"](from);
12398
12399                    extend(horizontal, {
12400                        effects: SLIDE + ":" + (future ? LEFT : "right"),
12401                        complete: function() {
12402                            from.remove();
12403                            to.unwrap();
12404                        }
12405                    });
12406
12407                    from.parent().kendoStop(true, true).kendoAnimate(horizontal);
12408                }
12409        },
12410
12411        _vertical: function(from, to) {
12412            var that = this,
12413                vertical = that.options.animation.vertical,
12414                effects = vertical.effects,
12415                cell, position;
12416
12417            if (effects && effects.indexOf("zoom") != -1) {
12418                to.css({
12419                    position: "absolute",
12420                    top: from.prev().outerHeight(),
12421                    left: 0
12422                }).insertBefore(from);
12423
12424                if (transitionOrigin) {
12425                    cell = that._cellByDate(that._view.toDateString(that._current));
12426                    position = cell.position();
12427                    position = (position.left + parseInt(cell.width() / 2, 10)) + "px" + " " + (position.top + parseInt(cell.height() / 2, 10) + "px");
12428                    to.css(transitionOrigin, position);
12429                }
12430
12431                from.kendoStop(true, true).kendoAnimate({
12432                    effects: "fadeOut",
12433                    duration: 600,
12434                    complete: function() {
12435                        from.remove();
12436                        to.css({
12437                            position: "static",
12438                            top: 0,
12439                            left: 0
12440                        });
12441                    }
12442                });
12443
12444                to.kendoStop(true, true).kendoAnimate(vertical);
12445            }
12446        },
12447
12448        _cellByDate: function(value) {
12449            return this._table.find("td:not(." + OTHERMONTH + ")")
12450                       .filter(function() {
12451                           return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
12452                       });
12453        },
12454
12455        _class: function(className, value) {
12456            this._table.find("td:not(." + OTHERMONTH + ")")
12457                .removeClass(className)
12458                .filter(function() {
12459                   return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
12460                })
12461                .addClass(className);
12462        },
12463
12464        _click: function(e) {
12465            var that = this,
12466                options = that.options,
12467                currentValue = that._current,
12468                link = $(e.currentTarget.firstChild),
12469                value = link.attr(kendo.attr(VALUE)).split("/");
12470
12471            //Safari cannot create corretly date from "1/1/2090"
12472            value = new DATE(value[0], value[1], value[2]);
12473
12474            e.preventDefault();
12475
12476            if (link.parent().hasClass(OTHERMONTH)) {
12477                currentValue = value;
12478            } else {
12479                that._view.setDate(currentValue, value);
12480            }
12481
12482            that.navigateDown(restrictValue(currentValue, options.min, options.max));
12483        },
12484
12485        _focus: function(value) {
12486            var that = this,
12487                view = that._view;
12488
12489            if (view.compare(value, that._current) !== 0) {
12490                that.navigate(value);
12491            } else {
12492                that._current = value;
12493            }
12494
12495            that._class("k-state-focused", view.toDateString(value));
12496        },
12497
12498        _footer: function(template) {
12499            var that = this,
12500                element = that.element,
12501                today = new DATE(),
12502                footer = element.find(".k-footer");
12503
12504            if (!template) {
12505                that._toggle(false);
12506                footer.hide();
12507                return;
12508            }
12509
12510            if (!footer[0]) {
12511                footer = $('<div class="k-footer"><a href="#" class="k-link k-nav-today"></a></div>').appendTo(element);
12512            }
12513
12514            that._today = footer.show()
12515                                .find(".k-link")
12516                                .html(template(today))
12517                                .attr("title", kendo.toString(today, "D"));
12518
12519            that._toggle();
12520        },
12521
12522        _header: function() {
12523            var that = this,
12524            element = that.element,
12525            links;
12526
12527            if (!element.find(".k-header")[0]) {
12528                element.html('<div class="k-header">' +
12529                             '<a href="#" class="k-link k-nav-prev"><span class="k-icon k-arrow-prev"></span></a>' +
12530                             '<a href="#" class="k-link k-nav-fast"></a>' +
12531                             '<a href="#" class="k-link k-nav-next"><span class="k-icon k-arrow-next"></span></a>' +
12532                             '</div>');
12533            }
12534
12535            links = element.find(".k-link")
12536                           .bind(MOUSEENTER, mouseenter)
12537                           .bind(MOUSELEAVE, mouseleave)
12538                           .click(false);
12539
12540            that._title = links.eq(1).bind(CLICK, proxy(that.navigateUp, that));
12541            that[PREVARROW] = links.eq(0).bind(CLICK, proxy(that.navigateToPast, that));
12542            that[NEXTARROW] = links.eq(2).bind(CLICK, proxy(that.navigateToFuture, that));
12543        },
12544
12545        _navigate: function(arrow, modifier) {
12546            var that = this,
12547                index = that._index + 1,
12548                currentValue = new DATE(that._current);
12549
12550            arrow = that[arrow];
12551
12552            if (!arrow.hasClass(DISABLED)) {
12553                if (index > 3) {
12554                    currentValue.setFullYear(currentValue.getFullYear() + 100 * modifier);
12555                } else {
12556                    calendar.views[index].setDate(currentValue, modifier);
12557                }
12558
12559                that.navigate(currentValue);
12560            }
12561        },
12562
12563        _option: function(option, value) {
12564            var that = this,
12565                options = that.options,
12566                selectedValue = +that._value,
12567                bigger, navigate;
12568
12569            if (value === undefined) {
12570                return options[option];
12571            }
12572
12573            value = parse(value, options.format);
12574
12575            if (!value) {
12576                return;
12577            }
12578
12579            options[option] = new DATE(value);
12580
12581            navigate = that._view.compare(value, that._current);
12582
12583            if (option === MIN) {
12584                bigger = +value > selectedValue;
12585                navigate = navigate > -1;
12586            } else {
12587                bigger = selectedValue > +value;
12588                navigate = navigate < 1;
12589            }
12590
12591            if (bigger) {
12592                that.value(null);
12593            } else if (navigate) {
12594                that.navigate();
12595            }
12596
12597            that._toggle();
12598        },
12599
12600        _toggle: function(toggle) {
12601            var that = this,
12602                options = that.options,
12603                link = that._today;
12604
12605            if (toggle === undefined) {
12606                toggle = isInRange(new DATE(), options.min, options.max);
12607            }
12608
12609            if (link) {
12610                link.unbind(CLICK);
12611
12612                if (toggle) {
12613                    link.addClass(TODAY)
12614                        .removeClass(DISABLED)
12615                        .bind(CLICK, proxy(that._todayClick, that));
12616                } else {
12617                    link.removeClass(TODAY)
12618                        .addClass(DISABLED)
12619                        .bind(CLICK, prevent);
12620                }
12621            }
12622        },
12623
12624        _todayClick: function(e) {
12625            var that = this,
12626                depth = views[that.options.depth],
12627                today = new DATE();
12628
12629            e.preventDefault();
12630
12631            if (that._view.compare(that._current, today) === 0 && that._index == depth) {
12632                that._changeView = false;
12633            }
12634
12635            that._value = today;
12636            that.navigate(today, depth);
12637
12638            that.trigger(CHANGE);
12639        },
12640
12641        _templates: function() {
12642            var that = this,
12643                options = that.options,
12644                footer = options.footer,
12645                month = options.month,
12646                content = month.content,
12647                empty = month.empty;
12648
12649            that.month = {
12650                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 }),
12651                empty: template("<td>" + (empty || "&nbsp;") + "</td>", { useWithBlock: !!empty })
12652            };
12653
12654            if (footer) {
12655                that.footer = template(footer, { useWithBlock: false });
12656            }
12657        }
12658    });
12659
12660    ui.plugin(Calendar);
12661
12662    var calendar = {
12663        firstDayOfMonth: function (date) {
12664            return new DATE(
12665                date.getFullYear(),
12666                date.getMonth(),
12667                1
12668            );
12669        },
12670
12671        firstVisibleDay: function (date) {
12672            var firstDay = kendo.culture().calendar.firstDay,
12673            firstVisibleDay = new DATE(date.getFullYear(), date.getMonth(), 0, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
12674
12675            while (firstVisibleDay.getDay() != firstDay) {
12676                calendar.setTime(firstVisibleDay, -1 * MS_PER_DAY);
12677            }
12678
12679            return firstVisibleDay;
12680        },
12681
12682        setTime: function (date, time) {
12683            var tzOffsetBefore = date.getTimezoneOffset(),
12684            resultDATE = new DATE(date.getTime() + time),
12685            tzOffsetDiff = resultDATE.getTimezoneOffset() - tzOffsetBefore;
12686
12687            date.setTime(resultDATE.getTime() + tzOffsetDiff * MS_PER_MINUTE);
12688        },
12689        views: [{
12690            name: MONTH,
12691            title: function(date) {
12692                return kendo.culture().calendar.months.names[date.getMonth()] + " " + date.getFullYear();
12693            },
12694            content: function(options) {
12695                var that = this,
12696                idx = 0,
12697                min = options.min,
12698                max = options.max,
12699                date = options.date,
12700                currentCalendar = kendo.culture().calendar,
12701                firstDayIdx = currentCalendar.firstDay,
12702                days = currentCalendar.days,
12703                names = shiftArray(days.names, firstDayIdx),
12704                short = shiftArray(days.namesShort, firstDayIdx),
12705                start = calendar.firstVisibleDay(date),
12706                firstDayOfMonth = that.first(date),
12707                lastDayOfMonth = that.last(date),
12708                toDateString = that.toDateString,
12709                today = new DATE(),
12710                html = '<table class="k-content" cellspacing="0"><thead><tr>';
12711
12712                for (; idx < 7; idx++) {
12713                    html += '<th scope="col" title="' + names[idx] + '">' + short[idx] + '</th>';
12714                }
12715
12716                today = +new DATE(today.getFullYear(), today.getMonth(), today.getDate());
12717
12718                return view({
12719                    cells: 42,
12720                    perRow: 7,
12721                    html: html += "</tr></thead><tbody><tr>",
12722                    start: new DATE(start.getFullYear(), start.getMonth(), start.getDate()),
12723                    min: new DATE(min.getFullYear(), min.getMonth(), min.getDate()),
12724                    max: new DATE(max.getFullYear(), max.getMonth(), max.getDate()),
12725                    content: options.content,
12726                    empty: options.empty,
12727                    setter: that.setDate,
12728                    build: function(date, idx) {
12729                        var cssClass = [],
12730                        day = date.getDay();
12731
12732                        if (date < firstDayOfMonth || date > lastDayOfMonth) {
12733                            cssClass.push(OTHERMONTH);
12734                        }
12735
12736                        if (+date === today) {
12737                            cssClass.push("k-today");
12738                        }
12739
12740                        if (day === 0 || day === 6) {
12741                            cssClass.push("k-weekend");
12742                        }
12743
12744                        return {
12745                            date: date,
12746                            ns: kendo.ns,
12747                            title: kendo.toString(date, "D"),
12748                            value: date.getDate(),
12749                            dateString: toDateString(date),
12750                            cssClass: cssClass[0] ? ' class="' + cssClass.join(" ") + '"' : ""
12751                        };
12752                    }
12753                });
12754            },
12755            first: function(date) {
12756                return calendar.firstDayOfMonth(date);
12757            },
12758            last: function(date) {
12759                return new DATE(date.getFullYear(), date.getMonth() + 1, 0);
12760            },
12761            compare: function(date1, date2) {
12762                var result,
12763                month1 = date1.getMonth(),
12764                year1 = date1.getFullYear(),
12765                month2 = date2.getMonth(),
12766                year2 = date2.getFullYear();
12767
12768                if (year1 > year2) {
12769                    result = 1;
12770                } else if (year1 < year2) {
12771                    result = -1;
12772                } else {
12773                    result = month1 == month2 ? 0 : month1 > month2 ? 1 : -1;
12774                }
12775
12776                return result;
12777            },
12778            setDate: function(date, value) {
12779                if (value instanceof DATE) {
12780                    date.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
12781                } else {
12782                    calendar.setTime(date, value * MS_PER_DAY);
12783                }
12784            },
12785            toDateString: function(date) {
12786                return date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate();
12787            }
12788        },
12789        {
12790            name: "year",
12791            title: function(date) {
12792                return date.getFullYear();
12793            },
12794            content: function(options) {
12795                var namesAbbr = kendo.culture().calendar.months.namesAbbr,
12796                toDateString = this.toDateString,
12797                min = options.min,
12798                max = options.max;
12799
12800                return view({
12801                    min: new DATE(min.getFullYear(), min.getMonth(), 1),
12802                    max: new DATE(max.getFullYear(), max.getMonth(), 1),
12803                    start: new DATE(options.date.getFullYear(), 0, 1),
12804                    setter: this.setDate,
12805                    build: function(date) {
12806                        return {
12807                            value: namesAbbr[date.getMonth()],
12808                            ns: kendo.ns,
12809                            dateString: toDateString(date),
12810                            cssClass: ""
12811                        };
12812                    }
12813                });
12814            },
12815            first: function(date) {
12816                return new DATE(date.getFullYear(), 0, date.getDate());
12817            },
12818            last: function(date) {
12819                return new DATE(date.getFullYear(), 11, date.getDate());
12820            },
12821            compare: function(date1, date2){
12822                return compare(date1, date2);
12823            },
12824            setDate: function(date, value) {
12825                if (value instanceof DATE) {
12826                    date.setFullYear(value.getFullYear(),
12827                    value.getMonth(),
12828                    date.getDate());
12829                } else {
12830                    var month = date.getMonth() + value;
12831
12832                    date.setMonth(month);
12833
12834                    if (month > 11) {
12835                        month -= 12;
12836                    }
12837
12838                    if (month > 0 && date.getMonth() != month) {
12839                        date.setDate(0);
12840                    }
12841                }
12842            },
12843            toDateString: function(date) {
12844                return date.getFullYear() + "/" + date.getMonth() + "/1";
12845            }
12846        },
12847        {
12848            name: "decade",
12849            title: function(date) {
12850                var start = date.getFullYear();
12851
12852                start = start - start % 10;
12853
12854                return start + "-" + (start + 9);
12855            },
12856            content: function(options) {
12857                var year = options.date.getFullYear(),
12858                toDateString = this.toDateString;
12859
12860                return view({
12861                    start: new DATE(year - year % 10 - 1, 0, 1),
12862                    min: new DATE(options.min.getFullYear(), 0, 1),
12863                    max: new DATE(options.max.getFullYear(), 0, 1),
12864                    setter: this.setDate,
12865                    build: function(date, idx) {
12866                        return {
12867                            value: date.getFullYear(),
12868                            ns: kendo.ns,
12869                            dateString: toDateString(date),
12870                            cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
12871                        };
12872                    }
12873                });
12874            },
12875            first: function(date) {
12876                var year = date.getFullYear();
12877                return new DATE(year - year % 10, date.getMonth(), date.getDate());
12878            },
12879            last: function(date) {
12880                var year = date.getFullYear();
12881                return new DATE(year - year % 10 + 9, date.getMonth(), date.getDate());
12882            },
12883            compare: function(date1, date2) {
12884                return compare(date1, date2, 10);
12885            },
12886            setDate: function(date, value) {
12887                setDate(date, value, 1);
12888            },
12889            toDateString: function(date) {
12890                return date.getFullYear() + "/0/1";
12891            }
12892        },
12893        {
12894            name: CENTURY,
12895            title: function(date) {
12896                var start = date.getFullYear();
12897
12898                start = start - start % 100;
12899
12900                return start + "-" + (start + 99);
12901            },
12902            content: function(options) {
12903                var year = options.date.getFullYear(),
12904                minYear = options.min.getFullYear(),
12905                maxYear = options.max.getFullYear(),
12906                toDateString = this.toDateString;
12907
12908                minYear = minYear - minYear % 10;
12909                maxYear = maxYear - maxYear % 10;
12910
12911                if (maxYear - minYear < 10) {
12912                    maxYear = minYear + 9;
12913                }
12914
12915                return view({
12916                    start: new DATE(year - year % 100 - 10, 0, 1),
12917                    min: new DATE(minYear, 0, 1),
12918                    max: new DATE(maxYear, 0, 1),
12919                    setter: this.setDate,
12920                    build: function(date, idx) {
12921                        var year = date.getFullYear();
12922                        return {
12923                            value: year + " - " + (year + 9),
12924                            ns: kendo.ns,
12925                            dateString: toDateString(date),
12926                            cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
12927                        };
12928                    }
12929                });
12930            },
12931            first: function(date) {
12932                var year = date.getFullYear();
12933                return new DATE(year - year % 100, date.getMonth(), date.getDate());
12934            },
12935            last: function(date) {
12936                var year = date.getFullYear();
12937                return new DATE(year - year % 100 + 99, date.getMonth(), date.getDate());
12938            },
12939            compare: function(date1, date2) {
12940                return compare(date1, date2, 100);
12941            },
12942            setDate: function(date, value) {
12943                setDate(date, value, 10);
12944            },
12945            toDateString: function(date) {
12946                var year = date.getFullYear();
12947                return (year - year % 10) + "/0/1";
12948            }
12949        }]
12950    };
12951
12952    function view(options) {
12953        var idx = 0,
12954            data,
12955            min = options.min,
12956            max = options.max,
12957            start = options.start,
12958            setter = options.setter,
12959            build = options.build,
12960            length = options.cells || 12,
12961            cellsPerRow = options.perRow || 4,
12962            content = options.content || cellTemplate,
12963            empty = options.empty || emptyCellTemplate,
12964            html = options.html || '<table class="k-content k-meta-view" cellspacing="0"><tbody><tr>';
12965
12966        for(; idx < length; idx++) {
12967            if (idx > 0 && idx % cellsPerRow === 0) {
12968                html += "</tr><tr>";
12969            }
12970
12971            data = build(start, idx);
12972
12973            html += isInRange(start, min, max) ? content(data) : empty(data);
12974
12975            setter(start, 1);
12976        }
12977
12978        return html + "</tr></tbody></table>";
12979    }
12980
12981    function compare(date1, date2, modifier) {
12982        var year1 = date1.getFullYear(),
12983            start  = date2.getFullYear(),
12984            end = start,
12985            result = 0;
12986
12987        if (modifier) {
12988            start = start - start % modifier;
12989            end = start - start % modifier + modifier - 1;
12990        }
12991
12992        if (year1 > end) {
12993            result = 1;
12994        } else if (year1 < start) {
12995            result = -1;
12996        }
12997
12998        return result;
12999    }
13000
13001    function restrictValue (value, min, max) {
13002        var today = new DATE();
13003
13004        today = new DATE(today.getFullYear(), today.getMonth(), today.getDate());
13005
13006        if (value) {
13007            today = new DATE(value);
13008        }
13009
13010        if (min > today) {
13011            today = new DATE(min);
13012        } else if (max < today) {
13013            today = new DATE(max);
13014        }
13015        return today;
13016    }
13017
13018    function isInRange(date, min, max) {
13019        return +date >
13019= +min && +date <= +max;
13020    }
13021
13022    function shiftArray(array, idx) {
13023        return array.slice(idx).concat(array.slice(0, idx));
13024    }
13025
13026    function setDate(date, value, multiplier) {
13027        value = value instanceof DATE ? value.getFullYear() : date.getFullYear() + multiplier * value;
13028        date.setFullYear(value);
13029    }
13030
13031    function mouseenter() {
13032        $(this).addClass(HOVER);
13033    }
13034
13035    function mouseleave() {
13036        $(this).removeClass(HOVER);
13037    }
13038
13039    function prevent (e) {
13040        e.preventDefault();
13041    }
13042
13043    function validate(options) {
13044        var start = views[options.start],
13045            depth = views[options.depth],
13046            format = options.format || kendo.culture().calendar.patterns.d;
13047
13048        if (isNaN(start)) {
13049            start = 0;
13050            options.start = MONTH;
13051        }
13052
13053        if (depth === undefined || depth > start) {
13054            options.depth = MONTH;
13055        }
13056
13057        if (format.slice(0,3) === "{0:") {
13058            format = format.slice(3, format.length - 1);
13059        }
13060
13061        options.format = format;
13062    }
13063
13064    function makeUnselectable(element) {
13065        if (isIE8) {
13066            element.find("*").attr("unselectable", "on");
13067        }
13068    }
13069
13070    calendar.makeUnselectable =  makeUnselectable;
13071    calendar.restrictValue = restrictValue;
13072    calendar.isInRange = isInRange;
13073    calendar.validate = validate;
13074    calendar.viewsEnum = views;
13075
13076    kendo.calendar = calendar;
13077})(jQuery);
13078(function($, undefined) {
13079    /**
13080     * @name kendo.ui.DatePicker.Description
13081     *
13082     * @section
13083     * <p>
13084     *  The <b>DatePicker</b> allows the end user to select a date from a
13085     *  calendar or by direct input. It supports custom templates for "month"
13086     *  view, configurable options for min and max date, start view and the
13087     *  depth of the navigation.
13088     * </p>
13089     * <h3>Getting Started</h3>
13090     *
13091     * @exampleTitle Creating a DatePicker from existing input element
13092     * @example
13093     * <input id="datePicker" />
13094     *
13095     * @exampleTitle DatePicker initialization
13096     * @example
13097     * $(document).ready(function(){
13098     *  $("#datePicker").kendoDatePicker();
13099     * });
13100     *
13101     * @section
13102     * <p>
13103     *  When a <b>DatePicker</b> is initialized, it will be displayed at the
13104     *  location of the target HTML element.
13105     * </p>
13106     * <h3>Configuring DatePicker Behaviors</h3>
13107     * <p>
13108     *  The <b>DatePicker</b> provides configuration options that can be set
13109     *  during initialization. Among the properties that can be controlled:
13110     * </p>
13111     * <ul>
13112     *  <li>Selected date</li>
13113     *  <li>Minimum and/or maximum date</li>
13114     *  <li>Define format</li>
13115     *  <li>Start view</li>
13116     *  <li>Navigation depth (last view to which end user can navigate)</li>
13117     * </ul>
13118     *
13119     * @exampleTitle Create DatePicker with a selected date and a defined
13120     * minimum and maximum date
13121     * @example
13122     * $(document).ready(function(){
13123     *  $("#datePicker").kendoDatePicker({
13124     *   value: new Date(),
13125     *   min: new Date(1950, 0, 1),
13126     *   max: new Date(2049, 11, 31)
13127     *  })
13128     * });
13129     *
13130     * @section
13131     * <p>
13132     *  DatePicker will set the value only if the entered date is valid and
13133     *  within the defined range.
13134     * </p>
13135     * @section
13136     * <h3>Defining a Start View and Navigation Depth</h3>
13137     * <p>
13138     *  The first rendered view can be defined with "start" option.
13139     *  Navigation depth can be controlled with "depth" option. Predefined
13140     *  views are:
13141     * </p>
13142     * <ul>
13143     *  <li>"month" - shows the days from the month</li>
13144     *  <li>"year" - shows the months of the year</li>
13145     *  <li>"decade" - shows the years from the decade</li>
13146     *  <li>"century" - shows the decades from the century</li>
13147     * </ul>
13148     *
13149     * @exampleTitle Create a DatePicker for selecting a month
13150     * @example
13151     * $("#datePicker").kendoDatePicker({
13152     *  start: "year",
13153     *  depth: "year"
13154     * });
13155     *
13156     * @section
13157     * <h3>Accessing an Existing DatePicker</h3>
13158     * <p>
13159     *  You can reference an existing <b>DatePicker</b> instance via
13160     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
13161     *  Once a reference has been established, you can use the API to control
13162     *  its behavior.
13163     * </p>
13164     *
13165     * @exampleTitle Accessing an existing DatePicker instance
13166     * @example
13167     * var datePicker = $("#datePicker").data("kendoDatePicker");
13168     *
13169     */
13170    var kendo = window.kendo,
13171    ui = kendo.ui,
13172    touch = kendo.support.touch,
13173    Widget = ui.Widget,
13174    parse = kendo.parseDate,
13175    keys = kendo.keys,
13176    template = kendo.template,
13177    DIV = "<div />",
13178    SPAN = "<span />",
13179    CLICK = (touch ? "touchend" : "click"),
13180    CLICK_DATEPICKER = CLICK + ".datepicker",
13181    OPEN = "open",
13182    CLOSE = "close",
13183    CHANGE = "change",
13184    NAVIGATE = "navigate",
13185    DATEVIEW = "dateView",
13186    DISABLED = "disabled",
13187    DEFAULT = "k-state-default",
13188    FOCUSED = "k-state-focused",
13189    SELECTED = "k-state-selected",
13190    STATEDISABLED = "k-state-disabled",
13191    HOVER = "k-state-hover",
13192    HOVEREVENTS = "mouseenter mouseleave",
13193    MOUSEDOWN = (touch ? "touchstart" : "mousedown"),
13194    MIN = "min",
13195    MAX = "max",
13196    MONTH = "month",
13197    FIRST = "first",
13198    calendar = kendo.calendar,
13199    isInRange = calendar.isInRange,
13200    restrictValue = calendar.restrictValue,
13201    extend = $.extend,
13202    proxy = $.proxy,
13203    DATE = Date;
13204
13205    var DateView = function(options) {
13206        var that = this,
13207            body = document.body,
13208            sharedCalendar = DatePicker.sharedCalendar;
13209
13210        if (!sharedCalendar) {
13211            sharedCalendar = DatePicker.sharedCalendar = new ui.Calendar($(DIV).hide().appendTo(body));
13212            calendar.makeUnselectable(sharedCalendar.element);
13213        }
13214
13215        that.calendar = sharedCalendar;
13216        that.options = options = options || {};
13217        that.popup = new ui.Popup($(DIV).addClass("k-calendar-container").appendTo(body), extend(options.popup, options));
13218
13219        that._templates();
13220
13221        that.value(options.value);
13222    };
13223
13224    DateView.prototype = {
13225        _calendar: function() {
13226            var that = this,
13227                popup = that.popup,
13228                options = that.options,
13229                calendar = that.calendar,
13230                element = calendar.element;
13231
13232            if (element.data(DATEVIEW) !== that) {
13233
13234                element.appendTo(popup.element)
13235                       .data(DATEVIEW, that)
13236                       .undelegate(CLICK_DATEPICKER)
13237                       .delegate("td:has(.k-link)", CLICK_DATEPICKER, proxy(that._click, that))
13238                       .unbind(MOUSEDOWN)
13239                       .bind(MOUSEDOWN, function(e) { e.preventDefault(); })
13240                       .show();
13241
13242                calendar.unbind(CHANGE)
13243                        .bind(CHANGE, options);
13244
13245                if (!touch) {
13246                    calendar.unbind(NAVIGATE)
13247                            .bind(NAVIGATE, proxy(that._navigate, that));
13248                }
13249
13250                calendar.month = that.month;
13251                calendar.options.depth = options.depth;
13252
13253                calendar._footer(that.footer);
13254
13255                calendar.min(options.min);
13256                calendar.max(options.max);
13257
13258                calendar.navigate(that._value, options.start);
13259                that.value(that._value);
13260            }
13261        },
13262
13263        open: function() {
13264            var that = this;
13265
13266            that._calendar();
13267            that.popup.open();
13268        },
13269
13270        close: function() {
13271            this.popup.close();
13272        },
13273
13274        min: function(value) {
13275            this._option(MIN, value);
13276        },
13277
13278        max: function(value) {
13279            this._option(MAX, value);
13280        },
13281
13282        toggle: function() {
13283            var that = this;
13284
13285            that[that.popup.visible() ? CLOSE : OPEN]();
13286        },
13287
13288        move: function(e) {
13289            var that = this,
13290                options = that.options,
13291                currentValue = new DATE(that._current),
13292                calendar = that.calendar,
13293                index = calendar._index,
13294                view = calendar._view,
13295                key = e.keyCode,
13296                value, prevent, method;
13297
13298            if (key == keys.ESC) {
13299                that.close();
13300                return;
13301            }
13302
13303            if (e.altKey) {
13304                if (key == keys.DOWN) {
13305                    that.open();
13306                    prevent = true;
13307                } else if (key == keys.UP) {
13308                    that.close();
13309                    prevent = true;
13310                }
13311                return;
13312            }
13313
13314            if (!that.popup.visible() || calendar._table.parent().data("animating")) {
13315                return;
13316            }
13317
13318            if (e.ctrlKey) {
13319                if (key == keys.RIGHT) {
13320                    calendar.navigateToFuture();
13321                    prevent = true;
13322                } else if (key == keys.LEFT) {
13323                    calendar.navigateToPast();
13324                    prevent = true;
13325                } else if (key == keys.UP) {
13326                    calendar.navigateUp();
13327                    prevent = true;
13328                } else if (key == keys.DOWN) {
13329                    that._navigateDown();
13330                    prevent = true;
13331                }
13332            } else {
13333                if (key == keys.RIGHT) {
13334                    value = 1;
13335                    prevent = true;
13336                } else if (key == keys.LEFT) {
13337                    value = -1;
13338                    prevent = true;
13339                } else if (key == keys.UP) {
13340                    value = index === 0 ? -7 : -4;
13341                    prevent = true;
13342                } else if (key == keys.DOWN) {
13343                    value = index === 0 ? 7 : 4;
13344                    prevent = true;
13345                } else if (key == keys.ENTER) {
13346                    that._navigateDown();
13347                    prevent = true;
13348                } else if (key == keys.HOME || key == keys.END) {
13349                    method = key == keys.HOME ? FIRST : "last";
13350                    currentValue = view[method](currentValue);
13351                    prevent = true;
13352                } else if (key == keys.PAGEUP) {
13353                    prevent = true;
13354                    calendar.navigateToPast();
13355                } else if (key == keys.PAGEDOWN) {
13356                    prevent = true;
13357                    calendar.navigateToFuture();
13358                }
13359
13360                if (value || method) {
13361                    if (!method) {
13362                        view.setDate(currentValue, value);
13363                    }
13364
13365                    that._current = currentValue = restrictValue(currentValue, options.min, options.max);
13366                    calendar._focus(currentValue);
13367                }
13368            }
13369
13370            if (prevent) {
13371                e.preventDefault();
13372            }
13373        },
13374
13375        value: function(value) {
13376            var that = this,
13377                calendar = that.calendar,
13378                options = that.options;
13379
13380            that._value = value;
13381            that._current = new DATE(restrictValue(value, options.min, options.max));
13382
13383            if (calendar.element.data(DATEVIEW) === that) {
13384                calendar._focus(that._current);
13385                calendar.value(value);
13386            }
13387        },
13388
13389        _click: function(e) {
13390            if (e.currentTarget.className.indexOf(SELECTED) !== -1) {
13391                this.close();
13392            }
13393        },
13394
13395        _navigate: function() {
13396            var that = this,
13397                calendar = that.calendar;
13398
13399            that._current = new DATE(calendar._current);
13400            calendar._focus(calendar._current);
13401        },
13402
13403        _navigateDown: function() {
13404            var that = this,
13405                calendar = that.calendar,
13406                currentValue = calendar._current,
13407                cell = calendar._table.find("." + FOCUSED),
13408                value = cell.children(":" + FIRST).attr(kendo.attr("value")).split("/");
13409
13410            //Safari cannot create corretly date from "1/1/2090"
13411            value = new DATE(value[0], value[1], value[2]);
13412
13413            if (!cell[0] || cell.hasClass(SELECTED)) {
13414                that.close();
13415                return;
13416            }
13417
13418            calendar._view.setDate(currentValue, value);
13419            calendar.navigateDown(currentValue);
13420        },
13421
13422        _option: function(option, value) {
13423            var that = this,
13424                options = that.options,
13425                calendar = that.calendar;
13426
13427            options[option] = value;
13428
13429            if (calendar.element.data(DATEVIEW) === that) {
13430                calendar[option](value);
13431            }
13432        },
13433
13434        _templates: function() {
13435            var that = this,
13436                options = that.options,
13437                footer = options.footer,
13438                month = options.month || {},
13439                content = month.content,
13440                empty = month.empty;
13441
13442            that.month = {
13443                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 }),
13444                empty: template("<td>" + (empty || "&nbsp;") + "</td>", { useWithBlock: !!empty })
13445            };
13446
13447            if (footer) {
13448                that.footer = template(footer, { useWithBlock: false });
13449            }
13450        }
13451    };
13452
13453    kendo.DateView = DateView;
13454
13455    var DatePicker = Widget.extend(/** @lends kendo.ui.DatePicker.prototype */{
13456        /**
13457         * @constructs
13458         * @extends kendo.ui.Widget
13459         * @param {DomElement} element DOM element
13460         * @param {Object} options Configuration options.
13461         * @option {Date} [value] <null> Specifies the selected date.
13462         * _example
13463         * // set the selected value to January 1st, 2011
13464         * $("#datePicker").kendoDatePicker({
13465         *  value: new Date(2011, 0, 1)
13466         * });
13467         * _exampleTitle To set after initialization
13468         * _example
13469         * // get a reference to the datePicker widget
13470         * var datePicker = $("#datePicker").data("kendoDatePicker");
13471         * // set the selected date on the datePicker to January 1st, 2011
13472         * datePicker.value(new Date(2011, 0, 1));
13473         * @option {Date} [min] <Date(1900, 0, 1)> Specifies the minimum date that the calendar can show.
13474         * _example
13475         * // set the min date to Jan 1st, 2011
13476         * $("#datePicker").kendoDatePicker({
13477         *  min: new Date(2011, 0, 1)
13478         * });
13479         * _exampleTitle To set after initialization
13480         * _example
13481         * // get a reference to the datePicker widget
13482         * var datePicker = $("#datePicker").data("kendoDatePicker");
13483         * // set the min date to Jan 1st, 2011
13484         * datePicker.min(new Date(2011, 0, 1));
13485         * @option {Date} [max] <Date(2099, 11, 31)> Specifies the maximum date, which the calendar can show.
13486         * _example
13487         * $("#datePicker").kendoDatePicker({
13488         *  max: new Date(2013, 0, 1) // sets max date to Jan 1st, 2013
13489         * });
13490         * _exampleTitle To set after initialization
13491         * _example
13492         * var datePicker = $("#datePicker").data("kendoDatePicker");
13493         * // set the max date to Jan 1st, 2013
13494         * datePicker.max(new Date(2013,0, 1));
13495         * @option {String} [format] <MM/dd/yyyy> Specifies the format, which is used to parse value set with value() method.
13496         * _example
13497         * $("#datePicker").kendoDatePicker({
13498         *     format: "yyyy/MM/dd"
13499         * });
13500         * @option {String} [start] <month> Specifies the start view.
13501         * The following settings are available for the <b>start</b> value:
13502         * <div class="details-list">
13503         *    <dl>
13504         *         <dt>
13505         *              <code>"month"</code>
13506         *         </dt>
13507         *         <dd>
13508         *             shows the days of the month
13509         *         </dd>
13510         *         <dt>
13511         *              <code>"year"</code>
13512         *         </dt>
13513         *         <dd>
13514         *              shows the months of the year
13515         *         </dd>
13516         *         <dt>
13517         *              <code>"decade"</code>
13518         *         </dt>
13519         *         <dd>
13520         *              shows the years of the decade
13521         *         </dd>
13522         *         <dt>
13523         *              <code>"century"</code>
13524         *         </dt>
13525         *         <dd>
13526         *              shows the decades from the centery
13527         *         </dd>
13528         *    </dl>
13529         * </div>
13530         * _example
13531         * $("#datePicker").kendoDatePicker({
13532         *     start: "decade" // the datePicker will start with a decade display
13533         * });
13534         * @option {String} [depth] Specifies the navigation depth. The following
13535         * settings are available for the <b>depth</b> value:
13536         * <div class="details-list">
13537         *    <dl>
13538         *         <dt>
13539         *              <code>"month"</code>
13540         *         </dt>
13541         *         <dd>
13542         *             shows the days of the month
13543         *         </dd>
13544         *         <dt>
13545         *              <code>"year"</code>
13546         *         </dt>
13547         *         <dd>
13548         *              shows the months of the year
13549         *         </dd>
13550         *         <dt>
13551         *              <code>"decade"</code>
13552         *         </dt>
13553         *         <dd>
13554         *              shows the years of the decade
13555         *         </dd>
13556         *         <dt>
13557         *              <code>"century"</code>
13558         *         </dt>
13559         *         <dd>
13560         *              shows the decades from the centery
13561         *         </dd>
13562         *    </dl>
13563         * </div>
13564         * _example
13565         * $("#datePicker").kendoDatePicker({
13566         *     start: "decade",
13567         *     depth: "year" // the datePicker will only go to the year level
13568         * });
13569         * @option {Function} [footer] <> Template to be used for rendering the footer of the calendar.
13570         * _example
13571         *  // DatePicker initialization
13572         *  &lt;script&gt;
13573         *      $("#datePicker").kendoDatePicker({
13574         *          footer: kendo.template("Today - #=kendo.toString(data, 'd') #")
13575         *      });
13576         *  &lt;/script&gt;
13577         * @option {Object} [month] <> Templates for the cells rendered in the calendar "month" view.
13578         * @option {Function} [month.content] <> Template to be used for rendering the cells in the calendar "month" view, which are in range.
13579         * _example
13580         *  //template
13581         *  &lt;script id="cellTemplate" type="text/x-kendo-tmpl"&gt;
13582         *      &lt;div class="${ data.value < 10 ? exhibition : party }"&gt;
13583         *      &lt;/div&gt;
13584         *      ${ data.value }
13585         *  &lt;/script&gt;
13586         *
13587         *  //datePicker initialization
13588         *  &lt;script&gt;
13589         *      $("#datePicker").kendoDatePicker({
13590         *          month: {
13591         *             content:  kendo.template($("#cellTemplate").html()),
13592         *          }
13593         *      });
13594         *  &lt;/script&gt;
13595         *
13596         * @option {Function} [month.empty]
13597         * The template used for rendering the cells in the calendar "month" view, which are not in the range between
13598         * the minimum and maximum values.
13599         *
13600         * @option {Object} [animation]
13601         * The animation(s) used for opening and/or closing the pop-up. Setting this value to <strong>false</strong>
13602         * will disable the animation(s).
13603         *
13604         * @option {Object} [animation.open]
13605         * The animation(s) used for displaying of the pop-up.
13606         *
13607         * _exampleTitle Fade-in the pop-up over 300 milliseconds
13608         * _example
13609         * $("#datePicker").kendoDatePicker({
13610         *     animation: {
13611         *         open: {
13612         *             effects: "fadeIn",
13613         *             duration: 300,
13614         *             show: true
13615         *         }
13616         *     }
13617         * });
13618         *
13619         * @option {Object} [animation.close]
13620         * The animation(s) used for hiding of the pop-up.
13621         *
13622         * _exampleTitle Fade-out the pop-up over 300 milliseconds
13623         * _example
13624         * $("#datePicker").kendoDatePicker({
13625         *     animation: {
13626         *         close: {
13627         *             effects: "fadeOut",
13628         *             duration: 300,
13629         *             show: false,
13630         *             hide: true
13631         *         }
13632         *     }
13633         * });
13634         *
13635         */
13636        init: function(element, options) {
13637            var that = this,
13638                dateView;
13639
13640            Widget.fn.init.call(that, element, options);
13641            element = that.element;
13642            options = that.options;
13643
13644            calendar.validate(options);
13645
13646            that._wrapper();
13647
13648            that.dateView = dateView = new DateView(extend({}, options, {
13649                anchor: that.wrapper,
13650                change: function() {
13651                    // calendar is the current scope
13652                    that._change(this.value());
13653                    that.close();
13654                },
13655                close: function(e) {
13656                    if (that.trigger(CLOSE)) {
13657                        e.preventDefault();
13658                    }
13659                },
13660                open: function(e) {
13661                    if (that.trigger(OPEN)) {
13662                        e.preventDefault();
13663                    }
13664                }
13665            }));
13666
13667            that._icon();
13668
13669            element
13670                .addClass("k-input")
13671                .bind({
13672                    keydown: proxy(that._keydown, that),
13673                    focus: function(e) {
13674                        that._inputWrapper.addClass(FOCUSED);
13675                    },
13676                    blur: proxy(that._blur, that)
13677                })
13678                .closest("form")
13679                .bind("reset", function() {
13680                    that.value(element[0].defaultValue);
13681                });
13682
13683            that.enable(!element.is('[disabled]'));
13684            that.value(options.value || that.element.val());
13685
13686            kendo.notify(that);
13687        },
13688        events: [
13689        /**
13690        * Fires when the selected date is changed
13691        * @name kendo.ui.DatePicker#change
13692        * @event
13693        * @param {Event} e
13694        * @example
13695        * $("#datePicker").kendoDatePicker({
13696        *     change: function(e) {
13697        *         // handle event
13698        *     }
13699        * });
13700        * @exampleTitle To set after initialization
13701        * @example
13702        * // get a reference to the datePicker widget
13703        * var datePicker = $("#datePicker").data("kendoDatePicker");
13704        * // bind to the change event
13705        * datePicker.bind("change", function(e) {
13706        *     // handle event
13707        * });
13708        */
13709        /**
13710        * Fires when the calendar is opened
13711        * @name kendo.ui.DatePicker#open
13712        * @event
13713        * @param {Event} e
13714        * @example
13715        * $("#datePicker").kendoDatePicker({
13716        *     open: function(e) {
13717        *         // handle event
13718        *     }
13719        * });
13720        * @exampleTitle To set after initialization
13721        * @example
13722        * // get a reference to the datePicker widget
13723        * var datePicker = $("#datePicker").data("kendoDatePicker");
13724        * // bind to the open event
13725        * datePicker.bind("open", function(e) {
13726        *     // handle event
13727        * });
13728        */
13729        /**
13730        * Fires when the calendar is closed
13731        * @name kendo.ui.DatePicker#close
13732        * @event
13733        * @param {Event} e
13734        * @example
13735        * $("#datePicker").kendoDatePicker({
13736        *     close: function(e) {
13737        *         // handle event
13738        *     }
13739        * });
13740        * @exampleTitle To set after initialization
13741        * @example
13742        * // get a reference to the datePicker widget
13743        * var datePicker = $("#datePicker").data("kendoDatePicker");
13744        * // bind to the close event
13745        * datePicker.bind("close", function(e) {
13746        *     // handle event
13747        * });
13748        */
13749        OPEN,
13750        CLOSE,
13751        CHANGE],
13752        options: {
13753            name: "DatePicker",
13754            value: null,
13755            format: "",
13756            min: new Date(1900, 0, 1),
13757            max: new Date(2099, 11, 31),
13758            footer: '#= kendo.toString(data,"D") #',
13759            start: MONTH,
13760            depth: MONTH,
13761            animation: {},
13762            month : {}
13763        },
13764
13765        setOptions: function(options) {
13766            var that = this;
13767
13768            Widget.fn.setOptions.call(that, options);
13769
13770            calendar.validate(that.options);
13771
13772            extend(that.dateView.options, that.options);
13773        },
13774
13775        /**
13776        * Enable/Disable the datePicker widget.
13777        * @param {Boolean} enable The argument, which defines whether to enable/disable the datePicker.
13778        * @example
13779        * // get a reference to the datepicker widget
13780        * var datePicker = $("#datePicker").data("kendoDatePicker");
13781        *
13782        * // disables the datePicker
13783        * datePicker.enable(false);
13784        *
13785        * // enables the datePicker
13786        * datePicker.enable(true);
13787        */
13788        enable: function(enable) {
13789            var that = this,
13790                icon = that._icon.unbind(CLICK + " " + MOUSEDOWN),
13791                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
13792                element = that.element;
13793
13794            if (enable === false) {
13795                wrapper
13796                    .removeClass(DEFAULT)
13797                    .addClass(STATEDISABLED);
13798
13799                element.attr(DISABLED, DISABLED);
13800            } else {
13801                wrapper
13802                    .addClass(DEFAULT)
13803                    .removeClass(STATEDISABLED)
13804                    .bind(HOVEREVENTS, that._toggleHover);
13805
13806                element
13807                    .removeAttr(DISABLED);
13808
13809                icon.bind(CLICK, proxy(that._click, that))
13810                    .bind(MOUSEDOWN, function(e) { e.preventDefault(); });
13811            }
13812        },
13813
13814        /**
13815        * Opens the calendar.
13816        * @name kendo.ui.DatePicker#open
13817        * @function
13818        * @example
13819        * // get a reference to the datepicker widget
13820        * var datePicker = $("#datePicker").data("kendoDatePicker");
13821        * // open the datepicker
13822        * datePicker.open();
13823        */
13824        open: function() {
13825            this.dateView.open();
13826        },
13827
13828        /**
13829        * Closes the calendar.
13830        * @name kendo.ui.DatePicker#close
13831        * @function
13832        * @example
13833        * // get a reference to the datepicker widget
13834        * var datePicker = $("#datePicker").data("kendoDatePicker");
13835        * // close the datepicker
13836        * datePicker.close();
13837        */
13838        close: function() {
13839            this.dateView.close();
13840        },
13841
13842        /**
13843        * Gets/Sets the min value of the datePicker.
13844        * @param {Date | String} value The min date to set.
13845        * @returns {Date} The min value of the datePicker.
13846        * @example
13847        * // get a reference to the datepicker widget
13848        * var datePicker = $("#datePicker").data("kendoDatePicker");
13849        *
13850        * // get the min value of the datePicker.
13851        * var min = datePicker.min();
13852        *
13853        * // set the min value of the datePicker.
13854        * datePicker.min(new Date(1900, 0, 1));
13855        */
13856        min: function(value) {
13857            return this._option(MIN, value);
13858        },
13859
13860        /**
13861        * Gets/Sets the max value of the datePicker.
13862        * @param {Date | String} value The max date to set.
13863        * @returns {Date} The max value of the datePicker.
13864        * @example
13865        * // get a reference to the datepicker widget
13866        * var datePicker = $("#datePicker").data("kendoDatePicker");
13867        *
13868        * // get the max value of the datePicker.
13869        * var max = datePicker.max();
13870        *
13871        * // set the max value of the datePicker.
13872        * datePicker.max(new Date(1900, 0, 1));
13873        */
13874        max: function(value) {
13875            return this._option(MAX, value);
13876        },
13877
13878        /**
13879        * Gets/Sets the value of the datePicker.
13880        * @param {Date | String} value The value to set.
13881        * @returns {Date} The value of the datePicker.
13882        * @example
13883        * // get a reference to the datepicker widget
13884        * var datePicker = $("#datePicker").data("kendoDatePicker");
13885        *
13886        * // get the value of the datePicker.
13887        * var value = datePicker.value();
13888        *
13889        * // set the value of the datePicker.
13890        * datePicker.value("10/10/2000"); //parse "10/10/2000" date and selects it in the calendar.
13891        */
13892        value: function(value) {
13893            var that = this;
13894
13895            if (value === undefined) {
13896                return that._value;
13897            }
13898
13899            that._old = that._update(value);
13900        },
13901
13902        _toggleHover: function(e) {
13903            if (!touch) {
13904                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
13905            }
13906        },
13907
13908        _blur: function() {
13909            var that = this;
13910
13911            that.close();
13912            that._change(that.element.val());
13913            that._inputWrapper.removeClass(FOCUSED);
13914        },
13915
13916        _click: function() {
13917            var that = this,
13918                element = that.element;
13919
13920            if (!touch && element[0] !== document.activeElement) {
13921                element.focus();
13922            }
13923
13924            that.dateView.toggle();
13925        },
13926
13927        _change: function(value) {
13928            var that = this;
13929
13930            value = that._update(value);
13931
13932            if (+that._old != +value) {
13933                that._old = value;
13934                that.trigger(CHANGE);
13935
13936                // trigger the DOM change event so any subscriber gets notified
13937                that.element.trigger(CHANGE);
13938            }
13939        },
13940
13941        _keydown: function(e) {
13942            var that = this,
13943                dateView = that.dateView;
13944
13945            if (!dateView.popup.visible() && e.keyCode == keys.ENTER) {
13946                that._change(that.element.val());
13947            } else {
13948                dateView.move(e);
13949            }
13950        },
13951
13952        _icon: function() {
13953            var that = this,
13954                element = that.element,
13955                icon;
13956
13957            icon = element.next("span.k-select");
13958
13959            if (!icon[0]) {
13960                icon = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-icon-calendar">select</span></span>').insertAfter(element);
13961            }
13962
13963            that._icon = icon;
13964        },
13965
13966        _option: function(option, value) {
13967            var that = this,
13968                options = that.options;
13969
13970            if (value === undefined) {
13971                return options[option];
13972            }
13973
13974            value = parse(value, options.format);
13975
13976            if (!value) {
13977                return;
13978            }
13979
13980            options[option] = new DATE(value);
13981            that.dateView[option](value);
13982        },
13983
13984        _update: function(value) {
13985            var that = this,
13986                options = that.options,
13987                format = options.format,
13988                date = parse(value, format);
13989
13990            if (!isInRange(date, options.min, options.max)) {
13991                date = null;
13992            }
13993
13994            that._value = date;
13995            that.dateView.value(date);
13996            that.element.val(date ? kendo.toString(date, format) : value);
13997
13998            return date;
13999        },
14000
14001        _wrapper: function() {
14002            var that = this,
14003                element = that.element,
14004                wrapper;
14005
14006            wrapper = element.parents(".k-datepicker");
14007
14008            if (!wrapper[0]) {
14009                wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
14010                wrapper = wrapper.wrap(SPAN).parent();
14011            }
14012
14013            wrapper[0].style.cssText = element[0].style.cssText;
14014            element.css({
14015                width: "100%",
14016                height: element[0].style.height
14017            });
14018
14019            that.wrapper = wrapper.addClass("k-widget k-datepicker k-header");
14020            that._inputWrapper = $(wrapper[0].firstChild);
14021        }
14022    });
14023
14024    ui.plugin(DatePicker);
14025
14026})(jQuery);
14027(function ($, undefined) {
14028    /**
14029     * @name kendo.ui.AutoComplete.Description
14030     *
14031     * @section
14032     * <p>
14033     *  The <b>AutoComplete</b> provides suggestions depending on the typed
14034     *  text. It also allows multiple value entries. The suggestions shown by
14035     *  the <b>AutoComplete</b> can come from a local Array or from a remote
14036     *  data service.
14037     * </p>
14038     * <h3>Getting Started</h3>
14039     *
14040     * @exampleTitle Create a HTML input element
14041     * @example
14042     * <input id="autoComplete" />
14043     *
14044     * @exampleTitle Initialize the AutoComplete using a jQuery selector
14045     * @example
14046     * $(document).ready(function() {
14047     *  $("#autoComplete").kendoAutoComplete(["Item1", "Item2"]);
14048     * });
14049     *
14050     * @section <h3>AutoComplete Suggestions</h3>
14051     * <p>
14052     *  There are two primary ways to provide the <b>AutoComplete</b>
14053     *  suggestions:
14054     * </p>
14055     * <ol>
14056     *  <li>From a local array</li>
14057     *  <li>From a remote data service</li>
14058     * </ol>
14059     * <p>
14060     *  Locally defined values are best for small, fixed sets of suggestions.
14061     *  Remote suggestions should be used for larger data sets. When used
14062     *  with the <a href="../datasource/index.html">DataSource</a> component,
14063     *  filtering large remote data services can be pushed to the server as
14064     *  well, maximizing client-side performance.
14065     * </p>
14066     * <h3>Local Suggestions</h3>
14067     * <p>
14068     *  To configure and provide <b>AutoComplete</b> suggestions locally, you
14069     *  can either pass an array directly to its constructor or you can set
14070     *  the dataSource property to an local array.
14071     * </p>
14072     *
14073     * @exampleTitle Directly initialize suggestions in constructor
14074     * @example
14075     * $("#autoComplete").kendoAutoComplete(["Item1", "Item2", "Item3"]);
14076     *
14077     * @exampleTitle Using dataSource property to bind to local Array
14078     * @example
14079     * var data = ["Item1", "Item2", "Item3"];
14080     * $("#autoComplete").kendoAutoComplete({
14081     *    dataSource: data
14082     * });
14083     *
14084     * @section
14085     * <h3>Remote Suggestions</h3>
14086     * <p>
14087     *  The easiest way to bind an <b>AutoComplete</b> to remote
14088     *  suggestions is to use the
14089     *  <a href="../datasource/index.html">DataSource</a> component; an
14090     *  abstraction for local and remote data. The <b>DataSource</b>
14091     *  component can be used to serve data from a variety of data services,
14092     *  such as
14093     *  <a href="http://en.wikipedia.org/wiki/XML">XML</a>,
14094     *  <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>, and
14095     *  <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>.
14096     * </p>
14097     *
14098     * @exampleTitle Using the Kendo UI Web DataSource component to bind to
14099     * remote suggestions with OData
14100     * @example
14101     * $(document).ready(function(){
14102     *  $("#autoComplete").kendoAutoComplete({
14103     *   minLength: 3,
14104     *   dataTextField: "Name", // JSON property name to use
14105     *   dataSource: new kendo.data.DataSource({
14106     *    type: "odata", // specifies data protocol
14107     *    pageSize: 10, // limits result set
14108     *    transport: {
14109     *     read: "http://odata.netflix.com/Catalog/Titles"
14110     *    }
14111     *   })
14112     *  })
14113     * });
14114     *
14115     * @exampleTitle Using the Kendo UI Web DataSource to bind to JSONP
14116     * suggestions
14117     * @example
14118     * $(document).ready(function(){
14119     *  $("#autoComplete").kendoAutoComplete({
14120     *   minLength:6,
14121     *   dataTextField:"title",
14122     *   filter: "contains",
14123     *   dataSource: new kendo.data.DataSource({
14124     *    transport: {
14125     *     read: {
14126     *      url: "http://api.geonames.org/wikipediaSearchJSON",
14127     *      data: {
14128     *       q: function(){
14129     *        return $("#autoComplete").data("kendoAutoComplete").value();
14130     *       },
14131     *       maxRows: 10,
14132     *       username: "demo"
14133     *      }
14134     *     }
14135     *    },
14136     *    schema: {
14137     *     data:"geonames"
14138     *    }
14139     *   }),
14140     *   change: function(){
14141     *    this.dataSource.read();
14142     *   }
14143     *  })
14144     * });
14145     *
14146     * @section
14147     * <h3>Accessing an Existing AutoComplete</h3>
14148     * <p>
14149     *  You can reference an existing <b>AutoComplete</b> instance via
14150     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
14151     *  Once a reference has been established, you can use the API to control
14152     *  its behavior.
14153     * </p>
14154     *
14155     * @exampleTitle Accessing an existing AutoComplete instance
14156     * @example
14157     * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
14158     *
14159     */
14160    var kendo = window.kendo,
14161        support = kendo.support,
14162        placeholderSupported = support.placeholder,
14163        ui = kendo.ui,
14164        keys = kendo.keys,
14165        DataSource = kendo.data.DataSource,
14166        List = ui.List,
14167        CHANGE = "change",
14168        DEFAULT = "k-state-default",
14169        DISABLED = "disabled",
14170        FOCUSED = "k-state-focused",
14171        SELECTED = "k-state-selected",
14172        STATEDISABLED = "k-state-disabled",
14173        HOVER = "k-state-hover",
14174        HOVEREVENTS = "mouseenter mouseleave",
14175        caretPosition = List.caret,
14176        selectText = List.selectText,
14177        proxy = $.proxy;
14178
14179    function indexOfWordAtCaret(caret, text, separator) {
14180        return separator ? text.substring(0, caret).split(separator).length - 1 : 0;
14181    }
14182
14183    function wordAtCaret(caret, text, separator) {
14184        return text.split(separator)[indexOfWordAtCaret(caret, text, separator)];
14185    }
14186
14187    function replaceWordAtCaret(caret, text, word, separator) {
14188        var words = text.split(separator);
14189
14190        words.splice(indexOfWordAtCaret(caret, text, separator), 1, word);
14191
14192        if (separator && words[words.length - 1] !== "") {
14193            words.push("");
14194        }
14195
14196        return words.join(separator);
14197    }
14198
14199    function moveCaretAtEnd(element) {
14200        var length = element.value.length;
14201
14202        selectText(element, length, length);
14203    }
14204
14205    var AutoComplete = List.extend/** @lends kendo.ui.AutoComplete.prototype */({
14206        /**
14207        * @constructs
14208        * @extends kendo.ui.List
14209        * @param {DomElement} element DOM element
14210        * @param {Object} options Configuration options.
14211        * @option {Object | kendo.data.DataSource } [dataSource] The set of data that the AutoComplete will be bound to.
14212        *  Either a local JavaScript object, or an instance of the Kendo UI DataSource.
14213        * _exampleTitle Bind to local array
14214        * _example
14215        * var items = [ { Name: "Item 1" }, { Name: "Item 2"} ];
14216        * $("#autoComplete").kendoAutoComplete({ dataSource: items });
14217        * _exampleTitle Bind to a remote URL
14218        * _example
14219        * $("#autocomplete").kendoAutoComplete({
14220        *     dataSource: new kendo.data.DataSource({
14221        *         transport: {
14222        *             read: "Items/GetData" // url to server method which returns data
14223        *         }
14224        *     });
14225        * });
14226        * @option {Boolean} [enable] <true> Controls whether the AutoComplete should be initially enabled.
14227        * _example
14228        * // disable the autocomplete when it is created (enabled by default)
14229        * $("#autoComplete").kendoAutoComplete({
14230        *     enable: false
14231        * });
14232        * @option {Boolean} [highlightFirst] <true> Controls whether the first item will be automatically highlighted.
14233        * _example
14234        * $("#autocomplete").kendoAutoComplete({
14235        *     highlightFirst: false //no of the suggested items will be highlighted
14236        * });
14237        * @option {Boolean} [suggest] <false> Controls whether the AutoComplete should automatically auto-type the rest of text.
14238        * _example
14239        * // turn on auto-typing (off by default)
14240        * $("#autoComplete").kendoAutoComplete({
14241        *     suggest: true
14242        * });
14243        * @option {Number} [delay] <200> Specifies the delay in ms after which the AutoComplete will start filtering the dataSource.
14244        * _example
14245        * // set the delay to 500 milliseconds
14246        * $("#autoComplete").kendoAutoComplete({
14247        *     delay: 500
14248        * });
14249        * @option {Number} [minLength] <1> Specifies the minimum number of characters that should be typed before the AutoComplete queries
14250        * the dataSource.
14251        * _example
14252        * // wait until the user types 3 characters before querying the server
14253        * $("#autoComplete").kendoAutoComplete({
14254        *     minLength: 3
14255        * });
14256        * @option {String} [dataTextField] <null> Sets the field of the data item that provides the text content of the list items.
14257        * _example
14258        * var items = [ { ID: 1, Name: "Item 1" }, { ID: 2, Name: "Item 2"} ];
14259        * $("#autoComplete").kendoAutoComplete({
14260        *     dataSource: items,
14261        *     dataTextField: "Name"
14262        * });
14263        * @option {String} [filter] <"startswith"> Defines the type of filtration. This value is handled by the remote data source.
14264        * _example
14265        * // send a filter value of 'contains' to the server
14266        * $("#autoComplete").kendoAutoComplete({
14267        *     filter: 'contains'
14268        * });
14269        * @option {String} [ignoreCase] <true> Defines whether the filtration should be case sensitive.
14270        * _example
14271        * $("#autoComplete").kendoAutoComplete({
14272        *     filter: 'contains',
14273        *     ignoreCase: false //now filtration will be case sensitive
14274        * });
14275        * @option {Number} [height] <200> Sets the height of the drop-down list in pixels.
14276        * _example
14277        * // set the height of the drop-down list that appears when the autocomplete is activated to 500px
14278        * $("#autoComplete").kendoAutoComplete({
14279        *     height: 500
14280        * });
14281        * @option {String} [separator] <""> Sets the separator for completion. Empty by default, allowing for only one completion.
14282        * _example
14283        * // set completion separator to ,
14284        * $("#autoComplete").kendoAutoComplete({
14285        *     separator: ", "
14286        * });
14287        * @option {Function} [template] Template to be used for rendering the items in the list.
14288        * _example
14289        *  //template
14290        * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
14291        *       # if (data.BoxArt.SmallUrl) { #
14292        *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
14293        *       # } else { #
14294        *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
14295        *       # } #
14296        *  &lt;/script&gt;
14297        *
14298        *  //autocomplete initialization
14299        *  &lt;script&gt;
14300        *      $("#autocomplete").kendoAutoComplete({
14301        *          dataSource: dataSource,
14302        *          dataTextField: "Name",
14303        *          dataValueField: "Id",
14304        *          template: kendo.template($("#template").html())
14305        *      });
14306        *  &lt;/script&gt;
14307        * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
14308        * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
14309        * _example
14310        *  //autocomplete initialization
14311        *  &lt;script&gt;
14312        *      $("#autocomplete").kendoAutoComplete({
14313        *          dataSource: dataSource,
14314        *          animation: {
14315        *             open: {
14316        *                 effects: "fadeIn",
14317        *                 duration: 300,
14318        *                 show: true
14319        *             }
14320        *          }
14321        *      });
14322        *  &lt;/script&gt;
14323        * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
14324        * _example
14325        *  //autocomplete initialization
14326        *  &lt;script&gt;
14327        *      $("#autocomplete").kendoAutoComplete({
14328        *          dataSource: dataSource,
14329        *          animation: {
14330        *             close: {
14331        *                 effects: "fadeOut",
14332        *                 duration: 300,
14333        *                 hide: true
14334        *                 show: false
14335        *             }
14336        *          }
14337        *      });
14338        *  &lt;/script&gt;
14339        *  @option {String} [placeholder] <""> A string that appears in the textbox when it has no value.
14340        *  _example
14341        *  //autocomplete initialization
14342        *  <script>
14343        *      $("#autocomplete").kendoAutoComplete({
14344        *          dataSource: dataSource,
14345        *          placeholder: "Enter value..."
14346        *      });
14347        *  </script>
14348        *  _example
14349        *  <input id="autocomplete" placeholder="Enter value..." />
14350        *
14351        *  //combobox initialization
14352        *  <script>
14353        *      $("#autocomplete").kendoAutoComplete({
14354        *          dataSource: dataSource
14355        *      });
14356        *  </script>
14357        */
14358        init: function (element, options) {
14359            var that = this, wrapper;
14360
14361            options = $.isArray(options) ? { dataSource: options} : options;
14362
14363            List.fn.init.call(that, element, options);
14364
14365            element = that.element;
14366            options = that.options;
14367
14368            options.placeholder = options.placeholder || element.attr("placeholder");
14369            if (placeholderSupported) {
14370                element.attr("placeholder", options.placeholder);
14371            }
14372
14373            that._wrapper();
14374
14375            that._accessors();
14376
14377            that._dataSource();
14378
14379            element[0].type = "text";
14380            wrapper = that.wrapper;
14381
14382            element
14383                .attr("autocomplete", "off")
14384                .addClass("k-input")
14385                .bind({
14386                    keydown: proxy(that._keydown, that),
14387                    paste: proxy(that._search, that),
14388                    focus: function () {
14389                        that._prev = that.value();
14390                        that._placeholder(false);
14391                        wrapper.addClass(FOCUSED);
14392                        clearTimeout(that._bluring);
14393                    },
14394                    blur: function () {
14395                        that._change();
14396                        that._placeholder();
14397                        wrapper.removeClass(FOCUSED);
14398                    }
14399                });
14400
14401            that._enable();
14402
14403            that._popup();
14404
14405            that._old = that.value();
14406
14407            that._placeholder();
14408
14409            kendo.notify(that);
14410        },
14411
14412        options: {
14413            name: "AutoComplete",
14414            suggest: false,
14415            template: "",
14416            dataTextField: "",
14417            minLength: 1,
14418            delay: 200,
14419            height: 200,
14420            filter: "startswith",
14421            ignoreCase: true,
14422            highlightFirst: false,
14423            separator: null,
14424            placeholder: "",
14425            animation: {}
14426        },
14427
14428        _dataSource: function() {
14429            var that = this;
14430
14431            if (that.dataSource && that._refreshHandler) {
14432                that.dataSource.unbind(CHANGE, that._refreshHandler);
14433            } else {
14434                that._refreshHandler = proxy(that.refresh, that);
14435            }
14436
14437            that.dataSource = DataSource.create(that.options.dataSource)
14438                .bind(CHANGE, that._refreshHandler);
14439        },
14440
14441        setDataSource: function(dataSource) {
14442            this.options.dataSource = dataSource;
14443
14444            this._dataSource();
14445        },
14446
14447        events: [
14448        /**
14449        * Fires when the drop-down list is opened
14450        * @name kendo.ui.AutoComplete#open
14451        * @event
14452        * @param {Event} e
14453        * @example
14454        * $("#autoComplete").kendoAutoComplete({
14455        *     open: function(e) {
14456        *         // handle event
14457        *     }
14458        * });
14459        * @example
14460        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
14461        * autoComplete.bind("open", function(e) {
14462        *     // handle event
14463        * });
14464        */
14465        "open",
14466        /**
14467        * Fires when the drop-down list is closed
14468        * @name kendo.ui.AutoComplete#close
14469        * @event
14470        * @param {Event} e
14471        * @example
14472        * $("#autoComplete").kendoAutoComplete({
14473        *     close: function(e) {
14474        *         // handle event
14475        *     }
14476        * });
14477        * @exampleTitle To set after initialization
14478        * @example
14479        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
14480        * autoComplete.bind("close", function(e) {
14481        *     // handle event
14482        * });
14483            */
14484        "close",
14485            /**
14486            * Fires when the value has been changed.
14487            * @name kendo.ui.AutoComplete#change
14488            * @event
14489            * @param {Event} e
14490            * $("#autoComplete").kendoAutoComplete({
14491            *     change: function(e) {
14492            *         // handle event
14493            *     }
14494            * });
14495            * @exampleTitle To set after initialization
14496            * @example
14497            * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
14498            * $("#autoComplete").data("kendoAutoComplete").bind("change", function(e) {
14499            *     // handle event
14500            * });
14501                */
14502               CHANGE,
14503               /**
14504               *
14505               * Triggered when a Li element is selected.
14506               *
14507               * @name kendo.ui.AutoComplete#select
14508               * @event
14509               *
14510               * @param {Event} e
14511               *
14512               * @param {jQuery} e.item
14513               * The selected item chosen by a user.
14514               *
14515               * @exampleTitle Attach select event handler during initialization; detach via unbind()
14516               * @example
14517               * // event handler for select
14518               * var onSelect = function(e) {
14519               *     // access the selected item via e.item (jQuery object)
14520               * };
14521               *
14522               * // attach select event handler during initialization
14523               * var autocomplete = $("#autocomplete").kendoAutoComplete({
14524               *     select: onSelect
14525               * });
14526               *
14527               * // detach select event handler via unbind()
14528               * autocomplete.data("kendoAutoComplete").unbind("select", onSelect);
14529               *
14530               * @exampleTitle Attach select event handler via bind(); detach via unbind()
14531               * @example
14532               * // event handler for select
14533               * var onSelect = function(e) {
14534               *     // access the selected item via e.item (jQuery object)
14535               * };
14536               *
14537               * // attach select event handler via bind()
14538               * $("#autocomplete").data("kendoAutoComplete").bind("select", onSelect);
14539               *
14540               * // detach select event handler via unbind()
14541               * $("#autocomplete").data("kendoAutoComplete").unbind("select", onSelect);
14542               *
14543               */
14544               "select",
14545               "dataBinding",
14546               "dataBound"
14547            ],
14548        setOptions: function(options) {
14549            List.fn.setOptions.call(this, options);
14550
14551            this._template();
14552            this._accessors();
14553        },
14554
14555        /**
14556        * Returns the raw data record at the specified index
14557        * @name kendo.ui.AutoComplete#dataItem
14558        * @function
14559        * @param {Number} index The zero-based index of the data record
14560        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
14561        * @example
14562        * var autocomplete = $("#autocomplete").data("kendoAutoComplete");
14563        *
14564        * // get the dataItem corresponding to the passed index.
14565        * var dataItem = autocomplete.dataItem(1);
14566        */
14567
14568        /**
14569        * Enable/Disable the autocomplete widget.
14570        * @param {Boolean} enable The argument, which defines whether to enable/disable the autocomplete.
14571        * @example
14572        * // get a reference to the autocomplete widget
14573        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14574        *
14575        * // disables the autocomplete
14576        * autocomplete.enable(false);
14577        *
14578        * // enables the autocomplete
14579        * autocomplete.enable(true);
14580        */
14581        enable: function(enable) {
14582            var that = this,
14583                element = that.element,
14584                wrapper = that.wrapper;
14585
14586            if (enable === false) {
14587                wrapper
14588                    .removeClass(DEFAULT)
14589                    .addClass(STATEDISABLED)
14590                    .unbind(HOVEREVENTS);
14591
14592                element.attr(DISABLED, DISABLED);
14593            } else {
14594                wrapper
14595                    .removeClass(STATEDISABLED)
14596                    .addClass(DEFAULT)
14597                    .bind(HOVEREVENTS, that._toggleHover);
14598
14599                element
14600                    .removeAttr(DISABLED);
14601            }
14602        },
14603
14604        /**
14605        * Closes the drop-down list.
14606        * @example
14607        * // get a reference to the autocomplete widget
14608        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14609        *
14610        * autocomplete.close();
14611        */
14612        close: function () {
14613            var that = this;
14614            that._current = null;
14615            that.popup.close();
14616        },
14617
14618        /**
14619        * Re-render the items in drop-down list.
14620        * @name kendo.ui.AutoComplete#refresh
14621        * @function
14622        * @example
14623        * // get a referenence to the Kendo UI AutoComplete
14624        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14625        * // re-render the items in drop-down list.
14626        * autocomplete.refresh();
14627        */
14628        refresh: function () {
14629            var that = this,
14630            ul = that.ul[0],
14631            popup = that.popup,
14632            options = that.options,
14633            data = that._data(),
14634            length = data.length;
14635
14636            that.trigger("dataBinding");
14637
14638            ul.innerHTML = kendo.render(that.template, data);
14639
14640            that._height(length);
14641
14642            if (length) {
14643                if (options.highlightFirst) {
14644                    that.current($(ul.firstChild));
14645                }
14646
14647                if (options.suggest) {
14648                    that.suggest($(ul.firstChild));
14649                }
14650            }
14651
14652            if (that._open) {
14653                that._open = false;
14654                popup[length ? "open" : "close"]();
14655            }
14656
14657            if (that._touchScroller) {
14658                that._touchScroller.reset();
14659            }
14660
14661            that._makeUnselectable();
14662
14663            that.trigger("dataBound");
14664        },
14665
14666        /**
14667        * Selects drop-down list item and sets the text of the autocomplete.
14668        * @param {jQuery Object} li The LI element.
14669        * @example
14670        * // get a reference to the autocomplete widget
14671        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14672        *
14673        * // selects by jQuery object
14674        * autocomplete.select(autocomplete.ul.children().eq(0));
14675        */
14676        select: function (li) {
14677            this._select(li);
14678        },
14679
14680        /**
14681        * Filters dataSource using the provided parameter and rebinds drop-down list.
14682        * @param {string} word The filter value.
14683        * @example
14684        * // get a reference to the autocomplete widget
14685        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14686        *
14687        * // Searches for item which has "Inception" in the name.
14688        * autocomplete.search("Inception");
14689        */
14690        search: function (word) {
14691            var that = this,
14692            options = that.options,
14693            ignoreCase = options.ignoreCase,
14694            separator = options.separator,
14695            length;
14696
14697            word = word || that.value();
14698
14699            that._current = null;
14700
14701            clearTimeout(that._typing);
14702
14703            if (separator) {
14704                word = wordAtCaret(caretPosition(that.element[0]), word, separator);
14705            }
14706
14707            length = word.length;
14708
14709            if (!length) {
14710                that.popup.close();
14711            } else if (length >= that.options.minLength) {
14712                that._open = true;
14713
14714                that.dataSource.filter({
14715                    value: ignoreCase ? word.toLowerCase() : word,
14716                    operator: options.filter,
14717                    field: options.dataTextField,
14718                    ignoreCase: ignoreCase
14719                });
14720            }
14721        },
14722
14723        /**
14724        * Forces a suggestion onto the text of the AutoComplete.
14725        * @param {string} value Characters to force a suggestion.
14726        * @example
14727        * // note that this suggest is not the same as the configuration method
14728        * // suggest which enables/disables auto suggesting for the AutoComplete
14729        * //
14730        * // get a referenence to the Kendo UI AutoComplete
14731        * var autoComplete = $("#autoComplete").data("kendoAutoComplete");
14732        *
14733        * // force a suggestion to the item with the name "Inception"
14734        * autoComplete.suggest("Inception");
14735        */
14736        suggest: function (word) {
14737            var that = this,
14738                key = that._last,
14739                value = that.value(),
14740                element = that.element[0],
14741                caret = caretPosition(element),
14742                separator = that.options.separator,
14743                words = value.split(separator),
14744                wordIndex = indexOfWordAtCaret(caret, value, separator),
14745                selectionEnd = caret,
14746                idx;
14747
14748            if (key == keys.BACKSPACE || key == keys.DELETE) {
14749                that._last = undefined;
14750                return;
14751            }
14752
14753            word = word || "";
14754
14755            if (typeof word !== "string") {
14756                idx = List.inArray(word[0], that.ul[0]);
14757
14758                if (idx > -1) {
14759                    word = that._text(that._data()[idx]);
14760                } else {
14761                    word = "";
14762                }
14763            }
14764
14765            if (caret <= 0) {
14766                caret = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
14767            }
14768
14769            idx = value.substring(0, caret).lastIndexOf(separator);
14770            idx = idx > -1 ? caret - (idx + separator.length) : caret;
14771            value = words[wordIndex].substring(0, idx);
14772
14773            if (word) {
14774                idx = word.toLowerCase().indexOf(value.toLowerCase());
14775                if (idx > -1) {
14776                    word = word.substring(idx + value.length);
14777
14778                    selectionEnd = caret + word.length;
14779
14780                    value += word;
14781                }
14782
14783                if (separator && words[words.length - 1] !== "") {
14784                    words.push("");
14785                }
14786
14787            }
14788
14789            words[wordIndex] = value;
14790
14791            that.value(words.join(separator || ""));
14792
14793            selectText(element, caret, selectionEnd);
14794        },
14795
14796        /**
14797        * Gets/Sets the value of the autocomplete.
14798        * @param {String} value The value to set.
14799        * @returns {String} The value of the autocomplete.
14800        * @example
14801        * // get a reference to the autocomplete widget
14802        * var autocomplete = $("autocomplete").data("kendoAutoComplete");
14803        *
14804        * // get the text of the autocomplete.
14805        * var value = autocomplete.value();
14806        */
14807        value: function (value) {
14808            var that = this,
14809                element = that.element[0];
14810
14811            if (value !== undefined) {
14812                element.value = value;
14813            } else {
14814                value = element.value;
14815
14816                if (element.className.indexOf("k-readonly") > -1) {
14817                    if (value === that.options.placeholder) {
14818                        return "";
14819                    } else {
14820                        return value;
14821                    }
14822                }
14823
14824                return value;
14825            }
14826        },
14827
14828        _accept: function (li) {
14829            var that = this;
14830
14831            that._focus(li);
14832            moveCaretAtEnd(that.element[0]);
14833        },
14834
14835        _keydown: function (e) {
14836            var that = this,
14837                ul = that.ul[0],
14838                key = e.keyCode,
14839                current = that._current,
14840                visible = that.popup.visible();
14841
14842            that._last = key;
14843
14844            if (key === keys.DOWN) {
14845                if (visible) {
14846                    that._move(current ? current.next() : $(ul.firstChild));
14847                }
14848                e.preventDefault();
14849            } else if (key === keys.UP) {
14850                if (visible) {
14851                    that._move(current ? current.prev() : $(ul.lastChild));
14852                }
14853                e.preventDefault();
14854            } else if (key === keys.ENTER || key === keys.TAB) {
14855
14856                if (that.popup.visible()) {
14857                    e.preventDefault();
14858                }
14859
14860                that._accept(current);
14861            } else if (key === keys.ESC) {
14862                that.close();
14863            } else {
14864                that._search();
14865            }
14866        },
14867
14868        _move: function (li) {
14869            var that = this;
14870
14871            li = li[0] ? li : null;
14872
14873            that.current(li);
14874
14875            if (that.options.suggest) {
14876                that.suggest(li);
14877            }
14878        },
14879
14880        _placeholder: function(show) {
14881            if (placeholderSupported) {
14882                return;
14883            }
14884
14885            var that = this,
14886                element = that.element,
14887                placeholder = that.options.placeholder,
14888                value;
14889
14890            if (placeholder) {
14891                value = element.val();
14892
14893                if (show === undefined) {
14894                    show = !value;
14895                }
14896
14897                if (!show) {
14898                    if (value !== placeholder) {
14899                        placeholder = value;
14900                    } else {
14901                        placeholder = "";
14902                    }
14903                }
14904
14905                if (value === that._old && !show) {
14906                    return;
14907                }
14908
14909                element.toggleClass("k-readonly", show)
14910                       .val(placeholder);
14911            }
14912        },
14913
14914        _search: function () {
14915            var that = this;
14916            clearTimeout(that._typing);
14917
14918            that._typing = setTimeout(function () {
14919                if (that._prev !== that.value()) {
14920                    that._prev = that.value();
14921                    that.search();
14922                }
14923            }, that.options.delay);
14924        },
14925
14926        _select: function (li) {
14927            var that = this,
14928                separator = that.options.separator,
14929                data = that._data(),
14930                text,
14931                idx;
14932
14933            li = $(li);
14934
14935            if (li[0] && !li.hasClass(SELECTED)) {
14936                idx = List.inArray(li[0], that.ul[0]);
14937
14938                if (idx > -1) {
14939                    data = data[idx];
14940                    text = that._text(data);
14941
14942                    if (separator) {
14943                        text = replaceWordAtCaret(caretPosition(that.element[0]), that.value(), text, separator);
14944                    }
14945
14946                    that.value(text);
14947                    that.current(li.addClass(SELECTED));
14948                }
14949            }
14950        },
14951
14952        _toggleHover: function(e) {
14953            if (!support.touch) {
14954                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
14955            }
14956        },
14957
14958        _wrapper: function () {
14959            var that = this,
14960                element = that.element,
14961                DOMelement = element[0],
14962                wrapper;
14963
14964            wrapper = element.parent();
14965
14966            if (!wrapper.is("span.k-widget")) {
14967                wrapper = element.wrap("<span />").parent();
14968            }
14969
14970            wrapper[0].style.cssText = DOMelement.style.cssText;
14971            element.css({
14972                width: "100%",
14973                height: DOMelement.style.height
14974            });
14975
14976            that._focused = that.element;
14977            that.wrapper = wrapper
14978                              .addClass("k-widget k-autocomplete k-header")
14979                              .addClass(DOMelement.className);
14980        }
14981    });
14982
14983    ui.plugin(AutoComplete);
14984})(jQuery);
14985/**
14986 * @fileOverview Provides a DropDownList implementation which can be used to display a list of values and allows the
14987 * selection of a single value from the list.
14988 */
14989
14990(function($, undefined) {
14991    /**
14992     * @name kendo.ui.DropDownList.Description
14993     *
14994     * @section
14995     * <p>
14996     *  A <strong>DropDownList</strong> displays a list of values and allows the selection of a single value from the
14997     *  list.Custom values may not be entered via keyboard input.If you wish permit keyboard input - that is, custom
14998     *  values are allowed - use the <a href="../combobox/index.html" title="ComboBox">ComboBox</a>.
14999     * </p>
15000     * <h3>Getting Started</h3>
15001     * <p>There are two ways to create a <strong>DropDownList</strong>:</p>
15002     * <ol>
15003     *  <li>From a &lt;select&gt; element with HTML to define the list items</li>
15004     *  <li>From an &lt;input&gt; element with databinding to define the listitems</li>
15005     * </ol>
15006     * <p>
15007     *  A <strong>DropDownList</strong> will look and operate consistently regardless of the way in which it was
15008     *  created.
15009     * </p>
15010     *
15011     * @exampleTitle Creating a DropDownList from existing &lt;input&gt; element
15012     * @example
15013     * <input id="dropDownList" />
15014     *
15015     * @section
15016     * <p></p>
15017     * <p>
15018     *  Initialization of a <strong>DropDownList</strong> should occur after the DOM is fully loaded. It is recommended
15019     *  that initialization the <strong>DropDownList</strong> occur within a handler is provided to
15020     *  $(document).ready().
15021     * </p>
15022     *
15023     * @exampleTitle Initialize a DropDownList using a selector within $(document).ready()
15024     * @example
15025     * $(document).ready(function() {
15026     *     $("#dropDownList").kendoDropDownList({
15027     *         dataTextField: "text",
15028     *         dataValueField: "value",
15029     *         dataSource: [
15030     *             { text: "Item1", value: "1" },
15031     *             { text: "Item2", value: "2" }
15032     *         ]
15033     *     });
15034     * });
15035     *
15036     * @exampleTitle Create a DropDownList from existing &lt;select&gt; element with a pre-defined structure
15037     * @example
15038     * <select id="dropDownList">
15039     *     <option>Item 1</option>
15040     *     <option>Item 2</option>
15041     *     <option>Item 3</option>
15042     * </select>
15043     *
15044     * <script>
15045     *     $(document).ready(function(){
15046     *         $("#dropDownList").kendoDropDownList();
15047     *     });
15048     * </script>
15049     *
15050     * @section
15051     * <h3>Binding to Local or Remote Data</h3>
15052     * <p>
15053     *  The <strong>DropDownList</strong> can be bound to both local arrays and remote data via the
15054     *  <a href="../datasource/index.html" title="DataSource">DataSource</a> component; an abstraction for local and
15055     *  remote data. Local arrays are appropriate for limited value options, while remote data binding is better for
15056     *  larger data sets. With remote data-binding, items will be loaded on-demand; when they are displayed.
15057     * </p>
15058     *
15059     * @exampleTitle Binding to a remote OData service
15060     * @example
15061     * $(document).ready(function() {
15062     *     $("#titles").kendoDropDownList({
15063     *         index: 0,
15064     *         dataTextField: "Name",
15065     *         dataValueField: "Id",
15066     *         filter: "contains",
15067     *         dataSource: {
15068     *             type: "odata",
15069     *             serverFiltering: true,
15070     *             serverPaging: true,
15071     *             pageSize: 20,
15072     *             transport: {
15073     *                 read: "http://odata.netflix.com/Catalog/Titles"
15074     *             }
15075     *         }
15076     *     });
15077     * });
15078     *
15079     * @section
15080     * @section
15081     * <h3>Customizing Item Templates</h3>
15082     * <p>
15083     *  The <strong>DropDownList</strong> uses Kendo UI templates to enable you to control how items are rendered. For
15084     *  a detailed description of the capabilities and syntax of the Kendo UI templates, please refer to the
15085     *  <a href="../templates/index.html" title="Kendo UI Template">demos and documentation</a>.
15086     * </p>
15087     *
15088     * @exampleTitle Basic item template customization
15089     * @example
15090     * <!-- HTML -->
15091     * <input id="titles" />
15092     *
15093     * <!-- Template -->
15094     * <script id="scriptTemplate" type="text/x-kendo-template">
15095     *     # if (data.BoxArt.SmallUrl) { #
15096     *         <img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" />
15097     *         Title:${ data.Name }, Year: ${ data.Name }
15098     *     # } else { #
15099     *         <img alt="${ data.Name }" />
15100     *         Title:${ data.Name }, Year: ${ data.Name }
15101     *     # } #
15102     * </script>
15103     *
15104     * <!-- DropDownList initialization -->
15105     * <script type="text/javascript">
15106     *     $(document).ready(function() {
15107     *         $("#titles").kendoDropDownList({
15108     *             autoBind: false,
15109     *             dataTextField: "Name",
15110     *             dataValueField: "Id",
15111     *             template: $("#scriptTemplate").html(),
15112     *             dataSource: {
15113     *                 type: "odata",
15114     *                 serverFiltering: true,
15115     *                 serverPaging: true,
15116     *                 pageSize: 20,
15117     *                 transport: {
15118     *                     read: "http://odata.netflix.com/Catalog/Titles"
15119     *                 }
15120     *             }
15121     *         });
15122     *     });
15123     * </script>
15124     *
15125     * @section
15126     * <h3>Accessing an Existing DropDownList</h3>
15127     * <p>
15128     *  You can reference an existing <b>DropDownList</b> instance via
15129     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
15130     *  use the API to control its behavior.
15131     * </p>
15132     *
15133     * @exampleTitle Accessing an existing DropDownList instance
15134     * @example
15135     * var dropDownList = $("#dropDownList").data("kendoDropDownList");
15136     *
15137     */
15138    var kendo = window.kendo,
15139        ui = kendo.ui,
15140        Select = ui.Select,
15141        os = kendo.support.mobileOS,
15142        ATTRIBUTE = "disabled",
15143        CHANGE = "change",
15144        SELECT = "select",
15145        FOCUSED = "k-state-focused",
15146        DEFAULT = "k-state-default",
15147        DISABLED = "k-state-disabled",
15148        SELECTED = "k-state-selected",
15149        HOVEREVENTS = "mouseenter mouseleave",
15150        trimRegExp = /^\s/,
15151        proxy = $.proxy;
15152
15153    var DropDownList = Select.extend( /** @lends kendo.ui.DropDownList.prototype */ {
15154        /**
15155         * @constructs
15156         * @extends kendo.ui.Select
15157         * @param {DomElement} element DOM element
15158         * @param {Object} options Configuration options.
15159         * @option {kendo.data.DataSource | Object} [dataSource] Instance of DataSource or the data that the DropDownList will be bound to.
15160         * _exampleTitle Bind to a local array
15161         * _example
15162         * // bind to local data
15163         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
15164         * $("#dropdownlist").kendoDropDownList({
15165         *     dataSource: items,
15166         *     dataTextField: "Title",
15167         *     dataValueField: "Id"
15168         * });
15169         * _exampleTitle Bind to a remote URL
15170         * _example
15171         * $("#dropdownlist").kendoDropDownList({
15172         *     dataSource: {
15173         *         transport: {
15174         *             read: "titles.json"
15175         *         }
15176         *     },
15177         *     dataTextField: "Title",
15178         *     dataValueField: "Id"
15179         * });
15180         * @option {Boolean} [enable] <true> Controls whether the DropDownList should be initially enabled.
15181         * _example
15182         * $("#dropdownlist").kendoDropDownList({
15183         *     enabled: false // dropdown list will not be enabled
15184         * });
15185         * _exampleTitle To set after initialization
15186         * _example
15187         * // get a reference to the dropdown list
15188         * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15189         * // disable the dropdown
15190         * dropdownlist.enable(false);
15191         * @option {Number} [index] <0> Defines the initial selected item.
15192         * _example
15193         * $("#dropdownlist").kendoDropDownList({
15194         *     index: 1 // selects the second item in the dropdown list
15195         * });
15196         * @option {Boolean} [autoBind] <true> Controls whether to bind the widget on initialization.
15197         * _example
15198         * $("#dropdownlist").kendoDropDownList({
15199         *     autoBind: false
15200         * });
15201         * @option {Number} [delay] <500> Specifies the delay in ms before the search text typed by the end user is cleared.
15202         * _example
15203         * $("#dropdownlist").kendoDropDownList({
15204         *     delay: 1000 // wait 1 second before clearing the user input
15205         * });
15206         * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text content of the list items.
15207         * _example
15208         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
15209         * $("#dropdownlist").kendoDropDownList({
15210         *     dataSource: items,
15211         *     dataTextField: "Title",
15212         *     dataValueField: "Id"
15213         * });
15214         * @option {String} [dataValueField] <""> Sets the field of the data item that provides the value content of the list items.
15215         * _example
15216         * var items = [ { Id: 0, Title: "Manager" }, { Id: 1, Title: "Developer" }, { Id: 2, Title: "Vice President" } ];
15217         * $("#dropdownlist").kendoDropDownList({
15218         *     dataSource: items,
15219         *     dataTextField: "Title",
15220         *     dataValueField: "Id"
15221         * });
15222         * @option {Number} [height] <200> Define the height of the drop-down list in pixels.
15223         * _example
15224         * $("#dropdownlist").kendoDropDownList({
15225         *     height: 400
15226         * });
15227         * @option {String} [optionLabel] Define the text of the default empty item.
15228         * _example
15229         * $("#dropdownlist").kendoDropDownList({
15230         *     optionLabel: "Select An Option"
15231         * });
15232         * @option {Function} [template] Template to be used for rendering the items in the list.
15233         * _example
15234         *  //template
15235         * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
15236         *       # if (data.BoxArt.SmallUrl) { #
15237         *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
15238         *       # } else { #
15239         *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
15240         *       # } #
15241         *  &lt;/script&gt;
15242         *
15243         *  //dropdownlist initialization
15244         *  &lt;script&gt;
15245         *      $("#dropdownlist").kendoDropDownList({
15246         *          dataSource: dataSource,
15247         *          dataTextField: "Name",
15248         *          dataValueField: "Id",
15249         *          template: kendo.template($("#template").html())
15250         *      });
15251         *  &lt;/script&gt;
15252         * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
15253         * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
15254         * _example
15255         *  //dropdownlist initialization
15256         *  &lt;script&gt;
15257         *      $("#dropdownlist").kendoDropDownList({
15258         *          dataSource: dataSource,
15259         *          animation: {
15260         *             open: {
15261         *                 effects: "fadeIn",
15262         *                 duration: 300,
15263         *                 show: true
15264         *             }
15265         *          }
15266         *      });
15267         *  &lt;/script&gt;
15268         * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
15269         * _example
15270         *  //dropdownlist initialization
15271         *  &lt;script&gt;
15272         *      $("#dropdownlist").kendoDropDownList({
15273         *          dataSource: dataSource,
15274         *          animation: {
15275         *             close: {
15276         *                 effects: "fadeOut",
15277         *                 duration: 300,
15278         *                 hide: true
15279         *                 show: false
15280         *             }
15281         *          }
15282         *      });
15283         *  &lt;/script&gt;
15284         * @option {String} [ignoreCase] <true> Controls whether the search should be case sensitive.
15285         * _example
15286         * $("#dropdownlist").kendoDropDownList({
15287         *     ignoreCase: false //now search will be case sensitive
15288         * });
15289         */
15290        init: function(element, options) {
15291            var that = this;
15292
15293            options = $.isArray(options) ? { dataSource: options } : options;
15294
15295            Select.fn.init.call(that, element, options);
15296
15297            options = that.options;
15298            element = that.element.focus(function() {
15299                that.wrapper.focus();
15300            });
15301
15302            that._reset();
15303
15304            that._word = "";
15305
15306            that._wrapper();
15307
15308            that._span();
15309
15310            that._popup();
15311
15312            that._mobile();
15313
15314            that._accessors();
15315
15316            that._dataSource();
15317
15318            that._enable();
15319
15320            that.selectedIndex = -1;
15321
15322            if (options.autoBind) {
15323                that._selectItem();
15324            } else {
15325                if (element.is(SELECT)) {
15326                    that.text(element.children(":selected").text());
15327                }
15328
15329                if (!that.text().replace(trimRegExp, "") && options.optionLabel) {
15330                    that.text(options.optionLabel);
15331                }
15332            }
15333
15334            kendo.notify(that);
15335        },
15336
15337        options: {
15338            name: "DropDownList",
15339            enable: true,
15340            index: 0,
15341            autoBind: true,
15342            template: "",
15343            delay: 500,
15344            height: 200,
15345            dataTextField: "",
15346            dataValueField: "",
15347            optionLabel: "",
15348            ignoreCase: true,
15349            animation: {}
15350        },
15351        events: [
15352            /**
15353            * Fires when the drop-down list is opened
15354            * @name kendo.ui.DropDownList#open
15355            * @event
15356            * @param {Event} e
15357            * @example
15358            * $("#dropdownlist").kendoDropDownList({
15359            *     open: function(e) {
15360            *         // handle event
15361            *     }
15362            * });
15363            * @exampleTitle To set after initialization
15364            * @example
15365            * // get a reference to the dropdown list
15366            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15367            * // bind to the open event
15368            * dropdownlist.bind("open", function(e) {
15369            *     // handle event
15370            * });
15371            */
15372            "open",
15373
15374            /**
15375            * Fires when the drop-down list is closed
15376            * @name kendo.ui.DropDownList#close
15377            * @event
15378            * @param {Event} e
15379            * @example
15380            * $("#dropdownlist").kendoDropDownList({
15381            *     close: function(e) {
15382            *         // handle event
15383            *     }
15384            * });
15385            * @exampleTitle To set after initialization
15386            * @example
15387            * // get a reference to the dropdown list
15388            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15389            * // bind to the close event
15390            * dropdownlist.bind("close", function(e) {
15391            *     // handle event
15392            * });
15393            */
15394            "close",
15395
15396            /**
15397            * Fires when the value has been changed.
15398            * @name kendo.ui.DropDownList#change
15399            * @event
15400            * @param {Event} e
15401            * @example
15402            * $("#dropdownlist").kendoDropDownList({
15403            *     change: function(e) {
15404            *         // handle event
15405            *     }
15406            * });
15407            * @exampleTitle To set after initialization
15408            * @example
15409            * // get a reference to the dropdown list
15410            * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15411            * // bind to the change event
15412            * dropdownlist.bind("change", function(e) {
15413            *     // handle event
15414            * });
15415            */
15416            CHANGE,
15417            /**
15418            *
15419            * Triggered when a Li element is selected.
15420            *
15421            * @name kendo.ui.DropDownList#select
15422            * @event
15423            *
15424            * @param {Event} e
15425            *
15426            * @param {jQuery} e.item
15427            * The selected item chosen by a user.
15428            *
15429            * @exampleTitle Attach select event handler during initialization; detach via unbind()
15430            * @example
15431            * // event handler for select
15432            * var onSelect = function(e) {
15433            *     // access the selected item via e.item (jQuery object)
15434            * };
15435            *
15436            * // attach select event handler during initialization
15437            * var dropdownlist = $("#dropdownlist").kendoDropDownList({
15438            *     select: onSelect
15439            * });
15440            *
15441            * // detach select event handler via unbind()
15442            * dropdownlist.data("kendoDropDownList").unbind("select", onSelect);
15443            *
15444            * @exampleTitle Attach select event handler via bind(); detach via unbind()
15445            * @example
15446            * // event handler for select
15447            * var onSelect = function(e) {
15448            *     // access the selected item via e.item (jQuery object)
15449            * };
15450            *
15451            * // attach select event handler via bind()
15452            * $("#dropdownlist").data("kendoDropDownList").bind("select", onSelect);
15453            *
15454            * // detach select event handler via unbind()
15455            * $("#dropdownlist").data("kendoDropDownList").unbind("select", onSelect);
15456            *
15457            */
15458            "select",
15459            "dataBinding",
15460            "dataBound"
15461        ],
15462
15463        setOptions: function(options) {
15464            Select.fn.setOptions.call(this, options);
15465
15466            this._template();
15467            this._accessors();
15468        },
15469
15470        /**
15471        * Closes the drop-down list.
15472        * @name kendo.ui.DropDownList#close
15473        * @function
15474        * @example
15475        * // get a reference to the dropdown widget
15476        * var dropdownList = $("#dropdownList").data("kendoDropDownList");
15477        * // close the dropdown
15478        * dropdownlist.close();
15479        */
15480
15481        /**
15482        * Returns the raw data record at the specified index. If the index is not specified, the selected index will be used.
15483        * @name kendo.ui.DropDownList#dataItem
15484        * @function
15485        * @param {Number} index The zero-based index of the data record
15486        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
15487        * @example
15488        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15489        *
15490        * // get the dataItem corresponding to the selectedIndex.
15491        * var dataItem = dropdownlist.dataItem();
15492        *
15493        * // get the dataItem corresponding to the passed index.
15494        * var dataItem = dropdownlist.dataItem(1);
15495        */
15496
15497        /**
15498        * Enables/disables the dropdownlist widget
15499        * @param {Boolean} enable Desired state
15500        * @example
15501        * // get a reference to the dropdown list
15502        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15503        * // disable the dropdown list
15504        * dropdownlist.enable(false);
15505        */
15506        enable: function(enable) {
15507            var that = this,
15508                element = that.element,
15509                wrapper = that.wrapper.unbind(".dropdownlist"),
15510                dropDownWrapper = that._inputWrapper.unbind(HOVEREVENTS);
15511
15512            if (enable === false) {
15513                element.attr(ATTRIBUTE, ATTRIBUTE);
15514
15515                dropDownWrapper
15516                    .removeClass(DEFAULT)
15517                    .addClass(DISABLED);
15518            } else {
15519                element.removeAttr(ATTRIBUTE, ATTRIBUTE);
15520
15521                dropDownWrapper
15522                    .addClass(DEFAULT)
15523                    .removeClass(DISABLED)
15524                    .bind(HOVEREVENTS, that._toggleHover);
15525
15526                wrapper
15527                    .bind({
15528                        "click.dropdownlist": function(e) {
15529                            e.preventDefault();
15530                            that.toggle();
15531                        },
15532                        "keydown.dropdownlist": proxy(that._keydown, that),
15533                        "keypress.dropdownlist": proxy(that._keypress, that),
15534                        "focusin.dropdownlist": function() {
15535                            dropDownWrapper.addClass(FOCUSED);
15536                        },
15537                        "focusout.dropdownlist": function(e) {
15538                            that._blur();
15539                            dropDownWrapper.removeClass(FOCUSED);
15540                        }
15541                    });
15542            }
15543        },
15544
15545        /**
15546        * Opens the drop-down list.
15547        * @example
15548        * // get a reference to the dropdown list
15549        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15550        * // open the drop down
15551        * dropdownlist.open();
15552        */
15553        open: function() {
15554            var that = this;
15555
15556            if (!that.ul[0].firstChild) {
15557                that._open = true;
15558                that._selectItem();
15559            } else {
15560                that.popup.open();
15561                that._scroll(that._current);
15562            }
15563        },
15564
15565        /**
15566        * Toggles the drop-down list between opened and closed state.
15567        * @param {Boolean} toggle Defines the whether to open/close the drop-down list.
15568        * @example
15569        * // get a reference to the dropdown list
15570        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15571        *
15572        * // toggles the open state of the drop-down list.
15573        * dropdownlist.toggle();
15574        */
15575        toggle: function(toggle) {
15576            this._toggle(toggle);
15577        },
15578
15579        /**
15580        * Re-render the items in drop-down list.
15581        * @name kendo.ui.DropDownList#refresh
15582        * @function
15583        * @example
15584        * // get a referenence to the Kendo UI DropDownList
15585        * var dropdownlist = $("dropdownlist").data("kendoDropDownList");
15586        * // re-render the items in drop-down list.
15587        * dropdownlist.refresh();
15588        */
15589        refresh: function() {
15590            var that = this,
15591                data = that._data(),
15592                length = data.length,
15593                optionLabel = that.options.optionLabel;
15594
15595            that.trigger("dataBinding");
15596            if (that._current) {
15597                that.current(null);
15598            }
15599
15600            that.ul[0].innerHTML = kendo.render(that.template, data);
15601            that._height(length);
15602
15603            if (that.element.is(SELECT)) {
15604                if (optionLabel && length) {
15605                    optionLabel = '<option value="">' + optionLabel + "</option>";
15606                }
15607
15608                that._options(data, optionLabel);
15609            }
15610
15611            if (that._open) {
15612                that._open = false;
15613                that.toggle(!!length);
15614            }
15615
15616            that._hideBusy();
15617            that._makeUnselectable();
15618
15619            that.trigger("dataBound");
15620        },
15621
15622
15623
15624        /**
15625        * Selects item, which starts with the provided parameter.
15626        * @param {string} word The search value.
15627        * @example
15628        * // get a reference to the dropdown list
15629        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15630        *
15631        * // Selects item which starts with "In".
15632        * dropdownlist.search("In");
15633        */
15634        search: function(word) {
15635            if (word) {
15636                var that = this,
15637                    ignoreCase = that.options.ignoreCase;
15638
15639                if (ignoreCase) {
15640                    word = word.toLowerCase();
15641                }
15642
15643                that._select(function(dataItem) {
15644                    var text = that._text(dataItem);
15645
15646                    if (text !== undefined) {
15647                        text = (text + "");
15648                        if (ignoreCase) {
15649                            text = text.toLowerCase();
15650                        }
15651
15652                        return text.indexOf(word) === 0;
15653                    }
15654                });
15655            }
15656        },
15657
15658        /**
15659        * Selects drop-down list item and sets the value and the text of the dropdownlist.
15660        * @param {jQueryObject | Number | Function} li LI element or index of the item or predicate function, which defines the item that should be selected.
15661        * @returns {Integer} The index of the selected LI element.
15662        * @example
15663        * // get a reference to the dropdown list
15664        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15665        *
15666        * // selects by jQuery object
15667        * dropdownlist.select(dropdownlist.ul.children().eq(0));
15668        *
15669        * // selects by index
15670        * dropdownlist.select(1);
15671        *
15672        * // selects item if its text is equal to "test" using predicate function
15673        * dropdownlist.select(function(dataItem) {
15674        *     return dataItem.text === "test";
15675        * });
15676        */
15677        select: function(li) {
15678            var that = this;
15679
15680            if (li === undefined) {
15681                return that.selectedIndex;
15682            } else {
15683                that._select(li);
15684                that._old = that._accessor();
15685                that._oldIndex = that.selectedIndex;
15686            }
15687        },
15688
15689        /**
15690        * Gets/Sets the text of the dropdownlist.
15691        * @param {String} text The text to set.
15692        * @returns {String} The text of the dropdownlist.
15693        * @example
15694        * // get a reference to the dropdown list
15695        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15696        *
15697        * // get the text of the dropdownlist.
15698        * var text = dropdownlist.text();
15699        */
15700        text: function (text) {
15701            var span = this.span;
15702
15703            if (text !== undefined) {
15704                span.text(text);
15705            } else {
15706                return span.text();
15707            }
15708        },
15709
15710        /**
15711        * 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.
15712        * @param {String} value The value to set.
15713        * @returns {String} The value of the dropdownlist.
15714        * @example
15715        * // get a reference to the dropdown list
15716        * var dropdownlist = $("#dropdownlist").data("kendoDropDownList");
15717        *
15718        * // get the value of the dropdownlist.
15719        * var value = dropdownlist.value();
15720        *
15721        * // set the value of the dropdownlist.
15722        * dropdownlist.value("1"); //looks for item which has value "1"
15723        */
15724        value: function(value) {
15725            var that = this,
15726                idx;
15727
15728            if (value !== undefined) {
15729                if (value && that._valueOnFetch(value)) {
15730                    return;
15731                }
15732
15733                idx = that._index(value);
15734
15735                that.select(idx > -1 ? idx : 0);
15736            } else {
15737                return that._accessor();
15738            }
15739        },
15740
15741        _selectItem: function() {
15742            var that = this;
15743
15744            that.dataSource.one(CHANGE, function() {
15745                var value = that.value();
15746                if (value) {
15747                    that.value(value);
15748                } else {
15749                    that.select(that.options.index);
15750                }
15751            }).fetch();
15752        },
15753
15754        _accept: function(li) {
15755            this._focus(li);
15756        },
15757
15758        _data: function() {
15759            var that = this,
15760                options = that.options,
15761                optionLabel = options.optionLabel,
15762                textField = options.dataTextField,
15763                valueField = options.dataValueField,
15764                data = that.dataSource.view(),
15765                length = data.length,
15766                first = optionLabel,
15767                idx = 0;
15768
15769            if (optionLabel && length) {
15770                if (textField) {
15771                    first = {};
15772
15773                    textField = textField.split(".");
15774                    valueField = valueField.split(".");
15775
15776                    assign(first, valueField, "");
15777                    assign(first, textField, optionLabel);
15778                }
15779
15780                first = new kendo.data.ObservableArray([first]);
15781
15782                for (; idx < length; idx++) {
15783                    first.push(data[idx]);
15784                }
15785                data = first;
15786            }
15787
15788            return data;
15789        },
15790
15791        _keydown: function(e) {
15792            var that = this,
15793                key = e.keyCode,
15794                keys = kendo.keys,
15795                ul = that.ul[0];
15796
15797            that._move(e);
15798
15799            if (key === keys.HOME) {
15800                e.preventDefault();
15801                that._select(ul.firstChild);
15802            } else if (key === keys.END) {
15803                e.preventDefault();
15804                that._select(ul.lastChild);
15805            }
15806        },
15807
15808        _keypress: function(e) {
15809            var that = this;
15810
15811            setTimeout(function() {
15812                that._word += String.fromCharCode(e.keyCode || e.charCode);
15813                that._search();
15814            });
15815        },
15816
15817        _popup: function() {
15818            Select.fn._popup.call(this);
15819            this.popup.one("open", function() {
15820                this.wrapper = kendo.wrap(this.element)
15821                                    .addClass("km-popup");
15822            });
15823        },
15824
15825        _search: function() {
15826            var that = this;
15827            clearTimeout(that._typing);
15828
15829            that._typing = setTimeout(function() {
15830                that._word = "";
15831            }, that.options.delay);
15832
15833            that.search(that._word);
15834        },
15835
15836        _select: function(li) {
15837            var that = this,
15838                current = that._current,
15839                data = that._data(),
15840                value,
15841                text,
15842                idx;
15843
15844            li = that._get(li);
15845
15846            if (li && li[0] && !li.hasClass(SELECTED)) {
15847                if (current) {
15848                    current.removeClass(SELECTED);
15849                }
15850
15851                idx = ui.List.inArray(li[0], that.ul[0]);
15852                if (idx > -1) {
15853                    data = data[idx];
15854                    text = that._text(data);
15855                    value = that._value(data);
15856                    that.selectedIndex = idx;
15857
15858                    that.text(text);
15859                    that._accessor(value !== undefined ? value : text, idx);
15860                    that.current(li.addClass(SELECTED));
15861                }
15862            }
15863        },
15864
15865        _mobile: function() {
15866            var that = this,
15867                popup = that.popup,
15868                root = popup.element.parents(".km-root").eq(0);
15869
15870            if (root.length && os) {
15871                popup.options.animation.open.effects = (os.android || os.meego) ? "fadeIn" : os.ios ? "slideIn:up" : popup.options.animation.open.effects;
15872            }
15873        },
15874
15875        _span: function() {
15876            var that = this,
15877                wrapper = that.wrapper,
15878                SELECTOR = ".k-input",
15879                span;
15880
15881            span = wrapper.find(SELECTOR);
15882
15883            if (!span[0]) {
15884                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>')
15885                       .append(that.element);
15886
15887                span = wrapper.find(SELECTOR);
15888            }
15889
15890            that.span = span;
15891            that._inputWrapper = $(wrapper[0].firstChild);
15892            that._arrow = wrapper.find(".k-icon").mousedown(function(e) { e.preventDefault(); });
15893        },
15894
15895        _wrapper: function() {
15896            var that = this,
15897                element = that.element,
15898                DOMelement = element[0],
15899                TABINDEX = "tabIndex",
15900                wrapper;
15901
15902            wrapper = element.parent();
15903
15904            if (!wrapper.is("span.k-widget")) {
15905                wrapper = element.wrap("<span />").parent();
15906            }
15907
15908            if (!wrapper.attr(TABINDEX)) {
15909                wrapper.attr(TABINDEX, 0);
15910            }
15911
15912            wrapper[0].style.cssText = DOMelement.style.cssText;
15913            element.hide();
15914
15915            that._focused = that.wrapper = wrapper
15916                              .attr("unselectable", "on")
15917                              .addClass("k-widget k-dropdown k-header")
15918                              .addClass(DOMelement.className);
15919        }
15920    });
15921
15922    function assign(instance, fields, value) {
15923        var idx = 0,
15924            lastIndex = fields.length - 1,
15925            field;
15926
15927        for (; idx < lastIndex; ++idx) {
15928            field = fields[idx];
15929
15930            if (!(field in instance)) {
15931                instance[field] = {};
15932            }
15933
15934            instance = instance[field];
15935        }
15936
15937        instance[fields[lastIndex]] = value;
15938    }
15939
15940    ui.plugin(DropDownList);
15941})(jQuery);
15942(function($, undefined) {
15943    /**
15944     * @name kendo.ui.ComboBox.Description
15945     *
15946     * @section
15947     * <p>
15948     * The <strong>ComboBox</strong> displays a list of values and allows the selection of a single value from this
15949     * list. Custom values may also be entered via keyboard input. If you do not wish permit keyboard input - that is,
15950     * custom values are not permitted - use the
15951     * <a href="../dropdownlist/index.html" title="DropDownList">DropDownList</a>.
15952     * </p>
15953     * <p>
15954     * The <strong>ComboBox</strong> represents a richer version of a &lt;select&gt; element, providing support for
15955     * local and remote data binding, item templates, and configurable options for controlling the list behavior.
15956     * </p>
15957     * <h3>Getting Started</h3>
15958     * <p>There are two ways to create a <strong>ComboBox</strong>:</p>
15959     * <ol>
15960     *  <li>From a &lt;select&gt; element with HTML to define the list items</li>
15961     *  <li>From an &lt;input&gt; element with databinding to define the listitems</li>
15962     * </ol>
15963     * <p>
15964     *  A <strong>ComboBox</strong> will look and operate consistently regardless of the way in which it was created.
15965     * </p>
15966     *
15967     * @exampleTitle Creating a ComboBox from an existing &lt;input&gt; element
15968     * @example
15969     * <input id="comboBox" />
15970     *
15971     * @section
15972     * <p></p>
15973     * <p>
15974     *  Initialization of a <strong>ComboBox</strong> should occur after the DOM is fully loaded. It is recommended
15975     *  that initialization the <strong>ComboBox</strong> occur within a handler is provided to
15976     *  $(document).ready().
15977     * </p>
15978     *
15979     * @exampleTitle Initialize a ComboBox using a selector within $(document).ready()
15980     * @example
15981     * $(document).ready(function(){
15982     *     $("#comboBox").kendoComboBox({
15983     *         dataTextField: "text",
15984     *         dataValueField: "value",
15985     *         dataSource: [
15986     *             { text: "Item1", value: "1" },
15987     *             { text: "Item2", value: "2" }
15988     *         ]
15989     *     });
15990     * });
15991     *
15992     * @exampleTitle Creating a ComboBox from existing &lt;select&gt; element with a pre-defined structure
15993     * @example
15994     * <select id="comboBox">
15995     *     <option>Item 1</option>
15996     *     <option>Item 2</option>
15997     *     <option>Item 3</option>
15998     * </select>
15999     *
16000     * <script>
16001     *     $(document).ready(function(){
16002     *         $("#comboBox").kendoComboBox();
16003     *     });
16004     * </script>
16005     *
16006     * @section
16007     * <h3>Binding to Local or Remote Data</h3>
16008     * <p>
16009     *  The <strong>ComboBox</strong> can be bound to both local arrays and remote data via the
16010     *  <a href="../datasource/index.html" title="DataSource">DataSource</a> component; an abstraction for local and
16011     *  remote data. Local arrays are appropriate for limited value options, while remote data binding is better for
16012     *  larger data sets. With remote data-binding, items will be loaded on-demand; when they are displayed.
16013     * </p>
16014     *
16015     * @exampleTitle Binding to a remote OData service
16016     * @example
16017     * $(document).ready(function() {
16018     *     $("#comboBox").kendoComboBox({
16019     *         index: 0,
16020     *         dataTextField: "Name",
16021     *         dataValueField: "Id",
16022     *         filter: "contains",
16023     *         dataSource: {
16024     *             type: "odata",
16025     *             serverFiltering: true,
16026     *             serverPaging: true,
16027     *             pageSize: 20,
16028     *             transport: {
16029     *                 read: "http://odata.netflix.com/Catalog/Titles"
16030     *             }
16031     *         }
16032     *     });
16033     * });
16034     *
16035     * @section
16036     * <h3>Customizing Item Templates</h3>
16037     * <p>
16038     *  The <strong>ComboBox</strong> uses Kendo UI templates to enable you to control how items are rendered. For a
16039     *  detailed description of the capabilities and syntax of the Kendo UI templates, please refer to the
16040     *  <a href="../templates/index.html" title="Kendo UI Template">demos and documentation</a>.
16041     * </p>
16042     *
16043     * @exampleTitle Basic item template customization
16044     * @example
16045     * <input id="comboBox" />
16046     * <!-- Template -->
16047     * <script id="scriptTemplate" type="text/x-kendo-template">
16048     *     # if (data.BoxArt.SmallUrl) { #
16049     *         <img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" />
16050     *         Title:${ data.Name }, Year: ${ data.Name }
16051     *     # } else { #
16052     *         <img alt="${ data.Name }" />
16053     *         Title:${ data.Name }, Year: ${ data.Name }
16054     *     # } #
16055     * </script>
16056     *
16057     * <!-- ComboBox initialization -->
16058     * <script>
16059     *     $(document).ready(function() {
16060     *         $("#comboBox").kendoComboBox({
16061     *             autoBind: false,
16062     *             dataTextField: "Name",
16063     *             dataValueField: "Id",
16064     *             template: $("#scriptTemplate").html(),
16065     *             dataSource: {
16066     *                 type: "odata",
16067     *                 serverFiltering: true,
16068     *                 serverPaging: true,
16069     *                 pageSize: 20,
16070     *                 transport: {
16071     *                     read: "http://odata.netflix.com/Catalog/Titles"
16072     *                 }
16073     *             }
16074     *         });
16075     *     });
16076     * </script>
16077     *
16078     * @section
16079     * <h3>Accessing an Existing ComboBox</h3>
16080     * <p>
16081     *  You can reference an existing <b>ComboBox</b> instance via
16082     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Objectnce a reference has been established, you
16083     *  can use the API to control its behavior.
16084     * </p>
16085     *
16086     * @exampleTitle Accessing an existing ComboBox instance
16087     * @example
16088     * var comboBox = $("#comboBox").data("kendoComboBox");
16089     *
16090     */
16091    var kendo = window.kendo,
16092        ui = kendo.ui,
16093        List = ui.List,
16094        Select = ui.Select,
16095        support = kendo.support,
16096        placeholderSupported = support.placeholder,
16097        keys = kendo.keys,
16098        CLICK = support.touch ? "touchend" : "click",
16099        ATTRIBUTE = "disabled",
16100        CHANGE = "change",
16101        DEFAULT = "k-state-default",
16102        DISABLED = "k-state-disabled",
16103        FOCUSED = "k-state-focused",
16104        MOUSEDOWN = "mousedown",
16105        SELECT = "select",
16106        STATE_SELECTED = "k-state-selected",
16107        STATE_FILTER = "filter",
16108        STATE_ACCEPT = "accept",
16109        HOVEREVENTS = "mouseenter mouseleave",
16110        NULL = null,
16111        proxy = $.proxy;
16112
16113    var ComboBox = Select.extend(/** @lends kendo.ui.ComboBox.prototype */{
16114        /**
16115        * @constructs
16116        * @extends kendo.ui.Select
16117        * @param {DomElement} element DOM element
16118        * @param {Object} options Configuration options.
16119        * @option {Object | kendo.data.DataSource} [dataSource] A local JavaScript object or instance of DataSource or the data that the ComboBox will be bound to.
16120        * _example
16121        * var items = [{ text: "Item 1", value: "1" }, { text: "Item 2", value: "2" }];
16122        * $("#comboBox").kendoComboBox({
16123        *     dataTextField: "text",
16124        *     dataValueField: "value",
16125        *     dataSource: items
16126        * });
16127        * _exampleTitle To set after initialization
16128        * _example
16129        * $("#comboBox").kendoComboBox({
16130        *     dataSource: new kendo.data.DataSource({
16131        *         transport: {
16132        *             read: {
16133        *                 url: "Get/Items" // url to remote data source (simple list of strings)
16134        *             }
16135        *         }
16136        *     });
16137        * });
16138        * @option {Boolean} [enable] <true> Controls whether the ComboBox should be initially enabled.
16139        * _example
16140        * $("#comboBox").kendoComboBox({
16141        *     enable: false
16142        * });
16143        * _exampleTitle To set after initialization
16144        * _example
16145        * // get a reference to the ComboBox widget
16146        * var comboBox = $("#comboBox").data("kendoComboBox");
16147        * comboBox.enable(false);
16148        * @option {Number} [index] <-1> Defines the initial selected item.
16149        * _example
16150        * var items = [{ text: "Item 1", value: "1" }, { text: "Item 2", value: "2" }];
16151        * $("#comboBox").kendoComboBox({
16152        *     dataSource: items,
16153        *     index: 1 // 0 based from the start of the collection of objects. this selects "Item 2".
16154        * });
16155        * @option {Boolean} [autoBind] <true> Controls whether to bind the widget to the DataSource on initialization.
16156        * _example
16157        * $("#comboBox").kendoComboBox({
16158        *     autoBind: false
16159        * });
16160        * @option {Boolean} [highlightFirst] <true> Controls whether the first item will be automatically highlighted.
16161        * _example
16162        * $("#comboBox").kendoComboBox({
16163        *     highLightFirst: true
16164        * });
16165        * @option {Boolean} [suggest] <false> Controls whether the ComboBox should automatically auto-type the rest of text.
16166        * _example
16167        * $("#comboBox").kendoComboBox({
16168        *     suggest: false
16169        * });
16170        * @option {Number} [delay] <200>
16170 Specifies the delay in ms after which the ComboBox will start filtering dataSource.
16171        * _example
16172        * $("#comboBox").kendoComboBox({
16173        *     delay: 500
16174        * });
16175        * @option {Number} [minLength] <1> Specifies the minimum characters that should be typed before the ComboBox activates
16176        * _example
16177        * $("#comboBox").kendoComboBox({
16178        *     minLength: 3
16179        * });
16180        * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text content of the list items.
16181        * _example
16182        * $("#comboBox").kendoComboBox({
16183        *     dataTextField: "Name",
16184        *     dataValueField: "ID"
16185        * });
16186        * @option {String} [dataValueField] <""> Sets the field of the data item that provides the value content of the list items.
16187        * _example
16188        * $("#comboBox").kendoComboBox({
16189        *     dataTextField: "Name",
16190        *     dataValueField: "ID"
16191        * });
16192        * @option {String} [filter] <"none"> Defines the type of filtration. If "none" the ComboBox will not filter the items.
16193        * _example
16194        * $("#comboBox").kendoComboBox({
16195        *     filter: "startswith"
16196        * });
16197        * @option {String} [ignoreCase] <true> Defines whether the filtration should be case sensitive.
16198        * _example
16199        * $("#combobox").kendoComboBox({
16200        *     filter: 'contains',
16201        *     ignoreCase: false //now filtration will be case sensitive
16202        * });
16203        * @option {Number} [height] <200> Define the height of the drop-down list in pixels.
16204        * _example
16205        * $("#comboBox").kendoComboBox({
16206        *     height: 500
16207        * });
16208        * @option {Function} [template] Template to be used for rendering the items in the list.
16209        * _example
16210        *  //template
16211        * &lt;script id="template" type="text/x-kendo-tmpl"&gt;
16212        *       # if (data.BoxArt.SmallUrl) { #
16213        *           &lt;img src="${ data.BoxArt.SmallUrl }" alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
16214        *       # } else { #
16215        *           &lt;img alt="${ data.Name }" /&gt;Title:${ data.Name }, Year: ${ data.Name }
16216        *       # } #
16217        *  &lt;/script&gt;
16218        *
16219        *  //combobox initialization
16220        *  &lt;script&gt;
16221        *      $("#combobox").kendoComboBox({
16222        *          dataSource: dataSource,
16223        *          dataTextField: "Name",
16224        *          dataValueField: "Id",
16225        *          template: kendo.template($("#template").html())
16226        *      });
16227        *  &lt;/script&gt;
16228        * @option {Object} [animation] <> Animations to be used for opening/closing the popup. Setting to false will turn off the animation.
16229        * _exampleTitle Turn of animation
16230        * _example
16231        * $("#comboBox").kendoComboBox({
16232        *     animation: false
16233        * });
16234        * @option {Object} [animation.open] <> Animation to be used for opening of the popup.
16235        * _example
16236        *  //combobox initialization
16237        *  &lt;script&gt;
16238        *      $("#combobox").kendoComboBox({
16239        *          dataSource: dataSource,
16240        *          animation: {
16241        *             open: {
16242        *                 effects: "fadeIn",
16243        *                 duration: 300,
16244        *                 show: true
16245        *             }
16246        *          }
16247        *      });
16248        *  &lt;/script&gt;
16249        *
16250        * @option {Object} [animation.close] <> Animation to be used for closing of the popup.
16251        * _example
16252        *  //combobox initialization
16253        *  &lt;script&gt;
16254        *      $("#combobox").kendoComboBox({
16255        *          dataSource: dataSource,
16256        *          animation: {
16257        *             close: {
16258        *                 effects: "fadeOut",
16259        *                 duration: 300,
16260        *                 hide: true
16261        *                 show: false
16262        *             }
16263        *          }
16264        *      });
16265        *  &lt;/script&gt;
16266        *  @option {String} [placeholder] <""> A string that appears in the textbox when the combobox has no value.
16267        *  _example
16268        *  //combobox initialization
16269        *  <script>
16270        *      $("#combobox").kendoComboBox({
16271        *          dataSource: dataSource,
16272        *          placeholder: "Select..."
16273        *      });
16274        *  </script>
16275        *  _example
16276        *  <input id="combobox" placeholder="Select..." />
16277        *
16278        *  //combobox initialization
16279        *  <script>
16280        *      $("#combobox").kendoComboBox({
16281        *          dataSource: dataSource
16282        *      });
16283        *  </script>
16284        */
16285        init: function(element, options) {
16286            var that = this, wrapper;
16287
16288            options = $.isArray(options) ? { dataSource: options } : options;
16289
16290            Select.fn.init.call(that, element, options);
16291
16292            options = that.options;
16293            element = that.element.focus(function() {
16294                        that.input.focus();
16295                      });
16296
16297            options.placeholder = options.placeholder || element.attr("placeholder");
16298
16299            that._reset();
16300
16301            that._wrapper();
16302
16303            that._input();
16304
16305            that._popup();
16306
16307            that._accessors();
16308
16309            that._dataSource();
16310
16311            that._enable();
16312
16313            wrapper = that._inputWrapper;
16314
16315            that.input.bind({
16316                keydown: proxy(that._keydown, that),
16317                focus: function() {
16318                    wrapper.addClass(FOCUSED);
16319                    that._placeholder(false);
16320                },
16321                blur: function() {
16322                    wrapper.removeClass(FOCUSED);
16323                    clearTimeout(that._typing);
16324                    that.text(that.text());
16325                    that._placeholder();
16326                    that._blur();
16327                }
16328            });
16329
16330            that._oldIndex = that.selectedIndex = -1;
16331            that._old = that.value();
16332
16333            if (options.autoBind) {
16334                that._selectItem();
16335            } else if (element.is(SELECT)) {
16336                that.input.val(element.children(":selected").text());
16337            }
16338
16339            that._placeholder();
16340
16341            kendo.notify(that);
16342        },
16343
16344        options: {
16345            name: "ComboBox",
16346            enable: true,
16347            index: -1,
16348            autoBind: true,
16349            delay: 200,
16350            dataTextField: "",
16351            dataValueField: "",
16352            minLength: 0,
16353            height: 200,
16354            highlightFirst: true,
16355            template: "",
16356            filter: "none",
16357            placeholder: "",
16358            suggest: false,
16359            ignoreCase: true,
16360            animation: {}
16361        },
16362
16363        events:[
16364            /**
16365            * Fires when the drop-down list is opened
16366            * @name kendo.ui.ComboBox#open
16367            * @event
16368            * @param {Event} e
16369            * @example
16370            * $("#comboBox").kendoComboBox({
16371            *     open: function(e) {
16372            *             // handle event
16373            *         }
16374            * });
16375            * @exampleTitle To set after initialization
16376            * @example
16377            * // get a reference to instance of the Kendo UI ComboBox
16378            * var combobox = $("#comboBox").data("kendoComboBox");
16379            * // bind to the open event
16380            * combobox.bind("open", function(e) {
16381            *     // handle event
16382            * });
16383            */
16384            "open",
16385
16386            /**
16387            * Fires when the drop-down list is closed
16388            * @name kendo.ui.ComboBox#close
16389            * @event
16390            * @param {Event} e
16391            * @example
16392            * $("#comboBox").kendoComboBox({
16393            *     close: function(e) {
16394            *         // handle event
16395            *     }
16396            * });
16397            * @exampleTitle To set after initialization
16398            * @example
16399            * // get a reference to instance of the Kendo UI ComboBox
16400            * var combobox = $("#comboBox").data("kendoComboBox");
16401            * // bind to the close event
16402            * combobox.bind("close", function(e) {
16403            *     // handle event
16404            * });
16405            */
16406            "close",
16407
16408            /**
16409            * Fires when the value has been changed.
16410            * @name kendo.ui.ComboBox#change
16411            * @event
16412            * @param {Event} e
16413            * @example
16414            * $("#comboBox").kendoComboBox({
16415            *     change: function(e) {
16416            *         // handle event
16417            *     }
16418            * });
16419            * @exampleTitle To set after initialization
16420            * @example
16421            * // get a reference to instance of the Kendo UI ComboBox
16422            * var combobox = $("#comboBox").data("kendoComboBox");
16423            * // bind to the change event
16424            * combobox.bind("change", function(e) {
16425            *     // handle event
16426            * });
16427            */
16428            CHANGE,
16429            /**
16430            *
16431            * Triggered when a Li element is selected.
16432            *
16433            * @name kendo.ui.ComboBox#select
16434            * @event
16435            *
16436            * @param {Event} e
16437            *
16438            * @param {jQuery} e.item
16439            * The selected item chosen by a user.
16440            *
16441            * @exampleTitle Attach select event handler during initialization; detach via unbind()
16442            * @example
16443            * // event handler for select
16444            * var onSelect = function(e) {
16445            *     // access the selected item via e.item (jQuery object)
16446            * };
16447            *
16448            * // attach select event handler during initialization
16449            * var combobox = $("#combobox").kendoComboBox({
16450            *     select: onSelect
16451            * });
16452            *
16453            * // detach select event handler via unbind()
16454            * combobox.data("kendoComboBox").unbind("select", onSelect);
16455            *
16456            * @exampleTitle Attach select event handler via bind(); detach via unbind()
16457            * @example
16458            * // event handler for select
16459            * var onSelect = function(e) {
16460            *     // access the selected item via e.item (jQuery object)
16461            * };
16462            *
16463            * // attach select event handler via bind()
16464            * $("#combobox").data("kendoComboBox").bind("select", onSelect);
16465            *
16466            * // detach select event handler via unbind()
16467            * $("#combobox").data("kendoComboBox").unbind("select", onSelect);
16468            *
16469            */
16470            "select",
16471            "dataBinding",
16472            "dataBound"
16473        ],
16474        setOptions: function(options) {
16475            Select.fn.setOptions.call(this, options);
16476
16477            this._template();
16478            this._accessors();
16479        },
16480
16481        current: function(li) {
16482            var that = this,
16483                current = that._current;
16484
16485            if (li === undefined) {
16486                return current;
16487            }
16488
16489            if (current) {
16490                current.removeClass(STATE_SELECTED);
16491            }
16492
16493            Select.fn.current.call(that, li);
16494        },
16495
16496        /**
16497        * Returns the raw data record at the specified index. If the index is not specified, the selected index will be used.
16498        * @name kendo.ui.ComboBox#dataItem
16499        * @function
16500        * @param {Number} index The zero-based index of the data record
16501        * @returns {Object} The raw data record. Returns <i>undefined</i> if no data.
16502        * @example
16503        * var combobox = $("#combobox").data("kendoComboBox");
16504        *
16505        * // get the dataItem corresponding to the selectedIndex.
16506        * var dataItem = combobox.dataItem();
16507        *
16508        * // get the dataItem corresponding to the passed index.
16509        * var dataItem = combobox.dataItem(1);
16510        */
16511
16512        /**
16513        * Closes the drop-down list.
16514        * @name kendo.ui.ComboBox#close
16515        * @function
16516        * @example
16517        * // get a reference to instance of the Kendo UI ComboBox
16518        * var combobox = $("#comboBox").data("kendoComboBox");
16519        * combobox.close();
16520        */
16521        /**
16522        * Enables/disables the combobox widget
16523        * @param {Boolean} enable Desired state
16524        * @example
16525        * // get a reference to instance of the Kendo UI ComboBox
16526        * var combobox = $("#comboBox").data("kendoComboBox");
16527        * // disables the combobox
16528        * combobox.enable(false);
16529        */
16530        enable: function(enable) {
16531            var that = this,
16532                input = that.input.add(that.element),
16533                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
16534                arrow = that._arrow.parent().unbind(CLICK + " " + MOUSEDOWN);
16535
16536            if (enable === false) {
16537                wrapper
16538                    .removeClass(DEFAULT)
16539                    .addClass(DISABLED);
16540
16541                input.attr(ATTRIBUTE, ATTRIBUTE);
16542            } else {
16543                wrapper
16544                    .removeClass(DISABLED)
16545                    .addClass(DEFAULT)
16546                    .bind(HOVEREVENTS, that._toggleHover);
16547
16548                input.removeAttr(ATTRIBUTE);
16549                arrow.bind(CLICK, function() { that.toggle(); })
16550                     .bind(MOUSEDOWN, function(e) { e.preventDefault(); });
16551            }
16552        },
16553
16554        /**
16555        * Opens the drop-down list.
16556        * @example
16557        * // get a reference to instance of the Kendo UI ComboBox
16558        * var combobox = $("#comboBox").data("kendoComboBox");
16559        * combobox.open();
16560        */
16561        open: function() {
16562            var that = this,
16563                serverFiltering = that.dataSource.options.serverFiltering;
16564
16565            if (that.popup.visible()) {
16566                return;
16567            }
16568
16569            if (!that.ul[0].firstChild || (that._state === STATE_ACCEPT && !serverFiltering)) {
16570                that._open = true;
16571                that._state = "";
16572                that._selectItem();
16573            } else {
16574                that.popup.open();
16575                that._scroll(that._current);
16576            }
16577        },
16578
16579        /**
16580        * Re-render the items of the drop-down list.
16581        * @name kendo.ui.ComboBox#refresh
16582        * @function
16583        * @example
16584        * // get a referenence to the Kendo UI ComboBox
16585        * var combobox = $("#combobox").data("kendoComboBox");
16586        * // re-render the items of the drop-down list.
16587        * combobox.refresh();
16588        */
16589        refresh: function() {
16590            var that = this,
16591                ul = that.ul[0],
16592                options = that.options,
16593                data = that._data(),
16594                length = data.length;
16595
16596            that.trigger("dataBinding");
16597
16598            ul.innerHTML = kendo.render(that.template, data);
16599            that._height(length);
16600
16601            if (that.element.is(SELECT)) {
16602                that._options(data);
16603            }
16604
16605            if (length) {
16606                if (options.highlightFirst) {
16607                    that.current($(ul.firstChild));
16608                }
16609
16610                if (options.suggest && that.input.val()) {
16611                    that.suggest($(ul.firstChild));
16612                }
16613            }
16614
16615            if (that._open) {
16616                that._open = false;
16617                that.toggle(!!length);
16618            }
16619
16620            if (that._touchScroller) {
16621                that._touchScroller.reset();
16622            }
16623
16624            that._makeUnselectable();
16625
16626            that._hideBusy();
16627            that.trigger("dataBound");
16628        },
16629
16630        /**
16631        * Selects drop-down list item and sets the value and the text of the combobox.
16632        * @param {jQueryObject | Number | Function} li LI element or index of the item or predicate function, which defines the item that should be selected.
16633        * @returns {Integer} The index of the selected LI element.
16634        * @example
16635        * var combobox = $("#combobox").data("kendoComboBox");
16636        *
16637        * // selects by jQuery object
16638        * combobox.select(combobox.ul.children().eq(0));
16639        *
16640        * // selects by index
16641        * combobox.select(1);
16642        *
16643        * // selects item if its text is equal to "test" using predicate function
16644        * combobox.select(function(dataItem) {
16645        *     return dataItem.text === "test";
16646        * });
16647        */
16648        select: function(li) {
16649            var that = this;
16650
16651            if (li === undefined) {
16652                return that.selectedIndex;
16653            } else {
16654                that._select(li);
16655                that._old = that._accessor();
16656                that._oldIndex = that.selectedIndex;
16657            }
16658        },
16659
16660        /**
16661        * Filters dataSource using the provided parameter and rebinds drop-down list.
16662        * @param {string} word The filter value.
16663        * @example
16664        * var combobox = $("#combobox").data("kendoComboBox");
16665        *
16666        * // Searches for item which has "In" in the name.
16667        * combobox.search("In");
16668        */
16669        search: function(word) {
16670            word = typeof word === "string" ? word : this.text();
16671            var that = this,
16672                length = word.length,
16673                options = that.options,
16674                ignoreCase = options.ignoreCase,
16675                filter = options.filter,
16676                field = options.dataTextField,
16677                filters, expression;
16678
16679            clearTimeout(that._typing);
16680
16681            if (length >= options.minLength) {
16682                if (filter === "none") {
16683                    that._filter(word);
16684                } else {
16685                    that._open = true;
16686                    that._state = STATE_FILTER;
16687
16688                    expression = that.dataSource.filter() || {};
16689                    removeFiltersForField(expression, field);
16690
16691                    filters = expression.filters || [];
16692                    filters.push({
16693                        value: ignoreCase ? word.toLowerCase() : word,
16694                        field: field,
16695                        operator: filter,
16696                        ignoreCase: ignoreCase
16697                    });
16698
16699                    that.dataSource.filter(filters);
16700                }
16701            }
16702        },
16703
16704        /**
16705        * Forces a suggestion onto the text of the ComboBox.
16706        * @param {string} value Characters to force a suggestion.
16707        * @example
16708        * // note that this suggest is not the same as the configuration method
16709        * // suggest which enables/disables auto suggesting for the ComboBox
16710        * //
16711        * // get a referenence to the Kendo UI ComboBox
16712        * var combobox = $("#combobox").data("kendoComboBox");
16713        * // force a suggestion to the item with the name "Inception"
16714        * combobox.suggest("Inception");
16715        */
16716        suggest: function(word) {
16717            var that = this,
16718                element = that.input[0],
16719                value = that.text(),
16720                caret = List.caret(element),
16721                key = that._last,
16722                idx;
16723
16724            if (key == keys.BACKSPACE || key == keys.DELETE) {
16725                that._last = undefined;
16726                return;
16727            }
16728
16729            word = word || "";
16730
16731            if (typeof word !== "string") {
16732                idx = List.inArray(word[0], that.ul[0]);
16733
16734                if (idx > -1) {
16735                    word = that._text(that.dataSource.view()[idx]);
16736                } else {
16737                    word = "";
16738                }
16739            }
16740
16741            if (caret <= 0) {
16742                caret = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
16743            }
16744
16745            if (word) {
16746                idx = word.toLowerCase().indexOf(value.toLowerCase());
16747                if (idx > -1) {
16748                    value += word.substring(idx + value.length);
16749                }
16750            } else {
16751                value = value.substring(0, caret);
16752            }
16753
16754            if (value.length !== caret || !word) {
16755                element.value = value;
16756                List.selectText(element, caret, value.length);
16757            }
16758        },
16759
16760        /**
16761        * Gets/Sets the text of the ComboBox.
16762        * @param {String} text The text to set.
16763        * @returns {String} The text of the combobox.
16764        * @example
16765        * var combobox = $("#combobox").data("kendoComboBox");
16766        *
16767        * // get the text of the combobox.
16768        * var text = combobox.text();
16769        */
16770        text: function (text) {
16771            text = text === null ? "" : text;
16772
16773            var that = this,
16774                textAccessor = that._text,
16775                input = that.input[0],
16776                ignoreCase = that.options.ignoreCase,
16777                loweredText = text,
16778                dataItem;
16779
16780            if (text !== undefined) {
16781                dataItem = that.dataItem();
16782
16783                if (dataItem && textAccessor(dataItem) === text) {
16784                    return;
16785                }
16786
16787                if (ignoreCase) {
16788                    loweredText = loweredText.toLowerCase();
16789                }
16790
16791                that._select(function(data) {
16792                    data = textAccessor(data);
16793
16794                    if (ignoreCase) {
16795                        data = (data + "").toLowerCase();
16796                    }
16797
16798                    return data === loweredText;
16799                });
16800
16801                if (that.selectedIndex < 0) {
16802                    that._custom(text);
16803                    input.value = text;
16804                }
16805
16806            } else {
16807                return input.value;
16808            }
16809        },
16810
16811        /**
16812        * Toggles the drop-down list between opened and closed state.
16813        * @param {Boolean} toggle Defines the whether to open/close the drop-down list.
16814        * @example
16815        * var combobox = $("#combobox").data("kendoComboBox");
16816        *
16817        * // toggles the open state of the drop-down list.
16818        * combobox.toggle();
16819        */
16820        toggle: function(toggle) {
16821            var that = this;
16822
16823            that._toggle(toggle);
16824        },
16825
16826        /**
16827        * Gets/Sets the value of the combobox. If the value is undefined, text of the data item will be used.
16828        * @param {String} value The value to set.
16829        * @returns {String} The value of the combobox.
16830        * @example
16831        * var combobox = $("#combobox").data("kendoComboBox");
16832        *
16833        * // get the value of the combobox.
16834        * var value = combobox.value();
16835        *
16836        * // set the value of the combobox.
16837        * combobox.value("1"); //looks for item which has value "1"
16838        */
16839        value: function(value) {
16840            var that = this,
16841                idx;
16842
16843            if (value !== undefined) {
16844                if (value && that._valueOnFetch(value)) {
16845                    return;
16846                }
16847
16848                idx = that._index(value);
16849
16850                if (idx > -1) {
16851                    that.select(idx);
16852                } else {
16853                    that.current(NULL);
16854                    that._custom(value);
16855                    that.text(value);
16856                }
16857
16858                that._old = that._accessor();
16859                that._oldIndex = that.selectedIndex;
16860            } else {
16861                return that._accessor();
16862            }
16863        },
16864
16865        _accept: function(li) {
16866            var that = this;
16867
16868            if (li && that.popup.visible()) {
16869
16870                if (that._state === STATE_FILTER) {
16871                    that._state = STATE_ACCEPT;
16872                }
16873
16874                that._focus(li);
16875            } else {
16876                that.text(that.text());
16877                that._change();
16878            }
16879        },
16880
16881        _custom: function(value) {
16882            var that = this,
16883                element = that.element,
16884                custom = that._option;
16885
16886            if (element.is(SELECT)) {
16887                if (!custom) {
16888                    custom = that._option = $("<option/>");
16889                    element.append(custom);
16890                }
16891                custom.text(value);
16892                custom[0].selected = true;
16893            } else {
16894                element.val(value);
16895            }
16896        },
16897
16898        _filter: function(word) {
16899            var that = this,
16900                options = that.options,
16901                dataSource = that.dataSource,
16902                ignoreCase = options.ignoreCase,
16903                predicate = function (dataItem) {
16904                    var text = that._text(dataItem);
16905                    if (text !== undefined) {
16906                        text = text + "";
16907                        if (text !== "" && word === "") {
16908                            return false;
16909                        }
16910
16911                        if (ignoreCase) {
16912                            text = text.toLowerCase();
16913                        }
16914
16915                        return text.indexOf(word) === 0;
16916                    }
16917                };
16918
16919            if (ignoreCase) {
16920                word = word.toLowerCase();
16921            }
16922
16923            if (!that.ul[0].firstChild) {
16924                dataSource.one(CHANGE, function () { that.search(word); }).fetch();
16925                return;
16926            }
16927
16928            if (that._highlight(predicate) !== -1) {
16929                if (options.suggest && that._current) {
16930                    that.suggest(that._current);
16931                }
16932                that.open();
16933            }
16934
16935            that._hideBusy();
16936        },
16937
16938        _highlight: function(li) {
16939            var that = this, idx;
16940
16941            if (li === undefined || li === null) {
16942                return -1;
16943            }
16944
16945            li = that._get(li);
16946            idx = List.inArray(li[0], that.ul[0]);
16947
16948            if (idx == -1) {
16949                if (that.options.highlightFirst && !that.text()) {
16950                    li = $(that.ul[0].firstChild);
16951                } else {
16952                    li = NULL;
16953                }
16954            }
16955
16956            that.current(li);
16957
16958            return idx;
16959        },
16960
16961        _input: function() {
16962            var that = this,
16963                element = that.element[0],
16964                wrapper = that.wrapper,
16965                SELECTOR = ".k-input",
16966                input;
16967
16968            input = wrapper.find(SELECTOR);
16969
16970            if (!input[0]) {
16971                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>')
16972                       .append(that.element);
16973
16974                input = wrapper.find(SELECTOR);
16975            }
16976
16977            input[0].style.cssText = element.style.cssText;
16978            input.addClass(element.className)
16979                 .val(element.value)
16980                 .css({
16981                    width: "100%",
16982                    height: element.style.height
16983                 })
16984                 .show();
16985
16986            if (placeholderSupported) {
16987                input.attr("placeholder", that.options.placeholder);
16988            }
16989
16990            that._focused = that.input = input;
16991            that._arrow = wrapper.find(".k-icon");
16992            that._inputWrapper = $(wrapper[0].firstChild);
16993        },
16994
16995        _keydown: function(e) {
16996            var that = this,
16997                key = e.keyCode,
16998                input = that.input;
16999
17000            that._last = key;
17001
17002            clearTimeout(that._typing);
17003
17004            if (key == keys.TAB) {
17005                that.text(input.val());
17006
17007                if (that._state === STATE_FILTER && that.selectedIndex > -1) {
17008                    that._state = STATE_ACCEPT;
17009                }
17010            } else if (!that._move(e)) {
17011               that._search();
17012            }
17013        },
17014
17015        _placeholder: function(show) {
17016            if (placeholderSupported) {
17017                return;
17018            }
17019
17020            var that = this,
17021                input = that.input,
17022                placeholder = that.options.placeholder,
17023                value;
17024
17025            if (placeholder) {
17026                value = that.value();
17027
17028                if (show === undefined) {
17029                    show = !value;
17030                }
17031
17032                input.toggleClass("k-readonly", show);
17033
17034                if (!show) {
17035                    if (!value) {
17036                        placeholder = "";
17037                    } else {
17038                        return;
17039                    }
17040                }
17041
17042                input.val(placeholder);
17043            }
17044        },
17045
17046        _search: function() {
17047            var that = this;
17048
17049            that._typing = setTimeout(function() {
17050                var value = that.text();
17051                if (that._prev !== value) {
17052                    that._prev = value;
17053                    that.search(value);
17054                }
17055            }, that.options.delay);
17056        },
17057
17058        _select: function(li) {
17059            var that = this,
17060                text,
17061                value,
17062                idx = that._highlight(li),
17063                data = that._data();
17064
17065            that.selectedIndex = idx;
17066
17067            if (idx !== -1) {
17068                that._current.addClass(STATE_SELECTED);
17069
17070                data = data[idx];
17071                text = that._text(data);
17072                value = that._value(data);
17073
17074                that._prev = that.input[0].value = text;
17075                that._accessor(value !== undefined ? value : text, idx);
17076                that._placeholder();
17077            }
17078        },
17079
17080        _selectItem: function() {
17081            var that = this,
17082                dataSource = that.dataSource,
17083                expression = dataSource.filter() || {};
17084
17085            removeFiltersForField(expression, that.options.dataTextField);
17086
17087            that.dataSource.one(CHANGE, function() {
17088                var value = that.value();
17089                if (value) {
17090                    that.value(value);
17091                } else {
17092                    that.select(that.options.index);
17093                }
17094            }).filter(expression);
17095        },
17096
17097        _wrapper: function() {
17098            var that = this,
17099                element = that.element,
17100                wrapper;
17101
17102            wrapper = element.parent();
17103
17104            if (!wrapper.is("span.k-widget")) {
17105                wrapper = element.hide().wrap("<span />").parent();
17106            }
17107
17108            wrapper[0].style.cssText = element[0].style.cssText;
17109            that.wrapper = wrapper.addClass("k-widget k-combobox k-header")
17110                                  .addClass(element[0].className)
17111                                  .show();
17112        }
17113    });
17114
17115    function removeFiltersForField(expression, field) {
17116        if (expression.filters) {
17117            expression.filters = $.grep(expression.filters, function(filter) {
17118                removeFiltersForField(filter, field);
17119                if (filter.filters) {
17120                    return filter.filters.length;
17121                } else {
17122                    return filter.field != field;
17123                }
17124            });
17125        }
17126    }
17127
17128    ui.plugin(ComboBox);
17129})(jQuery);
17130(function($, undefined) {
17131    var kendo = window.kendo,
17132        ui = kendo.ui,
17133        DataSource = kendo.data.DataSource,
17134        Groupable = ui.Groupable,
17135        tbodySupportsInnerHtml = kendo.support.tbodyInnerHtml,
17136        Widget = ui.Widget,
17137        keys = kendo.keys,
17138        isPlainObject = $.isPlainObject,
17139        extend = $.extend,
17140        map = $.map,
17141        isArray = $.isArray,
17142        proxy = $.proxy,
17143        isFunction = $.isFunction,
17144        isEmptyObject = $.isEmptyObject,
17145        math = Math,
17146        REQUESTSTART = "requestStart",
17147        ERROR = "error",
17148        ROW_SELECTOR = "tbody>tr:not(.k-grouping-row,.k-detail-row,.k-group-footer):visible",
17149        DATA_CELL = ":not(.k-group-cell,.k-hierarchy-cell):visible",
17150        SELECTION_CELL_SELECTOR = "tbody>tr:not(.k-grouping-row,.k-detail-row,.k-group-footer) > td:not(.k-group-cell,.k-hierarchy-cell)",
17151        CELL_SELECTOR =  ROW_SELECTOR + ">td" + DATA_CELL,
17152        FIRST_CELL_SELECTOR = CELL_SELECTOR + ":first",
17153        EDIT = "edit",
17154        SAVE = "save",
17155        REMOVE = "remove",
17156        DETAILINIT = "detailInit",
17157        CHANGE = "change",
17158        SAVECHANGES = "saveChanges",
17159        DATABOUND = "dataBound",
17160        DETAILEXPAND = "detailExpand",
17161        DETAILCOLLAPSE = "detailCollapse",
17162        FOCUSED = "k-state-focused",
17163        FOCUSABLE = "k-focusable",
17164        SELECTED = "k-state-selected",
17165        COLUMNRESIZE = "columnResize",
17166        COLUMNREORDER = "columnReorder",
17167        CLICK = "click",
17168        HEIGHT = "height",
17169        TABINDEX = "tabIndex",
17170        FUNCTION = "function",
17171        STRING = "string",
17172        DELETECONFIRM = "Are you sure you want to delete this record?",
17173        formatRegExp = /\}/ig,
17174        indicatorWidth = 3,
17175        templateHashRegExp = /#/ig,
17176        COMMANDBUTTONTEMP = '<a class="k-button k-button-icontext #=className#" #=attr# href="\\#"><span class="#=iconClass# #=imageClass#"></span>#=text#</a>';
17177
17178    var VirtualScrollable =  Widget.extend({
17179        init: function(element, options) {
17180            var that = this;
17181
17182            Widget.fn.init.call(that, element, options);
17183            that._refreshHandler = proxy(that.refresh, that);
17184            that.setDataSource(options.dataSource);
17185            that.wrap();
17186        },
17187
17188        setDataSource: function(dataSource) {
17189            var that = this;
17190            if (that.dataSource) {
17191                that.dataSource.unbind(CHANGE, that._refreshHandler);
17192            }
17193            that.dataSource = dataSource;
17194            that.dataSource.bind(CHANGE, that._refreshHandler);
17195        },
17196
17197        options: {
17198            name: "VirtualScrollable",
17199            itemHeight: $.noop
17200        },
17201
17202        wrap: function() {
17203            var that = this,
17204                // workaround for IE issue where scroll is not raised if container is same width as the scrollbar
17205                scrollbar = kendo.support.scrollbar() + 1,
17206                element = that.element;
17207
17208            element.css( {
17209                width: "auto",
17210                paddingRight: scrollbar,
17211                overflow: "hidden"
17212            });
17213            that.content = element.children().first();
17214            that.wrapper = that.content.wrap('<div class="k-virtual-scrollable-wrap"/>')
17215                                .parent()
17216                                .bind("DOMMouseScroll", proxy(that._wheelScroll, that))
17217                                .bind("mousewheel", proxy(that._wheelScroll, that));
17218
17219            if (kendo.support.touch) {
17220                that.drag = new kendo.Drag(that.wrapper, {
17221                    global: true,
17222                    move: function(e) {
17223                        that.verticalScrollbar.scrollTop(that.verticalScrollbar.scrollTop() - e.y.delta);
17224                        e.preventDefault();
17225                    }
17226                });
17227            }
17228
17229            that.verticalScrollbar = $('<div class="k-scrollbar k-scrollbar-vertical" />')
17230                                        .css({
17231                                            width: scrollbar
17232                                        }).appendTo(element)
17233                                        .bind("scroll", proxy(that._scroll, that));
17234        },
17235
17236        _wheelScroll: function(e) {
17237            var that = this,
17238                scrollTop = that.verticalScrollbar.scrollTop(),
17239                originalEvent = e.originalEvent,
17240                delta;
17241
17242            e.preventDefault();
17243
17244            if (originalEvent.wheelDelta) {
17245                delta = originalEvent.wheelDelta;
17246            } else if (originalEvent.detail) {
17247                delta = (-originalEvent.detail) * 10;
17248            } else if ($.browser.opera) {
17249                delta = -originalEvent.wheelDelta;
17250            }
17251            that.verticalScrollbar.scrollTop(scrollTop + (-delta));
17252        },
17253
17254        _scroll: function(e) {
17255            var that = this,
17256                scrollTop = e.currentTarget.scrollTop,
17257                dataSource = that.dataSource,
17258                rowHeight = that.itemHeight,
17259                skip = dataSource.skip() || 0,
17260                start = that._rangeStart || skip,
17261                height = that.element.innerHeight(),
17262                isScrollingUp = !!(that._scrollbarTop && that._scrollbarTop > scrollTop),
17263                firstItemIndex = math.max(math.floor(scrollTop / rowHeight), 0),
17264                lastItemIndex = math.max(firstItemIndex + math.floor(height / rowHeight), 0);
17265
17266            that._scrollTop = scrollTop - (start * rowHeight);
17267            that._scrollbarTop = scrollTop;
17268
17269            if (!that._fetch(firstItemIndex, lastItemIndex, isScrollingUp)) {
17270                that.wrapper[0].scrollTop = that._scrollTop;
17271            }
17272        },
17273
17274        _fetch: function(firstItemIndex, lastItemIndex, scrollingUp) {
17275            var that = this,
17276                dataSource = that.dataSource,
17277                itemHeight = that.itemHeight,
17278                take = dataSource.take(),
17279                rangeStart = that._rangeStart || dataSource.skip() || 0,
17280                currentSkip = math.floor(firstItemIndex / take) * take,
17281                fetching = false,
17282                prefetchAt = 0.33;
17283
17284            if (firstItemIndex < rangeStart) {
17285
17286                fetching = true;
17287                rangeStart = math.max(0, lastItemIndex - take);
17288                that._scrollTop = (firstItemIndex - rangeStart) * itemHeight;
17289                that._page(rangeStart, take);
17290
17291            } else if (lastItemIndex >= rangeStart + take && !scrollingUp) {
17292
17293                fetching = true;
17294                rangeStart = firstItemIndex;
17295                that._scrollTop = itemHeight;
17296                that._page(rangeStart, take);
17297
17298            } else if (!that._fetching) {
17299
17300                if (firstItemIndex < (currentSkip + take) - take * prefetchAt && firstItemIndex > take) {
17301                    dataSource.prefetch(currentSkip - take, take);
17302                }
17303                if (lastItemIndex > currentSkip + take * prefetchAt) {
17304                    dataSource.prefetch(currentSkip + take, take);
17305                }
17306
17307            }
17308            return fetching;
17309        },
17310
17311        _page: function(skip, take) {
17312            var that = this,
17313                dataSource = that.dataSource;
17314
17315            clearTimeout(that._timeout);
17316            that._fetching = true;
17317            that._rangeStart = skip;
17318
17319            if (dataSource.inRange(skip, take)) {
17320                dataSource.range(skip, take);
17321            } else {
17322                kendo.ui.progress(that.wrapper.parent(), true);
17323                that._timeout = setTimeout(function() {
17324                    dataSource.range(skip, take);
17325                }, 100);
17326            }
17327        },
17328
17329        refresh: function() {
17330            var that = this,
17331                html = "",
17332                maxHeight = 250000,
17333                dataSource = that.dataSource,
17334                rangeStart = that._rangeStart,
17335                scrollbar = kendo.support.scrollbar(),
17336                wrapperElement = that.wrapper[0],
17337                totalHeight,
17338                idx,
17339                itemHeight;
17340
17341            kendo.ui.progress(that.wrapper.parent(), false);
17342            clearTimeout(that._timeout);
17343
17344            itemHeight = that.itemHeight = that.options.itemHeight() || 0;
17345
17346            var addScrollBarHeight = (wrapperElement.scrollWidth > wrapperElement.offsetWidth) ? scrollbar : 0;
17347
17348            totalHeight = dataSource.total() * itemHeight + addScrollBarHeight;
17349
17350            for (idx = 0; idx < math.floor(totalHeight / maxHeight); idx++) {
17351                html += '<div style="width:1px;height:' + maxHeight + 'px"></div>';
17352            }
17353
17354            if (totalHeight % maxHeight) {
17355                html += '<div style="width:1px;height:' + (totalHeight % maxHeight) + 'px"></div>';
17356            }
17357
17358            that.verticalScrollbar.html(html);
17359            wrapperElement.scrollTop = that._scrollTop;
17360
17361            if (that.drag) {
17362                that.drag.cancel();
17363            }
17364
17365            if (rangeStart && !that._fetching) { // we are rebound from outside local range should be reset
17366                that._rangeStart = dataSource.skip();
17367            }
17368            that._fetching = false;
17369        }
17370    });
17371
17372    function groupCells(count) {
17373        return new Array(count + 1).join('<td class="k-group-cell">&nbsp;</td>');
17374    }
17375
17376    var defaultCommands = {
17377        create: {
17378            text: "Add new record",
17379            imageClass: "k-add",
17380            className: "k-grid-add",
17381            iconClass: "k-icon"
17382        },
17383        cancel: {
17384            text: "Cancel changes",
17385            imageClass: "k-cancel",
17386            className: "k-grid-cancel-changes",
17387            iconClass: "k-icon"
17388        },
17389        save: {
17390            text: "Save changes",
17391            imageClass: "k-update",
17392            className: "k-grid-save-changes",
17393            iconClass: "k-icon"
17394        },
17395        destroy: {
17396            text: "Delete",
17397            imageClass: "k-delete",
17398            className: "k-grid-delete",
17399            iconClass: "k-icon"
17400        },
17401        edit: {
17402            text: "Edit",
17403            imageClass: "k-edit",
17404            className: "k-grid-edit",
17405            iconClass: "k-icon"
17406        },
17407        update: {
17408            text: "Update",
17409            imageClass: "k-update",
17410            className: "k-grid-update",
17411            iconClass: "k-icon"
17412        },
17413        canceledit: {
17414            text: "Cancel",
17415            imageClass: "k-cancel",
17416            className: "k-grid-cancel",
17417            iconClass: "k-icon"
17418        }
17419    };
17420
17421    function heightAboveHeader(context) {
17422        var top = 0;
17423        $('> .k-grouping-header, > .k-grid-toolbar', context).each(function () {
17424            top += this.offsetHeight;
17425        });
17426        return top;
17427    }
17428
17429    function cursor(context, value) {
17430        $('th, th .k-grid-filter, th .k-link', context)
17431            .add(document.body)
17432            .css('cursor', value);
17433    }
17434    function buildEmptyAggregatesObject(aggregates) {
17435            var idx,
17436                length,
17437                fieldsMap = {};
17438
17439            if (!isEmptyObject(aggregates)) {
17440                if (isArray(aggregates)) {
17441                    for (idx = 0, length = aggregates.length; idx < length; idx++) {
17442                        fieldsMap[aggregates[idx].field] = {};
17443                    }
17444                } else {
17445                    fieldsMap[aggregates.field] = {};
17446                }
17447            }
17448            return fieldsMap;
17449    }
17450
17451    function reorder(selector, sourceIndex, destIndex) {
17452        var source = selector.eq(sourceIndex),
17453            dest = selector.eq(destIndex);
17454
17455        source[sourceIndex > destIndex ? "insertBefore" : "insertAfter"](dest);
17456    }
17457
17458    var Grid = Widget.extend({
17459        init: function(element, options) {
17460            var that = this;
17461
17462            options = isArray(options) ? { dataSource: options } : options;
17463
17464            Widget.fn.init.call(that, element, options);
17465
17466            that._element();
17467
17468            that._columns(that.options.columns);
17469
17470            that._dataSource();
17471
17472            that._tbody();
17473
17474            that._pageable();
17475
17476            that._toolbar();
17477
17478            that._thead();
17479
17480            that._groupable();
17481
17482            that._templates();
17483
17484            that._navigatable();
17485
17486            that._selectable();
17487
17488            that._details();
17489
17490            that._editable();
17491
17492            if (that.options.autoBind) {
17493                that.dataSource.fetch();
17494            }
17495
17496            kendo.notify(that);
17497        },
17498
17499        events: [
17500           CHANGE,
17501           "dataBinding",
17502           DATABOUND,
17503           DETAILEXPAND,
17504           DETAILCOLLAPSE,
17505           DETAILINIT,
17506           EDIT,
17507           SAVE,
17508           REMOVE,
17509           SAVECHANGES,
17510           COLUMNRESIZE,
17511           COLUMNREORDER
17512        ],
17513
17514        setDataSource: function(dataSource) {
17515            var that = this;
17516
17517            that.options.dataSource = dataSource;
17518
17519            that._dataSource();
17520
17521            that._pageable();
17522
17523            if (that.options.groupable) {
17524                that._groupable();
17525            }
17526
17527            that._thead();
17528
17529            if (that.virtualScrollable) {
17530                that.virtualScrollable.setDataSource(that.options.dataSource);
17531            }
17532
17533            if (that.options.autoBind) {
17534                dataSource.fetch();
17535            }
17536        },
17537
17538        options: {
17539            name: "Grid",
17540            columns: [],
17541            toolbar: null,
17542            autoBind: true,
17543            filterable: false,
17544            scrollable: true,
17545            sortable: false,
17546            selectable: false,
17547            navigatable: false,
17548            pageable: false,
17549            editable: false,
17550            groupable: false,
17551            rowTemplate: "",
17552            altRowTemplate: "",
17553            dataSource: {},
17554            height: null,
17555            resizable: false,
17556            reorderable: false
17557        },
17558
17559        setOptions: function(options) {
17560            var that = this;
17561
17562            Widget.fn.setOptions.call(this, options);
17563
17564            that._templates();
17565        },
17566
17567        items: function() {
17568            return this.tbody.children(':not(.k-grouping-row,.k-detail-row,.k-group-footer)');
17569        },
17570
17571        _element: function() {
17572            var that = this,
17573                table = that.element;
17574
17575            if (!table.is("table")) {
17576                table = $("<table />").appendTo(that.element);
17577            }
17578
17579            that.table = table.attr("cellspacing", 0);
17580
17581            that._wrapper();
17582        },
17583
17584        positionColumnResizeHandles: function() {
17585            var that = this,
17586                left = 0,
17587                th,
17588                scrollable = that.options.scrollable,
17589                container = scrollable ? that.wrapper.find(".k-grid-header-wrap") : that.wrapper;
17590
17591            if (that.options.resizable) {
17592                container.find("> .k-resize-handle").remove();
17593
17594                that.thead.find("th").each(function() {
17595                    left += this.offsetWidth;
17596
17597                    th = $(this);
17598                    if (th.is(":not(.k-group-cell,.k-hierarchy-cell)")) {
17599                        $('<div class="k-resize-handle" />')
17600                        .css({
17601                            left: left - indicatorWidth,
17602                            top: scrollable ? 0 : heightAboveHeader(that.wrapper),
17603                            width: indicatorWidth * 2
17604                        })
17605                        .appendTo(container)
17606                        .data("th", th);
17607                    }
17608                });
17609            }
17610        },
17611
17612        _resizable: function() {
17613            var that = this,
17614                options = that.options,
17615                container,
17616                columnStart,
17617                columnWidth,
17618                gridWidth,
17619                col;
17620
17621            if (options.resizable) {
17622                container = options.scrollable ? that.wrapper.find(".k-grid-header-wrap") : that.wrapper;
17623
17624                that.positionColumnResizeHandles();
17625
17626                container.kendoResizable({
17627                    handle: ".k-resize-handle",
17628                    hint: function(handle) {
17629                        return $('<div class="k-grid-resize-indicator" />').css({
17630                            height: handle.data("th").outerHeight() + that.tbody.attr("clientHeight")
17631                        });
17632                    },
17633                    start: function(e) {
17634                        var th = $(e.currentTarget).data("th"),
17635                            index = $.inArray(th[0], th.parent().children(":visible")),
17636                            contentTable = that.tbody.parent(),
17637                            footer = that.footer || $();
17638
17639                        cursor(that.wrapper, th.css('cursor'));
17640
17641                        if (options.scrollable) {
17642                            col = that.thead.parent().find("col:eq(" + index + ")")
17643                                .add(contentTable.children("colgroup").find("col:eq(" + index + ")"))
17644                                .add(footer.find("colgroup").find("col:eq(" + index + ")"));
17645                        } else {
17646                            col = contentTable.children("colgroup").find("col:eq(" + index + ")");
17647                        }
17648
17649                        columnStart = e.pageX;
17650                        columnWidth = th.outerWidth();
17651                        gridWidth = that.tbody.outerWidth();
17652                    },
17653                    resize: function(e) {
17654                        var width = columnWidth + e.pageX - columnStart,
17655                            footer = that.footer || $(),
17656                            left = 0;
17657
17658                        if (width > 10) {
17659                            col.css('width', width);
17660
17661                            if (options.scrollable) {
17662                                that._footerWidth = gridWidth + e.pageX - columnStart;
17663                                that.tbody.parent()
17664                                    .add(that.thead.parent())
17665                                    .add(footer.find("table"))
17666                                    .css('width', that._footerWidth);
17667                            }
17668
17669                            $('.k-resize-handle', that.wrapper).each(function () {
17670                                left += $(this).data('th').outerWidth();
17671                                $(this).css('left', left - indicatorWidth);
17672                            });
17673                        }
17674                    },
17675                    resizeend: function(e) {
17676                        var th = $(e.currentTarget).data("th"),
17677                            newWidth = th.outerWidth(),
17678                            column;
17679
17680                        cursor(that.wrapper, "");
17681
17682                        if (columnWidth != newWidth) {
17683                            column = that.columns[th.parent().find("th:not(.k-group-cell,.k-hierarchy-cell)").index(th)];
17684
17685                            column.width = newWidth;
17686
17687                            that.trigger(COLUMNRESIZE, {
17688                                column: column,
17689                                oldWidth: columnWidth,
17690                                newWidth: newWidth
17691                            });
17692                        }
17693
17694                        that.positionColumnResizeHandles();
17695
17696                        if (that._hasDetails()) {
17697                            that.tbody.find(">tr.k-detail-row [" + kendo.attr("role") + "=grid]").kendoGrid("positionColumnResizeHandles");
17698                        }
17699                    }
17700                });
17701            }
17702        },
17703
17704        _draggable: function() {
17705            var that = this;
17706            if (that.options.reorderable) {
17707                that._draggableInstance = that.thead.kendoDraggable({
17708                    group: kendo.guid(),
17709                    filter: ".k-header:not(.k-group-cell,.k-hierarchy-cell)[" + kendo.attr("field") + "]",
17710                    hint: function(target) {
17711                        return $('<div class="k-header k-drag-clue" />')
17712                            .css({
17713                                width: target.width(),
17714                                paddingLeft: target.css("paddingLeft"),
17715                                paddingRight: target.css("paddingRight"),
17716                                lineHeight: target.height() + "px",
17717                                paddingTop: target.css("paddingTop"),
17718                                paddingBottom: target.css("paddingBottom")
17719                            })
17720                            .html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field")))
17721                            .prepend('<span class="k-icon k-drag-status k-denied" />');
17722                    }
17723                }).data("kendoDraggable");
17724            }
17725        },
17726
17727        _reorderable: function() {
17728            var that = this;
17729            if (that.options.reorderable) {
17730                that.thead.kendoReorderable({
17731                    draggable: that._draggableInstance,
17732                    change: function(e) {
17733                        var column = that.columns[e.oldIndex];
17734                        that.trigger(COLUMNREORDER, {
17735                            newIndex: e.newIndex,
17736                            oldIndex: e.oldIndex,
17737                            column: column
17738                        });
17739                        that.reorderColumn(e.newIndex, column);
17740                    }
17741                });
17742            }
17743        },
17744
17745        reorderColumn: function(destIndex, column) {
17746            var that = this,
17747                sourceIndex = $.inArray(column, that.columns),
17748                rows,
17749                idx,
17750                length;
17751
17752            if (sourceIndex === destIndex) {
17753                return;
17754            }
17755
17756            that.columns.splice(sourceIndex, 1);
17757            that.columns.splice(destIndex, 0, column);
17758            that._templates();
17759
17760            reorder(that.thead.prev().find("col:not(.k-group-col,.k-hierarchy-col)"), sourceIndex, destIndex);
17761            if (that.options.scrollable) {
17762                reorder(that.tbody.prev().find("col:not(.k-group-col,.k-hierarchy-col)"), sourceIndex, destIndex);
17763            }
17764
17765            reorder(that.thead.find(".k-header:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
17766
17767            if (that.footer) {
17768                reorder(that.footer.find(".k-grid-footer-wrap>table>colgroup>col:not(.k-group-col,.k-hierarchy-col)"), sourceIndex, destIndex);
17769                reorder(that.footer.find(".k-footer-template>td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
17770            }
17771
17772            rows = that.tbody.children(":not(.k-grouping-row,.k-detail-row)");
17773            for (idx = 0, length = rows.length; idx < length; idx += 1) {
17774                reorder(rows.eq(idx).find(">td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
17775            }
17776
17777            if (that.options.resizable) {
17778                that.positionColumnResizeHandles();
17779
17780                if (that._hasDetails()) {
17781                    that.tbody.find(">tr.k-detail-row [" + kendo.attr("role") + "=grid]").kendoGrid("positionColumnResizeHandles");
17782                }
17783            }
17784        },
17785
17786        cellIndex: function(td) {
17787            return $(td).parent().find('td:not(.k-group-cell,.k-hierarchy-cell)').index(td);
17788        },
17789
17790        _modelForContainer: function(container) {
17791            if (!container.is("tr") && this._editMode() !== "popup") {
17792                container = container.closest("tr");
17793            }
17794
17795            var id = container.attr(kendo.attr("uid"));
17796
17797            return this.dataSource.getByUid(id);
17798        },
17799
17800        _editable: function() {
17801            var that = this,
17802                editable = that.options.editable,
17803                handler = function () {
17804                    var target = document.activeElement,
17805                        cell = that._editContainer;
17806
17807                    if (cell && !$.contains(cell[0], target) && cell[0] !== target && !$(target).closest(".k-animation-container").length) {
17808                        if (that.editable.end()) {
17809                            that.closeCell();
17810                        }
17811                    }
17812                };
17813
17814            if (editable) {
17815                var mode = that._editMode();
17816
17817                if (mode === "incell") {
17818                    if (editable.update !== false) {
17819                        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) {
17820                            var td = $(this);
17821
17822                            if (td.closest("tbody")[0] !== that.tbody[0] || $(e.target).is(":input")) {
17823                                return;
17824                            }
17825
17826                            if (that.editable) {
17827                                if (that.editable.end()) {
17828                                    that.closeCell();
17829                                    that.editCell(td);
17830                                }
17831                            } else {
17832                                that.editCell(td);
17833                            }
17834
17835                        });
17836
17837                        that.wrapper.bind("focusin", function(e) {
17838                            clearTimeout(that.timer);
17839                            that.timer = null;
17840                        });
17841                        that.wrapper.bind("focusout", function(e) {
17842                            that.timer = setTimeout(handler, 1);
17843                        });
17844                    }
17845                } else {
17846                    if (editable.update !== false) {
17847                        that.wrapper.delegate("tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-edit", CLICK, function(e) {
17848                            e.preventDefault();
17849                            that.editRow($(this).closest("tr"));
17850                        });
17851                    }
17852                }
17853
17854                if (editable.destroy !== false) {
17855                    that.wrapper.delegate("tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-delete", CLICK, function(e) {
17856                        e.preventDefault();
17857                        that.removeRow($(this).closest("tr"));
17858                    });
17859               }
17860            }
17861        },
17862
17863        editCell: function(cell) {
17864            var that = this,
17865                column = that.columns[that.cellIndex(cell)],
17866                model = that._modelForContainer(cell);
17867
17868
17869            if (model && (!model.editable || model.editable(column.field)) && !column.command && column.field) {
17870
17871                that._attachModelChange(model);
17872
17873                that._editContainer = cell;
17874
17875                that.editable = cell.addClass("k-edit-cell")
17876                    .kendoEditable({
17877                        fields: { field: column.field, format: column.format, editor: column.editor },
17878                        model: model,
17879                        change: function(e) {
17880                            if (that.trigger(SAVE, { values: e.values, container: cell, model: model } )) {
17881                                e.preventDefault();
17882                            }
17883                        }
17884                    }).data("kendoEditable");
17885
17886                cell.parent().addClass("k-grid-edit-row");
17887
17888                that.trigger(EDIT, { container: cell, model: model });
17889            }
17890        },
17891
17892        _destroyEditable: function() {
17893            var that = this;
17894
17895            if (that.editable) {
17896                that._detachModelChange();
17897
17898                that.editable.destroy();
17899                delete that.editable;
17900
17901                if (that._editMode() === "popup") {
17902                    that._editContainer.data("kendoWindow").close();
17903                }
17904
17905                that._editContainer = null;
17906            }
17907        },
17908
17909        _attachModelChange: function(model) {
17910            var that = this;
17911
17912            that._modelChangeHandler = function(e) {
17913                that._modelChange({ field: e.field, model: this });
17914            };
17915
17916            model.bind("change", that._modelChangeHandler);
17917        },
17918
17919        _detachModelChange: function() {
17920            var that = this,
17921                container = that._editContainer,
17922                model = that._modelForContainer(container);
17923
17924            if (model) {
17925                model.unbind(CHANGE, that._modelChangeHandler);
17926            }
17927        },
17928
17929        closeCell: function() {
17930            var that = this,
17931                cell = that._editContainer.removeClass("k-edit-cell"),
17932                id = cell.closest("tr").attr(kendo.attr("uid")),
17933                column = that.columns[that.cellIndex(cell)],
17934                model = that.dataSource.getByUid(id);
17935
17936            cell.parent().removeClass("k-grid-edit-row");
17937
17938            that._destroyEditable(); // editable should be destoryed before content of the container is changed
17939
17940            that._displayCell(cell, column, model);
17941
17942            if (cell.hasClass("k-dirty-cell")) {
17943                $('<span class="k-dirty"/>').prependTo(cell);
17944            }
17945        },
17946
17947        _displayCell: function(cell, column, dataItem) {
17948            var that = this,
17949                state = { storage: {}, count: 0 },
17950                settings = extend({}, kendo.Template, that.options.templateSettings),
17951                tmpl = kendo.template(that._cellTmpl(column, state), settings);
17952
17953            if (state.count > 0) {
17954                tmpl = proxy(tmpl, state.storage);
17955            }
17956
17957            cell.empty().html(tmpl(dataItem));
17958        },
17959
17960        removeRow: function(row) {
17961            var that = this,
17962                model,
17963                mode;
17964
17965            if (!that._confirmation()) {
17966                return;
17967            }
17968
17969            row = $(row).hide();
17970            model = that._modelForContainer(row);
17971
17972            if (model && !that.trigger(REMOVE, { row: row, model: model })) {
17973                that.dataSource.remove(model);
17974
17975                mode = that._editMode();
17976
17977                if (mode === "inline" || mode === "popup") {
17978                    that.dataSource.sync();
17979                }
17980            }
17981        },
17982
17983        _editMode: function() {
17984            var mode = "incell",
17985                editable = this.options.editable;
17986
17987            if (editable !== true) {
17988                mode = editable.mode || editable;
17989            }
17990
17991            return mode;
17992        },
17993
17994        editRow: function(row) {
17995            var that = this,
17996                model = that._modelForContainer(row),
17997                container;
17998
17999            that.cancelRow();
18000
18001            if (model) {
18002
18003                that._attachModelChange(model);
18004
18005                if (that._editMode() === "popup") {
18006                    that._createPopupEditor(model);
18007                } else {
18008                    that._createInlineEditor(row, model);
18009                }
18010
18011                container = that._editContainer;
18012
18013                container.delegate("a.k-grid-cancel", CLICK, function(e) {
18014                    e.preventDefault();
18015                    that.cancelRow();
18016                });
18017
18018                container.delegate("a.k-grid-update", CLICK, function(e) {
18019                    e.preventDefault();
18020                    that.saveRow();
18021                });
18022            }
18023        },
18024
18025        _createPopupEditor: function(model) {
18026            var that = this,
18027                html = '<div ' + kendo.attr("uid") + '="' + model.uid + '"><div class="k-edit-form-container">',
18028                column,
18029                fields = [],
18030                idx,
18031                length,
18032                tmpl,
18033                editable = that.options.editable,
18034                options = isPlainObject(editable) ? editable.window : {},
18035                settings = extend({}, kendo.Template, that.options.templateSettings);
18036
18037            if (editable.template) {
18038                html += (kendo.template(editable.template, settings))(model);
18039            } else {
18040                for (idx = 0, length = that.columns.length; idx < length; idx++) {
18041                    column = that.columns[idx];
18042
18043                    if (!column.command) {
18044                        html += '<div class="k-edit-label"><label for="' + column.field + '">' + (column.title || column.field || "") + '</label></div>';
18045
18046                        if ((!model.editable || model.editable(column.field)) && column.field) {
18047                            fields.push({ field: column.field, format: column.format, editor: column.editor });
18048                            html += '<div ' + kendo.attr("container-for") + '="' + column.field + '" class="k-edit-field"></div>';
18049                        } else {
18050                            var state = { storage: {}, count: 0 };
18051
18052                            tmpl = kendo.template(that._cellTmpl(column, state), settings);
18053
18054                            if (state.count > 0) {
18055                                tmpl = proxy(tmpl, state.storage);
18056                            }
18057
18058                            html += '<div class="k-edit-field">' + tmpl(model) + '</div>';
18059                        }
18060                    }
18061                }
18062            }
18063
18064            html += that._createButton("update") + that._createButton("canceledit");
18065            html += '</div></div>';
18066
18067            var container = that._editContainer = $(html)
18068                .appendTo(that.wrapper)
18069                .kendoWindow(extend({
18070                    modal: true,
18071                    resizable: false,
18072                    draggable: true,
18073                    title: "Edit",
18074                    visible: false
18075                }, options));
18076
18077            var wnd = container.data("kendoWindow");
18078
18079            wnd.wrapper.delegate(".k-close", "click", function() {
18080                    that.cancelRow();
18081                });
18082
18083            that.editable = that._editContainer
18084                .kendoEditable({
18085                    fields: fields,
18086                    model: model,
18087                    clearContainer: false
18088                }).data("kendoEditable");
18089
18090            wnd.center().open();
18091
18092            that.trigger(EDIT, { container: container, model: model });
18093        },
18094
18095        _createInlineEditor: function(row, model) {
18096            var that = this,
18097                column,
18098                cell,
18099                fields = [];
18100
18101            row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
18102                cell = $(this);
18103                column = that.columns[that.cellIndex(cell)];
18104
18105                if (!column.command && column.field && (!model.editable || model.editable(column.field))) {
18106                    fields.push({ field: column.field, format: column.format, editor: column.editor });
18107                    cell.attr("data-container-for", column.field);
18108                    cell.empty();
18109                } else if (column.command && hasCommand(column.command, "edit")) {
18110                    cell.empty();
18111                    $(that._createButton("update") + that._createButton("canceledit")).appendTo(cell);
18112                }
18113            });
18114
18115            that._editContainer = row;
18116
18117            that.editable = row
18118                .addClass("k-grid-edit-row")
18119                .kendoEditable({
18120                    fields: fields,
18121                    model: model,
18122                    clearContainer: false
18123                }).data("kendoEditable");
18124
18125            that.trigger(EDIT, { container: row, model: model });
18126        },
18127
18128        cancelRow: function() {
18129            var that = this,
18130                container = that._editContainer,
18131                model;
18132
18133            if (container) {
18134                model = that._modelForContainer(container);
18135
18136                that.dataSource.cancelChanges(model);
18137
18138                if (that._editMode() !== "popup") {
18139                    that._displayRow(container);
18140                } else {
18141                    that._displayRow(that.items().filter("[" + kendo.attr("uid") + "=" + model.uid + "]"));
18142                }
18143
18144                that._destroyEditable();
18145            }
18146        },
18147
18148        saveRow: function() {
18149            var that = this,
18150                container = that._editContainer,
18151                model = that._modelForContainer(container),
18152                editable = that.editable;
18153
18154            if (container && editable && editable.end() &&
18155                !that.trigger(SAVE, { container: container, model: model } )) {
18156
18157                if (that._editMode() !== "popup") {
18158                    that._displayRow(container);
18159                }
18160
18161                that._destroyEditable();
18162                that.dataSource.sync();
18163            }
18164        },
18165
18166        _displayRow: function(row) {
18167            var that = this,
18168                model = that._modelForContainer(row);
18169
18170            if (model) {
18171                row.replaceWith($((row.hasClass("k-alt") ? that.altRowTemplate : that.rowTemplate)(model)));
18172            }
18173        },
18174
18175        _showMessage: function(text) {
18176            return window.confirm(text);
18177        },
18178
18179        _confirmation: function() {
18180            var that = this,
18181                editable = that.options.editable,
18182                confirmation = editable === true || typeof editable === STRING ? DELETECONFIRM : editable.confirmation;
18183
18184            return confirmation !== false && confirmation != null ? that._showMessage(confirmation) : true;
18185        },
18186
18187        cancelChanges: function() {
18188            this.dataSource.cancelChanges();
18189        },
18190
18191        saveChanges: function() {
18192            var that = this;
18193
18194            if (((that.editable && that.editable.end()) || !that.editable) && !that.trigger(SAVECHANGES)) {
18195                that.dataSource.sync();
18196            }
18197        },
18198
18199        addRow: function() {
18200            var that = this,
18201                index,
18202                dataSource = that.dataSource;
18203
18204            if ((that.editable && that.editable.end()) || !that.editable) {
18205                index = dataSource.indexOf((dataSource.view() || [])[0]);
18206                if (index < 0) {
18207                    index = 0;
18208                }
18209
18210                var model = dataSource.insert(index, {}),
18211                    id = model.uid,
18212                    mode = that._editMode(),
18213                    row = that.table.find("tr[" + kendo.attr("uid") + "=" + id + "]"),
18214                    cell = row.children("td:not(.k-group-cell,.k-hierarchy-cell)").first();
18215
18216                if ((mode === "inline" || mode === "popup") && row.length) {
18217                    that.editRow(row);
18218                } else if (cell.length) {
18219                    that.editCell(cell);
18220                }
18221            }
18222        },
18223
18224        _toolbar: function() {
18225            var that = this,
18226                wrapper = that.wrapper,
18227                toolbar = that.options.toolbar,
18228                template;
18229
18230            if (toolbar) {
18231                toolbar = isFunction(toolbar) ? toolbar({}) : (typeof toolbar === STRING ? toolbar : that._toolbarTmpl(toolbar).replace(templateHashRegExp, "\\#"));
18232
18233                template = proxy(kendo.template(toolbar), that);
18234
18235                $('<div class="k-toolbar k-grid-toolbar" />')
18236                    .html(template({}))
18237                    .prependTo(wrapper)
18238                    .delegate(".k-grid-add", CLICK, function(e) { e.preventDefault(); that.addRow(); })
18239                    .delegate(".k-grid-cancel-changes", CLICK, function(e) { e.preventDefault(); that.cancelChanges(); })
18240                    .delegate(".k-grid-save-changes", CLICK, function(e) { e.preventDefault(); that.saveChanges(); });
18241            }
18242        },
18243
18244        _toolbarTmpl: function(commands) {
18245            var that = this,
18246                idx,
18247                length,
18248                html = "";
18249
18250            if (isArray(commands)) {
18251                for (idx = 0, length = commands.length; idx < length; idx++) {
18252                    html += that._createButton(commands[idx]);
18253                }
18254            }
18255            return html;
18256        },
18257
18258        _createButton: function(command) {
18259            var template = command.template || COMMANDBUTTONTEMP,
18260                commandName = typeof command === STRING ? command : command.name,
18261                options = { className: "", text: commandName, imageClass: "", attr: "", iconClass: "" };
18262
18263            if (isPlainObject(command)) {
18264                options = extend(true, options, defaultCommands[commandName], command);
18265            } else {
18266                options = extend(true, options, defaultCommands[commandName]);
18267            }
18268
18269            return kendo.template(template)(options);
18270        },
18271
18272        _groupable: function() {
18273            var that = this,
18274                wrapper = that.wrapper,
18275                groupable = that.options.groupable;
18276
18277            if (!that.groupable) {
18278                that.table.delegate(".k-grouping-row .k-collapse, .k-grouping-row .k-expand", CLICK, function(e) {
18279                    var element = $(this),
18280                    group = element.closest("tr");
18281
18282                    if(element.hasClass('k-collapse')) {
18283                        that.collapseGroup(group);
18284                    } else {
18285                        that.expandGroup(group);
18286                    }
18287                    e.preventDefault();
18288                    e.stopPropagation();
18289                });
18290            }
18291
18292            if (groupable) {
18293                if(!wrapper.has("div.k-grouping-header")[0]) {
18294                    $("<div />").addClass("k-grouping-header").html("&nbsp;").prependTo(wrapper);
18295                }
18296
18297                if (that.groupable) {
18298                    that.groupable.destroy();
18299                }
18300
18301                that.groupable = new Groupable(wrapper, {
18302                    draggable: that._draggableInstance,
18303                    groupContainer: "div.k-grouping-header",
18304                    dataSource: that.dataSource,
18305                    allowDrag: that.options.reorderable
18306                });
18307            }
18308        },
18309
18310        _selectable: function() {
18311            var that = this,
18312                multi,
18313                cell,
18314                selectable = that.options.selectable;
18315
18316            if (selectable) {
18317                multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;
18318                cell = typeof selectable === STRING && selectable.toLowerCase().indexOf("cell") > -1;
18319
18320                that.selectable = new kendo.ui.Selectable(that.table, {
18321                    filter: ">" + (cell ? SELECTION_CELL_SELECTOR : "tbody>tr:not(.k-grouping-row,.k-detail-row,.k-group-footer)"),
18322                    multiple: multi,
18323                    change: function() {
18324                        that.trigger(CHANGE);
18325                    }
18326                });
18327
18328                if (that.options.navigatable) {
18329                    that.wrapper.keydown(function(e) {
18330                        var current = that.current();
18331                        if (e.keyCode === keys.SPACEBAR && e.target == that.wrapper[0] && !current.hasClass("k-edit-cell")) {
18332                            e.preventDefault();
18333                            e.stopPropagation();
18334                            current = cell ? current : current.parent();
18335
18336                            if(multi) {
18337                                if(!e.ctrlKey) {
18338                                    that.selectable.clear();
18339                                } else {
18340                                    if(current.hasClass(SELECTED)) {
18341                                        current.removeClass(SELECTED);
18342                                        current = null;
18343                                    }
18344                                }
18345                            } else {
18346                                that.selectable.clear();
18347                            }
18348
18349                            that.selectable.value(current);
18350                        }
18351                    });
18352                }
18353            }
18354        },
18355
18356        clearSelection: function() {
18357            var that = this;
18358            that.selectable.clear();
18359            that.trigger(CHANGE);
18360        },
18361
18362        select: function(items) {
18363            var that = this,
18364                selectable = that.selectable;
18365
18366            items = $(items);
18367            if(items.length) {
18368                if(!selectable.options.multiple) {
18369                    selectable.clear();
18370                    items = items.first();
18371                }
18372                selectable.value(items);
18373                return;
18374            }
18375
18376            return selectable.value();
18377        },
18378
18379        current: function(element) {
18380            var that = this,
18381                scrollable = that.options.scrollable,
18382                current = that._current;
18383
18384            if (element !== undefined && element.length) {
18385                if (!current || current[0] !== element[0]) {
18386                    element.addClass(FOCUSED);
18387                    if (current) {
18388                        current.removeClass(FOCUSED);
18389                    }
18390                    that._current = element;
18391
18392                    if(element.length && scrollable) {
18393                        that._scrollTo(element.parent()[0], that.content[0]);
18394                        if (scrollable.virtual) {
18395                            that._scrollTo(element[0], that.content.find(">.k-virtual-scrollable-wrap")[0]);
18396                        } else {
18397                            that._scrollTo(element[0], that.content[0]);
18398                        }
18399                    }
18400                }
18401            }
18402
18403            return that._current;
18404        },
18405
18406        _scrollTo: function(element, container) {
18407            var isHorizontal =  element.tagName.toLowerCase() === "td",
18408                elementOffset = element[isHorizontal ? "offsetLeft" : "offsetTop"],
18409                elementOffsetDir = element[isHorizontal ? "offsetWidth" : "offsetHeight"],
18410                containerScroll = container[isHorizontal ? "scrollLeft" : "scrollTop"],
18411                containerOffsetDir = container[isHorizontal ? "clientWidth" : "clientHeight"],
18412                bottomDistance = elementOffset + elementOffsetDir;
18413
18414            container[isHorizontal ? "scrollLeft" : "scrollTop"] = containerScroll > elementOffset ?
18415                                    elementOffset :
18416                                    (bottomDistance > (containerScroll + containerOffsetDir) ?
18417                                        (bottomDistance - containerOffsetDir) : containerScroll);
18418        },
18419
18420        _navigatable: function() {
18421            var that = this,
18422                wrapper = that.wrapper,
18423                table = that.table.addClass(FOCUSABLE),
18424                currentProxy = proxy(that.current, that),
18425                selector = "." + FOCUSABLE + " " + CELL_SELECTOR,
18426                browser = $.browser,
18427                clickCallback = function(e) {
18428                    var currentTarget = $(e.currentTarget);
18429                    if (currentTarget.closest("tbody")[0] !== that.tbody[0]) {
18430                        return;
18431                    }
18432
18433                    currentProxy(currentTarget);
18434                    if(!$(e.target).is(":button,a,:input,a>.k-icon,textarea,span.k-icon,.k-input")) {
18435                        setTimeout(function() { wrapper.focus(); } );
18436                    }
18437
18438                    e.stopPropagation();
18439                };
18440
18441            if (that.options.navigatable) {
18442                wrapper.bind({
18443                    focus: function() {
18444                        var current = that._current;
18445                        if(current && current.is(":visible")) {
18446                            current.addClass(FOCUSED);
18447                        } else {
18448                            currentProxy(that.table.find(FIRST_CELL_SELECTOR));
18449                        }
18450                    },
18451                    focusout: function(e) {
18452                        if (that._current) {
18453                            that._current.removeClass(FOCUSED);
18454                        }
18455                        e.stopPropagation();
18456                    },
18457                    keydown: function(e) {
18458                        var key = e.keyCode,
18459                            current = that.current(),
18460                            shiftKey = e.shiftKey,
18461                            dataSource = that.dataSource,
18462                            pageable = that.options.pageable,
18463                            canHandle = !$(e.target).is(":button,a,:input,a>.t-icon"),
18464                            isInCell = that._editMode() == "incell",
18465                            currentIndex,
18466                            cell,
18467                            handled = false;
18468
18469                        if (canHandle && keys.UP === key) {
18470                            currentProxy(current ? current.parent().prevAll(ROW_SELECTOR).last().children(":eq(" + current.index() + "),:eq(0)").last() : table.find(FIRST_CELL_SELECTOR));
18471                            handled = true;
18472                        } else if (canHandle && keys.DOWN === key) {
18473                            currentProxy(current ? current.parent().nextAll(ROW_SELECTOR).first().children(":eq(" + current.index() + "),:eq(0)").last() : table.find(FIRST_CELL_SELECTOR));
18474                            handled = true;
18475                        } else if (canHandle && keys.LEFT === key) {
18476                            currentProxy(current ? current.prevAll(DATA_CELL + ":first") : table.find(FIRST_CELL_SELECTOR));
18477                            handled = true;
18478                        } else if (canHandle && keys.RIGHT === key) {
18479                            currentProxy(current ? current.nextAll(":visible:first") : table.find(FIRST_CELL_SELECTOR));
18480                            handled = true;
18481                        } else if (canHandle && pageable && keys.PAGEDOWN == key) {
18482                            that._current = null;
18483                            dataSource.page(dataSource.page() + 1);
18484                            handled = true;
18485                        } else if (canHandle && pageable && keys.PAGEUP == key) {
18486                            that._current = null;
18487                            dataSource.page(dataSource.page() - 1);
18488                            handled = true;
18489                        } else if (that.options.editable) {
18490                            current = current ? current : table.find(FIRST_CELL_SELECTOR);
18491                            if (keys.ENTER == key || keys.F12 == key) {
18492                                that._handleEditing(current);
18493                                handled = true;
18494                            } else if (keys.TAB == key && isInCell) {
18495                                cell = shiftKey ? current.prevAll(DATA_CELL + ":first") : current.nextAll(":visible:first");
18496                                if (!cell.length) {
18497                                    cell = current.parent()[shiftKey ? "prevAll" : "nextAll"]("tr:not(.k-grouping-row,.k-detail-row):visible")
18498                                        .children(DATA_CELL + (shiftKey ? ":last" : ":first"));
18499                                }
18500
18501                                if (cell.length) {
18502                                    that._handleEditing(current, cell);
18503                                    handled = true;
18504                                }
18505                            } else if (keys.ESC == key && that._editContainer) {
18506                                if (that._editContainer.has(current[0]) || current[0] === that._editContainer[0]) {
18507                                    if (isInCell) {
18508                                        that.closeCell();
18509                                    } else {
18510                                        currentIndex = that.items().index(current.parent());
18511                                        document.activeElement.blur();
18512                                        that.cancelRow();
18513                                        if (currentIndex >= 0) {
18514                                            that.current(that.items().eq(currentIndex).children().filter(DATA_CELL).first());
18515                                        }
18516                                    }
18517
18518                                    if (browser.msie && parseInt(browser.version, 10) < 9) {
18519                                        document.body.focus();
18520                                    }
18521                                    wrapper.focus();
18522                                    handled = true;
18523                                }
18524                            }
18525                        }
18526
18527                        if(handled) {
18528                            e.preventDefault();
18529                            e.stopPropagation();
18530                        }
18531                    }
18532                });
18533
18534                wrapper.delegate(selector, "mousedown", clickCallback); }
18535        },
18536
18537        _handleEditing: function(current, next) {
18538            var that = this,
18539                mode = that._editMode(),
18540                editContainer = that._editContainer,
18541                idx,
18542                isEdited;
18543
18544            if (mode == "incell") {
18545                isEdited = current.hasClass("k-edit-cell");
18546            } else {
18547                isEdited = current.parent().hasClass("k-grid-edit-row");
18548            }
18549
18550            if (that.editable) {
18551                if ($.contains(editContainer[0], document.activeElement)) {
18552                    $(document.activeElement).blur();
18553                }
18554
18555                if (that.editable.end()) {
18556                    if (mode == "incell") {
18557                        that.closeCell();
18558                    } else {
18559                        if (current.parent()[0] === editContainer[0]) {
18560                            idx = that.items().index(current.parent());
18561                        } else {
18562                            idx = that.items().index(editContainer);
18563                        }
18564                        that.saveRow();
18565                        that.current(that.items().eq(idx).children().filter(DATA_CELL).first());
18566                        isEdited = true;
18567                    }
18568                } else {
18569                    if (mode == "incell") {
18570                        that.current(editContainer);
18571                    } else {
18572                        that.current(editContainer.children().filter(DATA_CELL).first());
18573                    }
18574                    editContainer.find(":input:visible:first").focus();
18575                    return;
18576                }
18577            }
18578
18579            if (next) {
18580                that.current(next);
18581            }
18582
18583            that.wrapper.focus();
18584            if ((!isEdited && !next) || next) {
18585                if (mode == "incell") {
18586                    that.editCell(that.current());
18587                } else {
18588                    that.editRow(that.current().parent());
18589                }
18590            }
18591        },
18592
18593        _wrapper: function() {
18594            var that = this,
18595                table = that.table,
18596                height = that.options.height,
18597                wrapper = that.element;
18598
18599            if (!wrapper.is("div")) {
18600               wrapper = wrapper.wrap("<div/>").parent();
18601            }
18602
18603            that.wrapper = wrapper.addClass("k-grid k-widget")
18604                                  .attr(TABINDEX, math.max(table.attr(TABINDEX) || 0, 0));
18605
18606            table.removeAttr(TABINDEX);
18607
18608            if (height) {
18609                that.wrapper.css(HEIGHT, height);
18610                table.css(HEIGHT, "auto");
18611            }
18612        },
18613
18614        _tbody: function() {
18615            var that = this,
18616                table = that.table,
18617                tbody;
18618
18619            tbody = table.find(">tbody");
18620
18621            if (!tbody.length) {
18622                tbody = $("<tbody/>").appendTo(table);
18623            }
18624
18625            that.tbody = tbody;
18626        },
18627
18628        _scrollable: function() {
18629            var that = this,
18630                header,
18631                table,
18632                options = that.options,
18633                scrollable = options.scrollable,
18634                scrollbar = kendo.support.scrollbar();
18635
18636            if (scrollable) {
18637                header = that.wrapper.children(".k-grid-header");
18638
18639                if (!header[0]) {
18640                    header = $('<div class="k-grid-header" />').insertBefore(that.table);
18641                }
18642
18643                // workaround for IE issue where scroll is not raised if container is same width as the scrollbar
18644                header.css("padding-right", scrollable.virtual ? scrollbar + 1 : scrollbar);
18645                table = $('<table cellspacing="0" />');
18646                table.append(that.thead);
18647                header.empty().append($('<div class="k-grid-header-wrap" />').append(table));
18648
18649                that.content = that.table.parent();
18650
18651                if (that.content.is(".k-virtual-scrollable-wrap")) {
18652                    that.content = that.content.parent();
18653                }
18654
18655                if (!that.content.is(".k-grid-content, .k-virtual-scrollable-wrap")) {
18656                    that.content = that.table.wrap('<div class="k-grid-content" />').parent();
18657
18658                    if (scrollable !== true && scrollable.virtual) {
18659                        that.virtualScrollable = new VirtualScrollable(that.content, {
18660                                dataSource: that.dataSource,
18661                                itemHeight: proxy(that._averageRowHeight, that)
18662                            });
18663                    }
18664                }
18665
18666                that.scrollables = header.children(".k-grid-header-wrap"); // the footer does not exists here yet!
18667
18668                if (scrollable.virtual) {
18669                    that.content.find(">.k-virtual-scrollable-wrap").bind('scroll', function () {
18670                        that.scrollables.scrollLeft(this.scrollLeft);
18671                    });
18672                } else {
18673                    that.content.bind('scroll', function () {
18674                        that.scrollables.scrollLeft(this.scrollLeft);
18675                    });
18676
18677                    var touchScroller = kendo.touchScroller(that.content);
18678                    if (touchScroller && touchScroller.movable) {
18679                        touchScroller.movable.bind("change", function(e) {
18680                            that.scrollables.scrollLeft(-e.sender.x);
18681                        });
18682                    }
18683                }
18684            }
18685        },
18686
18687        _setContentHeight: function() {
18688            var that = this,
18689                options = that.options,
18690                height = that.wrapper.innerHeight(),
18691                header = that.wrapper.children(".k-grid-header"),
18692                scrollbar = kendo.support.scrollbar();
18693
18694            if (options.scrollable) {
18695
18696                height -= header.outerHeight();
18697
18698                if (that.pager) {
18699                    height -= that.pager.element.outerHeight();
18700                }
18701
18702                if(options.groupable) {
18703                    height -= that.wrapper.children(".k-grouping-header").outerHeight();
18704                }
18705
18706                if(options.toolbar) {
18707                    height -= that.wrapper.children(".k-grid-toolbar").outerHeight();
18708                }
18709
18710                if (that.footerTemplate) {
18711                    height -= that.wrapper.children(".k-grid-footer").outerHeight();
18712                }
18713
18714                var isGridHeightSet = function(el) {
18715                    var initialHeight, newHeight;
18716                    if (el[0].style.height) {
18717                        return true;
18718                    } else {
18719                        initialHeight = el.height();
18720                    }
18721
18722                    el.height("auto");
18723                    newHeight = el.height();
18724
18725                    if (initialHeight != newHeight) {
18726                        el.height("");
18727                        return true;
18728                    }
18729                    el.height("");
18730                    return false;
18731                };
18732
18733                if (isGridHeightSet(that.wrapper)) { // set content height only if needed
18734                    if (height > scrollbar * 2) { // do not set height if proper scrollbar cannot be displayed
18735                        that.content.height(height);
18736                    } else {
18737                        that.content.height(scrollbar * 2 + 1);
18738                    }
18739                }
18740            }
18741        },
18742
18743        _averageRowHeight: function() {
18744            var that = this,
18745                rowHeight = that._rowHeight;
18746
18747            if (!that._rowHeight) {
18748                that._rowHeight = rowHeight = that.table.outerHeight() / that.table[0].rows.length;
18749                that._sum = rowHeight;
18750                that._measures = 1;
18751            }
18752
18753            var currentRowHeight = that.table.outerHeight() / that.table[0].rows.length;
18754
18755            if (rowHeight !== currentRowHeight) {
18756                that._measures ++;
18757                that._sum += currentRowHeight;
18758                that._rowHeight = that._sum / that._measures;
18759            }
18760            return rowHeight;
18761        },
18762
18763        _dataSource: function() {
18764            var that = this,
18765                options = that.options,
18766                pageable,
18767                dataSource = options.dataSource;
18768
18769            dataSource = isArray(dataSource) ? { data: dataSource } : dataSource;
18770
18771            if (isPlainObject(dataSource)) {
18772                extend(dataSource, { table: that.table, fields: that.columns });
18773
18774                pageable = options.pageable;
18775
18776                if (isPlainObject(pageable) && pageable.pageSize !== undefined) {
18777                    dataSource.pageSize = pageable.pageSize;
18778                }
18779            }
18780
18781            if (that.dataSource && that._refreshHandler) {
18782                that.dataSource.unbind(CHANGE, that._refreshHandler)
18783                                .unbind(REQUESTSTART, that._requestStartHandler)
18784                                .unbind(ERROR, that._errorHandler);
18785            } else {
18786                that._refreshHandler = proxy(that.refresh, that);
18787                that._requestStartHandler = proxy(that._requestStart, that);
18788                that._errorHandler = proxy(that._error, that);
18789            }
18790
18791            that.dataSource = DataSource.create(dataSource)
18792                                .bind(CHANGE, that._refreshHandler)
18793                                .bind(REQUESTSTART, that._requestStartHandler)
18794                                .bind(ERROR, that._errorHandler);
18795        },
18796
18797        _error: function() {
18798            this._progress(false);
18799        },
18800
18801        _requestStart: function() {
18802            this._progress(true);
18803        },
18804
18805        _modelChange: function(e) {
18806            var that = this,
18807                model = e.model,
18808                row = that.tbody.find("tr[" + kendo.attr("uid") + "=" + model.uid +"]"),
18809                cell,
18810                column,
18811                isAlt = row.hasClass("k-alt"),
18812                tmp,
18813                idx,
18814                length;
18815
18816            if (row.children(".k-edit-cell").length && !that.options.rowTemplate) {
18817                row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
18818                    cell = $(this);
18819                    column = that.columns[that.cellIndex(cell)];
18820
18821                    if (column.field === e.field) {
18822                        if (!cell.hasClass("k-edit-cell")) {
18823                            that._displayCell(cell, column, model);
18824                            $('<span class="k-dirty"/>').prependTo(cell);
18825                        } else {
18826                            cell.addClass("k-dirty-cell");
18827                        }
18828                    }
18829                });
18830
18831            } else if (!row.hasClass("k-grid-edit-row")) {
18832                tmp = $((isAlt ? that.altRowTemplate : that.rowTemplate)(model));
18833
18834                row.replaceWith(tmp);
18835
18836                for (idx = 0, length = that.columns.length; idx < length; idx++) {
18837                    column = that.columns[idx];
18838
18839                    if (column.field === e.field) {
18840                        cell = tmp.children(":not(.k-group-cell,.k-hierarchy-cell)").eq(idx);
18841                        $('<span class="k-dirty"/>').prependTo(cell);
18842                    }
18843                }
18844                that.trigger("itemChange", { item: tmp, data: model, ns: ui });
18845            }
18846
18847        },
18848
18849        _pageable: function() {
18850            var that = this,
18851                wrapper,
18852                pageable = that.options.pageable;
18853
18854            if (pageable) {
18855                wrapper = that.wrapper.children("div.k-grid-pager").empty();
18856
18857                if (!wrapper.length) {
18858                    wrapper = $('<div class="k-pager-wrap k-grid-pager"/>').appendTo(that.wrapper);
18859                }
18860
18861                if (that.pager) {
18862                    that.pager.destroy();
18863                }
18864
18865                if (typeof pageable === "object" && pageable instanceof kendo.ui.Pager) {
18866                    that.pager = pageable;
18867                } else {
18868                    that.pager = new kendo.ui.Pager(wrapper, extend({}, pageable, { dataSource: that.dataSource }));
18869                }
18870            }
18871        },
18872
18873        _footer: function() {
18874            var that = this,
18875                aggregates = that.dataSource.aggregates(),
18876                html = "",
18877                footerTemplate = that.footerTemplate,
18878                options = that.options;
18879
18880            if (footerTemplate) {
18881                aggregates = !isEmptyObject(aggregates) ? aggregates : buildEmptyAggregatesObject(that.dataSource.aggregate());
18882
18883                html = $(that._wrapFooter(footerTemplate(aggregates)));
18884
18885                if (that.footer) {
18886                    var tmp = html;
18887
18888                    that.footer.replaceWith(tmp);
18889                    that.footer = tmp;
18890                } else {
18891                    if (options.scrollable) {
18892                        that.footer = options.pageable ? html.insertBefore(that.wrapper.children("div.k-grid-pager")) : html.appendTo(that.wrapper);
18893                        that.scrollables = that.scrollables.add(that.footer.children(".k-grid-footer-wrap"));
18894                    } else {
18895                        that.footer = html.insertBefore(that.tbody);
18896                    }
18897                }
18898
18899                if (options.resizable && that._footerWidth) {
18900                    that.footer.find("table").css('width', that._footerWidth);
18901                }
18902
18903                that._setContentHeight();
18904            }
18905        },
18906
18907        _wrapFooter: function(footerRow) {
18908            var that = this,
18909                html = "";
18910
18911            if (that.options.scrollable) {
18912                html = $('<div class="k-grid-footer"><div class="k-grid-footer-wrap"><table cellspacing="0"><tbody>' + footerRow + '</tbody></table></div></div>');
18913                that._appendCols(html.find("table"));
18914                return html;
18915            }
18916
18917            return '<tfoot class="k-grid-footer">' + footerRow + '</tfoot>';
18918        },
18919
18920        _filterable: function() {
18921            var that = this,
18922                columns = that.columns,
18923                filterable = that.options.filterable;
18924
18925            if (filterable) {
18926                that.thead
18927                    .find("th:not(.k-hierarchy-cell)")
18928                    .each(function(index) {
18929                        if (columns[index].filterable !== false) {
18930                            $(this).kendoFilterMenu(extend(true, {}, filterable, columns[index].filterable, { dataSource: that.dataSource }));
18931                        }
18932                    });
18933            }
18934        },
18935
18936        _sortable: function() {
18937            var that = this,
18938                columns = that.columns,
18939                column,
18940                sortable = that.options.sortable;
18941
18942            if (sortable) {
18943                that.thead
18944                    .find("th:not(.k-hierarchy-cell)")
18945                    .each(function(index) {
18946                        column = columns[index];
18947                        if (column.sortable !== false && !column.command) {
18948                            $(this).kendoSortable(extend({}, sortable, { dataSource: that.dataSource }));
18949                        }
18950                    });
18951            }
18952        },
18953
18954        _columns: function(columns) {
18955            var that = this,
18956                table = that.table,
18957                encoded,
18958                cols = table.find("col"),
18959                dataSource = that.options.dataSource;
18960
18961            // using HTML5 data attributes as a configuration option e.g. <th data-field="foo">Foo</foo>
18962            columns = columns.length ? columns : map(table.find("th"), function(th, idx) {
18963                th = $(th);
18964                var sortable = th.attr(kendo.attr("sortable")),
18965                    filterable = th.attr(kendo.attr("filterable")),
18966                    type = th.attr(kendo.attr("type")),
18967                    groupable = th.attr(kendo.attr("groupable")),
18968                    field = th.attr(kendo.attr("field"));
18969
18970                if (!field) {
18971                   field = th.text().replace(/\s|[^A-z0-9]/g, "");
18972                }
18973
18974                return {
18975                    field: field,
18976                    type: type,
18977                    sortable: sortable !== "false",
18978                    filterable: filterable !== "false",
18979                    groupable: groupable !== "false",
18980                    template: th.attr(kendo.attr("template")),
18981                    width: cols.eq(idx).css("width")
18982                };
18983            });
18984
18985            encoded = !(that.table.find("tbody tr").length > 0 && (!dataSource || !dataSource.transport));
18986
18987            that.columns = map(columns, function(column) {
18988                column = typeof column === STRING ? { field: column } : column;
18989                return extend({ encoded: encoded }, column);
18990            });
18991        },
18992
18993        _tmpl: function(rowTemplate, alt) {
18994            var that = this,
18995                settings = extend({}, kendo.Template, that.options.templateSettings),
18996                idx,
18997                length = that.columns.length,
18998                template,
18999                state = { storage: {}, count: 0 },
19000                column,
19001                type,
19002                hasDetails = that._hasDetails(),
19003                className = [],
19004                groups = that.dataSource.group().length;
19005
19006            if (!rowTemplate) {
19007                rowTemplate = "<tr";
19008
19009                if (alt) {
19010                    className.push("k-alt");
19011                }
19012
19013                if (hasDetails) {
19014                    className.push("k-master-row");
19015                }
19016
19017                if (className.length) {
19018                    rowTemplate += ' class="' + className.join(" ") + '"';
19019                }
19020
19021                if (length) { // data item is an object
19022                    rowTemplate += ' ' + kendo.attr("uid") + '="#=uid#"';
19023                }
19024
19025                rowTemplate += ">";
19026
19027                if (groups > 0) {
19028                    rowTemplate += groupCells(groups);
19029                }
19030
19031                if (hasDetails) {
19032                    rowTemplate += '<td class="k-hierarchy-cell"><a class="k-icon k-plus" href="\\#"></a></td>';
19033                }
19034
19035                for (idx = 0; idx < length; idx++) {
19036                    column = that.columns[idx];
19037                    template = column.template;
19038                    type = typeof template;
19039
19040                    rowTemplate += "<td>";
19041                    rowTemplate += that._cellTmpl(column, state);
19042
19043                    rowTemplate += "</td>";
19044                }
19045
19046                rowTemplate += "</tr>";
19047            }
19048
19049            rowTemplate = kendo.template(rowTemplate, settings);
19050
19051            if (state.count > 0) {
19052                return proxy(rowTemplate, state.storage);
19053            }
19054
19055            return rowTemplate;
19056        },
19057
19058        _cellTmpl: function(column, state) {
19059            var that = this,
19060                settings = extend({}, kendo.Template, that.options.templateSettings),
19061                template = column.template,
19062                paramName = settings.paramName,
19063                html = "",
19064                idx,
19065                length,
19066                format = column.format,
19067                type = typeof template;
19068
19069            if (column.command) {
19070                if (isArray(column.command)) {
19071                    for (idx = 0, length = column.command.length; idx < length; idx++) {
19072                        html += that._createButton(column.command[idx]);
19073                    }
19074                    return html.replace(templateHashRegExp, "\\#");
19075                }
19076                return that._createButton(column.command).replace(templateHashRegExp, "\\#");
19077            }
19078
19079            if (type === FUNCTION) {
19080                state.storage["tmpl" + state.count] = template;
19081                html += "#=this.tmpl" + state.count + "(" + paramName + ")#";
19082                state.count ++;
19083            } else if (type === STRING) {
19084                html += template;
19085            } else {
19086                html += column.encoded ? "${" : "#=";
19087
19088                if (format) {
19089                    html += 'kendo.format(\"' + format.replace(formatRegExp,"\\}") + '\",';
19090                }
19091
19092                if (!settings.useWithBlock) {
19093                    html += paramName + ".";
19094                }
19095
19096                html += column.field;
19097
19098                if (format) {
19099                    html += ")";
19100                }
19101
19102                html += column.encoded ? "}" : "#";
19103            }
19104            return html;
19105        },
19106
19107        _templates: function() {
19108            var that = this,
19109                options = that.options,
19110                dataSource = that.dataSource,
19111                groups = dataSource.group(),
19112                aggregates = dataSource.aggregate();
19113
19114            that.rowTemplate = that._tmpl(options.rowTemplate);
19115            that.altRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, true);
19116
19117            if (that._hasDetails()) {
19118                that.detailTemplate = that._detailTmpl(options.detailTemplate || "");
19119            }
19120
19121            if (!isEmptyObject(aggregates) ||
19122                $.grep(that.columns, function(column) { return column.footerTemplate; }).length) {
19123
19124                that.footerTemplate = that._footerTmpl(aggregates, "footerTemplate", "k-footer-template");
19125            }
19126
19127            if (groups.length && $.grep(that.columns, function(column) { return column.groupFooterTemplate; }).length) {
19128                aggregates = $.map(groups, function(g) { return g.aggregates; });
19129                that.groupFooterTemplate = that._footerTmpl(aggregates, "groupFooterTemplate", "k-group-footer");
19130            }
19131        },
19132
19133        _footerTmpl: function(aggregates, templateName, rowClass) {
19134            var that = this,
19135                settings = extend({}, kendo.Template, that.options.templateSettings),
19136                paramName = settings.paramName,
19137                html = "",
19138                idx,
19139                length,
19140                columns = that.columns,
19141                template,
19142                type,
19143                storage = {},
19144                count = 0,
19145                scope = {},
19146                dataSource = that.dataSource,
19147                groups = dataSource.group().length,
19148                fieldsMap = buildEmptyAggregatesObject(aggregates),
19149                column;
19150
19151            html += '<tr class="' + rowClass + '">';
19152
19153            if (groups > 0) {
19154                html += groupCells(groups);
19155            }
19156
19157            if (that._hasDetails()) {
19158                html += '<td class="k-hierarchy-cell">&nbsp;</td>';
19159            }
19160
19161            for (idx = 0, length = that.columns.length; idx < length; idx++) {
19162                column = columns[idx];
19163                template = column[templateName];
19164                type = typeof template;
19165
19166                html += "<td>";
19167
19168                if (template) {
19169                    if (type !== FUNCTION) {
19170                        scope = fieldsMap[column.field] ? extend({}, settings, { paramName: paramName + "." + column.field }) : {};
19171                        template = kendo.template(template, scope);
19172                    }
19173
19174                    storage["tmpl" + count] = template;
19175                    html += "#=this.tmpl" + count + "(" + paramName + ")#";
19176                    count ++;
19177                } else {
19178                    html += "&nbsp;";
19179                }
19180
19181                html += "</td>";
19182            }
19183
19184            html += '</tr>';
19185
19186            html = kendo.template(html, settings);
19187
19188            if (count > 0) {
19189                return proxy(html, storage);
19190            }
19191
19192            return html;
19193        },
19194
19195        _detailTmpl: function(template) {
19196            var that = this,
19197                html = "",
19198                settings = extend({}, kendo.Template, that.options.templateSettings),
19199                paramName = settings.paramName,
19200                templateFunctionStorage = {},
19201                templateFunctionCount = 0,
19202                groups = that.dataSource.group().length,
19203                columns = that.columns.length,
19204                type = typeof template;
19205
19206            html += '<tr class="k-detail-row">';
19207            if (groups > 0) {
19208                html += groupCells(groups);
19209            }
19210            html += '<td class="k-hierarchy-cell"></td><td class="k-detail-cell"' + (columns ? ' colspan="' + columns + '"' : '') + ">";
19211
19212            if (type === FUNCTION) {
19213                templateFunctionStorage["tmpl" + templateFunctionCount] = template;
19214                html += "#=this.tmpl" + templateFunctionCount + "(" + paramName + ")#";
19215                templateFunctionCount ++;
19216            } else {
19217                html += template;
19218            }
19219
19220            html += "</td></tr>";
19221
19222            html = kendo.template(html, settings);
19223
19224            if (templateFunctionCount > 0) {
19225                return proxy(html, templateFunctionStorage);
19226            }
19227
19228            return html;
19229        },
19230
19231        _hasDetails: function() {
19232            var that = this;
19233
19234            return that.options.detailTemplate !== undefined  || (that._events[DETAILINIT] || []).length;
19235        },
19236
19237        _details: function() {
19238            var that = this;
19239
19240            that.table.delegate(".k-hierarchy-cell .k-plus, .k-hierarchy-cell .k-minus", CLICK, function(e) {
19241                var button = $(this),
19242                    expanding = button.hasClass("k-plus"),
19243                    masterRow = button.closest("tr.k-master-row"),
19244                    detailRow,
19245                    detailTemplate = that.detailTemplate,
19246                    data,
19247                    hasDetails = that._hasDetails();
19248
19249                button.toggleClass("k-plus", !expanding)
19250                    .toggleClass("k-minus", expanding);
19251
19252                if(hasDetails && !masterRow.next().hasClass("k-detail-row")) {
19253                    data = that.dataItem(masterRow),
19254                    $(detailTemplate(data)).insertAfter(masterRow);
19255
19256                    that.trigger(DETAILINIT, { masterRow: masterRow, detailRow: masterRow.next(), data: data, detailCell: masterRow.next().find(".k-detail-cell") });
19257                }
19258
19259                detailRow = masterRow.next();
19260
19261                that.trigger(expanding ? DETAILEXPAND : DETAILCOLLAPSE, { masterRow: masterRow, detailRow: detailRow});
19262                detailRow.toggle(expanding);
19263
19264                e.preventDefault();
19265                return false;
19266            });
19267        },
19268
19269        dataItem: function(tr) {
19270            return this._data[this.tbody.find('> tr:not(.k-grouping-row,.k-detail-row,.k-group-footer)').index($(tr))];
19271        },
19272
19273        expandRow: function(tr) {
19274            $(tr).find('> td .k-plus, > td .k-expand').click();
19275        },
19276
19277        collapseRow: function(tr) {
19278            $(tr).find('> td .k-minus, > td .k-collapse').click();
19279        },
19280
19281        _thead: function() {
19282            var that = this,
19283                columns = that.columns,
19284                hasDetails = that._hasDetails() && columns.length,
19285                idx,
19286                length,
19287                html = "",
19288                thead = that.table.find("thead"),
19289                tr,
19290                th;
19291
19292            if (!thead.length) {
19293                thead = $("<thead/>").insertBefore(that.tbody);
19294            }
19295
19296            tr = that.table.find("tr").filter(":has(th)");
19297
19298            if (!tr.length) {
19299                tr = thead.children().first();
19300                if(!tr.length) {
19301                    tr = $("<tr/>");
19302                }
19303            }
19304
19305            if (!tr.children().length) {
19306                if (hasDetails) {
19307                    html += '<th class="k-hierarchy-cell">&nbsp;</th>';
19308                }
19309
19310                for (idx = 0, length = columns.length; idx < length; idx++) {
19311                    th = columns[idx];
19312
19313                    if (!th.command) {
19314                        html += "<th " + kendo.attr("field") + "='" + th.field + "' ";
19315                        if (th.title) {
19316                            html += kendo.attr("title") + '="' + th.title.replace(/'/g, "\'") + '" ';
19317                        }
19318
19319                        if (th.groupable !== undefined) {
19320                            html += kendo.attr("groupable") + "='" + th.groupable + "' ";
19321                        }
19322
19323                        if (th.aggregates) {
19324                            html += kendo.attr("aggregates") + "='" + th.aggregates + "'";
19325                        }
19326
19327                        html += ">" + (th.title || th.field || "") + "</th>";
19328                    } else {
19329                        html += "<th>" + (th.title || "") + "</th>";
19330                    }
19331                }
19332
19333                tr.html(html);
19334            } else if (hasDetails) {
19335                tr.prepend('<th class="k-hierarchy-cell">&nbsp;</th>');
19336            }
19337
19338            tr.find("th").addClass("k-header");
19339
19340            if(!that.options.scrollable) {
19341                thead.addClass("k-grid-header");
19342            }
19343
19344            tr.appendTo(thead);
19345
19346            that.thead = thead;
19347
19348            that._sortable();
19349
19350            that._filterable();
19351
19352            that._scrollable();
19353
19354            that._updateCols();
19355
19356            that._setContentHeight();
19357
19358            that._resizable();
19359
19360            that._draggable();
19361
19362            that._reorderable();
19363        },
19364
19365        _updateCols: function() {
19366            var that = this;
19367
19368            that._appendCols(that.thead.parent().add(that.table));
19369        },
19370
19371        _appendCols: function(table) {
19372            var that = this,
19373                colgroup = table.find("colgroup"),
19374                width,
19375                cols = map(that.columns, function(column) {
19376                    width = column.width;
19377                    if (width && parseInt(width, 10) !== 0) {
19378                        return kendo.format('<col style="width:{0}"/>', typeof width === STRING? width : width + "px");
19379                    }
19380
19381                    return "<col />";
19382                }),
19383                groups = that.dataSource.group().length;
19384
19385            if (that._hasDetails()) {
19386                cols.splice(0, 0, '<col class="k-hierarchy-col" />');
19387            }
19388
19389            if (colgroup.length) {
19390                colgroup.remove();
19391            }
19392
19393            colgroup = $("<colgroup/>").append($(new Array(groups + 1).join('<col class="k-group-col">') + cols.join("")));
19394
19395            table.prepend(colgroup);
19396        },
19397
19398        _autoColumns: function(schema) {
19399            if (schema && schema.toJSON) {
19400                var that = this,
19401                    field;
19402
19403                schema = schema.toJSON();
19404
19405                for (field in schema) {
19406                    that.columns.push({ field: field });
19407                }
19408
19409                that._thead();
19410
19411                that._templates();
19412            }
19413        },
19414
19415        _rowsHtml: function(data) {
19416            var that = this,
19417                html = "",
19418                idx,
19419                length,
19420                rowTemplate = that.rowTemplate,
19421                altRowTemplate = that.altRowTemplate;
19422
19423            for (idx = 0, length = data.length; idx < length; idx++) {
19424                if (idx % 2) {
19425                    html += altRowTemplate(data[idx]);
19426                } else {
19427                    html += rowTemplate(data[idx]);
19428                }
19429
19430                that._data.push(data[idx]);
19431            }
19432
19433            return html;
19434        },
19435
19436        _groupRowHtml: function(group, colspan, level) {
19437            var that = this,
19438                html = "",
19439                idx,
19440                length,
19441                field = group.field,
19442                column = $.grep(that.columns, function(column) { return column.field == field; })[0] || { },
19443                value = column.format ? kendo.format(column.format, group.value) : group.value,
19444                template = column.groupHeaderTemplate,
19445                text =  (column.title || field) + ': ' + value,
19446                data = extend({}, { field: group.field, value: group.value }, group.aggregates[group.field]),
19447                groupItems = group.items;
19448
19449            if (template) {
19450                text  = typeof template === FUNCTION ? template(data) : kendo.template(template)(data);
19451            }
19452
19453            html +=  '<tr class="k-grouping-row">' + groupCells(level) +
19454                      '<td colspan="' + colspan + '">' +
19455                        '<p class="k-reset">' +
19456                         '<a class="k-icon k-collapse" href="#"></a>
19456' + text +
19457                         '</p></td></tr>';
19458
19459            if(group.hasSubgroups) {
19460                for(idx = 0, length = groupItems.length; idx < length; idx++) {
19461                    html += that._groupRowHtml(groupItems[idx], colspan - 1, level + 1);
19462                }
19463            } else {
19464                html += that._rowsHtml(groupItems);
19465            }
19466
19467            if (that.groupFooterTemplate) {
19468                html += that.groupFooterTemplate(group.aggregates);
19469            }
19470            return html;
19471        },
19472
19473        collapseGroup: function(group) {
19474            group = $(group).find(".k-icon").addClass("k-expand").removeClass("k-collapse").end();
19475
19476            var level = group.find(".k-group-cell").length,
19477                footerCount = 1,
19478                offset,
19479                tr;
19480
19481            group.nextAll("tr").each(function() {
19482                tr = $(this),
19483                offset = tr.find(".k-group-cell").length;
19484
19485                if (tr.hasClass("k-group-row")) {
19486                    footerCount++;
19487                } else if (tr.hasClass("k-group-footer")) {
19488                    footerCount--;
19489                }
19490
19491                if (offset <= level || (tr.hasClass("k-group-footer") && footerCount < 0)) {
19492                    return false;
19493                }
19494
19495                tr.hide();
19496            });
19497        },
19498
19499        expandGroup: function(group) {
19500            group = $(group).find(".k-icon").addClass("k-collapse").removeClass("k-expand").end();
19501            var that = this,
19502                level = group.find(".k-group-cell").length;
19503
19504            group.nextAll("tr").each(function () {
19505                var tr = $(this);
19506                var offset = tr.find(".k-group-cell").length;
19507                if (offset <= level) {
19508                    return false;
19509                }
19510
19511                if (offset == level + 1 && !tr.hasClass("k-detail-row")) {
19512                    tr.show();
19513
19514                    if (tr.hasClass("k-grouping-row") && tr.find(".k-icon").hasClass("k-collapse")) {
19515                        that.expandGroup(tr);
19516                    }
19517
19518                    if (tr.hasClass("k-master-row") && tr.find(".k-icon").hasClass("k-minus")) {
19519                        tr.next().show();
19520                    }
19521                }
19522            });
19523        },
19524
19525        _updateHeader: function(groups) {
19526            var that = this,
19527                cells = that.thead.find("th.k-group-cell"),
19528                length = cells.length;
19529
19530            if(groups > length) {
19531                $(new Array(groups - length + 1).join('<th class="k-group-cell k-header">&nbsp;</th>')).prependTo(that.thead.find("tr"));
19532            } else if(groups < length) {
19533                length = length - groups;
19534                $($.grep(cells, function(item, index) { return length > index; } )).remove();
19535            }
19536        },
19537
19538        _firstDataItem: function(data, grouped) {
19539            if(data && grouped) {
19540                if(data.hasSubgroups) {
19541                    data = this._firstDataItem(data.items[0], grouped);
19542                } else {
19543                    data = data.items[0];
19544                }
19545            }
19546            return data;
19547        },
19548
19549        _progress: function(toggle) {
19550            var that = this,
19551                element = that.element.is("table") ? that.element.parent() : (that.content && that.content.length ? that.content : that.element);
19552
19553            kendo.ui.progress(element, toggle);
19554        },
19555
19556        refresh: function(e) {
19557            var that = this,
19558                length,
19559                idx,
19560                html = "",
19561                data = that.dataSource.view(),
19562                tbody,
19563                placeholder,
19564                currentIndex,
19565                current = that.current(),
19566                groups = (that.dataSource.group() || []).length,
19567                colspan = groups + that.columns.length;
19568
19569            if (e && e.action === "itemchange" && that.editable) { // skip rebinding if editing is in progress
19570                return;
19571            }
19572
19573            that.trigger("dataBinding");
19574
19575            if (current && current.hasClass("k-state-focused")) {
19576                currentIndex = that.items().index(current.parent());
19577            }
19578
19579            that._destroyEditable();
19580
19581            that._progress(false);
19582
19583            that._data = [];
19584
19585            if (!that.columns.length) {
19586                that._autoColumns(that._firstDataItem(data[0], groups));
19587                colspan = groups + that.columns.length;
19588            }
19589
19590            that._group = groups > 0 || that._group;
19591
19592            if(that._group) {
19593                that._templates();
19594                that._updateCols();
19595                that._updateHeader(groups);
19596                that._group = groups > 0;
19597            }
19598
19599            if(groups > 0) {
19600                if (that.detailTemplate) {
19601                    colspan++;
19602                }
19603
19604                for (idx = 0, length = data.length; idx < length; idx++) {
19605                    html += that._groupRowHtml(data[idx], colspan, 0);
19606                }
19607            } else {
19608                html += that._rowsHtml(data);
19609            }
19610
19611            if (tbodySupportsInnerHtml) {
19612                that.tbody[0].innerHTML = html;
19613            } else {
19614                placeholder = document.createElement("div");
19615                placeholder.innerHTML = "<table><tbody>" + html + "</tbody></table>";
19616                tbody = placeholder.firstChild.firstChild;
19617                that.table[0].replaceChild(tbody, that.tbody[0]);
19618                that.tbody = $(tbody);
19619            }
19620
19621            that._footer();
19622
19623            if (currentIndex >= 0) {
19624                that.current(that.items().eq(currentIndex).children().filter(DATA_CELL).first());
19625            }
19626
19627            if (that.options.resizable) {
19628                that.positionColumnResizeHandles();
19629            }
19630
19631            that.trigger(DATABOUND);
19632       }
19633   });
19634
19635   function hasCommand(commands, name) {
19636       var idx, length, command;
19637
19638       if (typeof commands === STRING) {
19639           return commands === name;
19640       }
19641
19642       if (isArray(commands)) {
19643           for (idx = 0, length = commands.length; idx < length; idx++) {
19644               command = commands[idx];
19645
19646               if ((typeof command === STRING && command === name) || (command.name === name)) {
19647                   return true;
19648               }
19649           }
19650       }
19651       return false;
19652   }
19653
19654   ui.plugin(Grid);
19655   ui.plugin(VirtualScrollable);
19656})(jQuery);
19657(function($, undefined) {
19658    var kendo = window.kendo,
19659        CHANGE = "change",
19660        DATABOUND = "dataBound",
19661        DATABINDING = "dataBinding",
19662        Widget = kendo.ui.Widget,
19663        keys = kendo.keys,
19664        FOCUSSELECTOR =  ">*",
19665        REQUESTSTART = "requestStart",
19666        ERROR = "error",
19667        FOCUSED = "k-state-focused",
19668        FOCUSABLE = "k-focusable",
19669        SELECTED = "k-state-selected",
19670        KEDITITEM = "k-edit-item",
19671        STRING = "string",
19672        EDIT = "edit",
19673        REMOVE = "remove",
19674        proxy = $.proxy,
19675        progress = kendo.ui.progress,
19676        DataSource = kendo.data.DataSource;
19677
19678    var ListView = Widget.extend( {
19679        init: function(element, options) {
19680            var that = this;
19681
19682            options = $.isArray(options) ? { dataSource: options } : options;
19683
19684            Widget.fn.init.call(that, element, options);
19685
19686            options = that.options;
19687
19688            that.wrapper = that.element;
19689
19690            that._element();
19691
19692            that._dataSource();
19693
19694            that.template = kendo.template(options.template || "");
19695            that.altTemplate = kendo.template(options.altTemplate || options.template);
19696            that.editTemplate = kendo.template(options.editTemplate || "");
19697
19698            that._navigatable();
19699
19700            that._selectable();
19701
19702            if (that.options.autoBind){
19703                that.dataSource.fetch();
19704            }
19705
19706            kendo.notify(that);
19707        },
19708
19709        events: [
19710            CHANGE,
19711            DATABINDING,
19712            DATABOUND,
19713            EDIT,
19714            REMOVE
19715        ],
19716
19717        options: {
19718            name: "ListView",
19719            autoBind: true,
19720            selectable: false,
19721            navigatable: false,
19722            template: "",
19723            altTemplate: "",
19724            editTemplate: ""
19725        },
19726
19727        items: function() {
19728            return this.element.find(FOCUSSELECTOR);
19729        },
19730
19731        setDataSource: function(dataSource) {
19732            this.options.dataSource = dataSource;
19733            this._dataSource();
19734
19735            if (this.options.autoBind) {
19736                dataSource.fetch();
19737            }
19738        },
19739
19740        _dataSource: function() {
19741            var that = this;
19742
19743            if (that.dataSource && that._refreshHandler) {
19744                that.dataSource.unbind(CHANGE, that._refreshHandler)
19745                                .unbind(REQUESTSTART, that._requestStartHandler)
19746                                .unbind(ERROR, that._errorHandler);
19747            } else {
19748                that._refreshHandler = proxy(that.refresh, that);
19749                that._requestStartHandler = proxy(that._requestStart, that);
19750                that._errorHandler = proxy(that._error, that);
19751            }
19752
19753            that.dataSource = DataSource.create(that.options.dataSource)
19754                                .bind(CHANGE, that._refreshHandler)
19755                                .bind(REQUESTSTART, that._requestStartHandler)
19756                                .bind(ERROR, that._errorHandler);
19757        },
19758
19759        _requestStart: function() {
19760            progress(this.element, true);
19761        },
19762
19763        _error: function() {
19764            progress(this.element, false);
19765        },
19766
19767        _element: function() {
19768            this.element.addClass("k-widget k-listview");
19769        },
19770
19771        refresh: function(e) {
19772            var that = this,
19773                view = that.dataSource.view(),
19774                data,
19775                item,
19776                html = "",
19777                idx,
19778                length,
19779                template = that.template,
19780                altTemplate = that.altTemplate;
19781
19782            if (e && e.action === "itemchange") {
19783                if (!that.editable) {
19784                    data = e.items[0];
19785                    idx = view.indexOf(data);
19786
19787                    if (idx >= 0) {
19788                        item = $(template(data));
19789                        that.items().eq(idx).replaceWith(item);
19790
19791                        that.trigger("itemChange", {
19792                            item: item,
19793                            data: data
19794                        });
19795                    }
19796                }
19797
19798                return;
19799            }
19800
19801            that.trigger(DATABINDING);
19802
19803            that._destroyEditable();
19804
19805            for (idx = 0, length = view.length; idx < length; idx++) {
19806                if (idx % 2) {
19807                    html += altTemplate(view[idx]);
19808                } else {
19809                    html += template(view[idx]);
19810                }
19811            }
19812
19813            that.element.html(html);
19814
19815            that.trigger(DATABOUND);
19816        },
19817
19818        _selectable: function() {
19819            var that = this,
19820                multi,
19821                current,
19822                selectable = that.options.selectable,
19823                navigatable = that.options.navigatable;
19824
19825            if (selectable) {
19826                multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;
19827
19828                that.selectable = new kendo.ui.Selectable(that.element, {
19829                    multiple: multi,
19830                    filter: FOCUSSELECTOR,
19831                    change: function() {
19832                        that.trigger(CHANGE);
19833                    }
19834                });
19835
19836                if (navigatable) {
19837                    that.element.keydown(function(e) {
19838                        if (e.keyCode === keys.SPACEBAR) {
19839                            current = that.current();
19840                            e.preventDefault();
19841                            if(multi) {
19842                                if(!e.ctrlKey) {
19843                                    that.selectable.clear();
19844                                } else {
19845                                    if(current.hasClass(SELECTED)) {
19846                                        current.removeClass(SELECTED);
19847                                        current = null;
19848                                    }
19849                                }
19850                            } else {
19851                                that.selectable.clear();
19852                            }
19853
19854                            that.selectable.value(current);
19855                        }
19856                    });
19857                }
19858            }
19859        },
19860
19861        current: function(element) {
19862            var that = this,
19863                current = that._current;
19864
19865            if (element !== undefined && element.length) {
19866                if (!current || current[0] !== element[0]) {
19867                    element.addClass(FOCUSED);
19868                    if (current) {
19869                        current.removeClass(FOCUSED);
19870                    }
19871                    that._current = element;
19872                }
19873            }
19874
19875            return that._current;
19876        },
19877
19878        _navigatable: function() {
19879            var that = this,
19880                navigatable = that.options.navigatable,
19881                element = that.element,
19882                currentProxy = proxy(that.current, that),
19883                clickCallback = function(e) {
19884                    currentProxy($(e.currentTarget));
19885                    if(!$(e.target).is(":button,a,:input,a>.k-icon,textarea")) {
19886                        element.focus();
19887                    }
19888                };
19889
19890            if (navigatable) {
19891                element.attr("tabIndex", Math.max(element.attr("tabIndex") || 0, 0));
19892                element.bind({
19893                    focus: function() {
19894                        var current = that._current;
19895                        if(current && current.is(":visible")) {
19896                            current.addClass(FOCUSED);
19897                        } else {
19898                            currentProxy(element.find(FOCUSSELECTOR).first());
19899                        }
19900                    },
19901                    focusout: function() {
19902                        if (that._current) {
19903                            that._current.removeClass(FOCUSED);
19904                        }
19905                    },
19906                    keydown: function(e) {
19907                        var key = e.keyCode,
19908                            current = that.current();
19909
19910                        if (keys.UP === key) {
19911                            that.current(current ? current.prev() : element.find(FOCUSSELECTOR).first());
19912                        } else if (keys.DOWN === key) {
19913                            that.current(current ? current.next() : element.find(FOCUSSELECTOR).first());
19914                        } else if (keys.PAGEUP == key) {
19915                            that._current = null;
19916                            that.dataSource.page(that.dataSource.page() - 1);
19917                        } else if (keys.PAGEDOWN == key) {
19918                            that._current = null;
19919                            that.dataSource.page(that.dataSource.page() + 1);
19920                        }
19921                    }
19922                });
19923
19924                element.addClass(FOCUSABLE).delegate("." + FOCUSABLE + FOCUSSELECTOR, "mousedown", clickCallback);
19925            }
19926       },
19927
19928       clearSelection: function() {
19929           var that = this;
19930           that.selectable.clear();
19931           that.trigger(CHANGE);
19932       },
19933
19934       select: function(items) {
19935           var that = this,
19936               selectable = that.selectable;
19937
19938            items = $(items);
19939            if(items.length) {
19940                if(!selectable.options.multiple) {
19941                    selectable.clear();
19942                    items = items.first();
19943                }
19944                selectable.value(items);
19945                return;
19946            }
19947
19948           return selectable.value();
19949       },
19950
19951       _destroyEditable: function() {
19952           var that = this;
19953           if (that.editable) {
19954               that.editable.destroy();
19955               delete that.editable;
19956           }
19957       },
19958
19959       _closeEditable: function(validate) {
19960           var that = this,
19961               editable = that.editable,
19962               data,
19963               container,
19964               valid = true;
19965
19966           if (editable) {
19967               if (validate) {
19968                   valid = editable.end();
19969               }
19970
19971               if (valid) {
19972                   data = that.dataSource.view()[editable.element.index()];
19973                   container = $(that.template(data));
19974                   that._destroyEditable();
19975                   editable.element.replaceWith(container);
19976               }
19977           }
19978
19979           return valid;
19980       },
19981
19982       edit: function(item) {
19983           var that = this,
19984               data = that.dataSource.view()[item.index()],
19985               container = $(that.editTemplate(data)).addClass(KEDITITEM);
19986
19987            that.cancel();
19988            item.replaceWith(container);
19989            that.editable = container.kendoEditable({ model: data, clearContainer: false, errorTemplate: false }).data("kendoEditable");
19990
19991            that.trigger(EDIT, { model: data, item: container });
19992       },
19993
19994       save: function() {
19995           if (this._closeEditable(true)) {
19996               this.dataSource.sync();
19997           }
19998       },
19999
20000       remove: function(item) {
20001           var that = this,
20002               dataSource = that.dataSource,
20003               data = dataSource.view()[item.index()];
20004
20005           if (!that.trigger(REMOVE, { model: data, item: item })) {
20006               item.hide();
20007               dataSource.remove(data);
20008               dataSource.sync();
20009           }
20010       },
20011
20012       add: function() {
20013           var that = this,
20014               dataSource = that.dataSource,
20015               index = dataSource.indexOf((dataSource.view() || [])[0]);
20016
20017           if (index < 0) {
20018               index = 0;
20019           }
20020
20021           that.cancel();
20022           dataSource.insert(index, {});
20023           that.edit(that.element.children().first());
20024       },
20025
20026       cancel: function() {
20027           var that = this,
20028               dataSource = that.dataSource,
20029               data,
20030               index = -1;
20031
20032           if (that.editable) {
20033               index = that.editable.element.index();
20034           }
20035
20036           if (index != -1) {
20037               data = dataSource.view()[index];
20038               dataSource.cancelChanges(data);
20039               that._closeEditable(false);
20040           }
20041       }
20042
20043    });
20044
20045    kendo.ui.plugin(ListView);
20046})(jQuery);
20047(function($,undefined) {
20048
20049    // Imports ================================================================
20050    var kendo = window.kendo,
20051        Class = kendo.Class,
20052        Widget = kendo.ui.Widget,
20053        extend = $.extend,
20054        deepExtend = kendo.deepExtend,
20055        keys = kendo.keys;
20056
20057    // options can be: template (as string), cssClass, title, defaultValue
20058    var ToolTemplate = Class.extend({
20059        init: function(options) {
20060            var that = this;
20061            that.options = options;
20062        },
20063
20064        getHtml: function() {
20065            var options = this.options;
20066            return kendo.template(options.template)({
20067                cssClass: options.cssClass,
20068                tooltip: options.title,
20069                initialValue: options.initialValue
20070            });
20071        }
20072    });
20073
20074    var EditorUtils = {
20075        select: function(editor) {
20076            editor.trigger("select", {});
20077        },
20078
20079        editorWrapperTemplate:
20080            '<table cellspacing="4" cellpadding="0" class="k-widget k-editor k-header"><tbody>' +
20081                '<tr><td class="k-editor-toolbar-wrap"><ul class="k-editor-toolbar"></ul></td></tr>' +
20082                '<tr><td class="k-editable-area"></td></tr>' +
20083            '</tbody></table>',
20084
20085        buttonTemplate:
20086            '<li class="k-editor-button">' +
20087                '<a href="" class="k-tool-icon #= cssClass #" unselectable="on" title="#= tooltip #">#= tooltip #</a>' +
20088            '</li>',
20089
20090        colorPickerTemplate:
20091            '<li class="k-editor-colorpicker">' +
20092                '<div class="k-widget k-colorpicker k-header #= cssClass #">' +
20093                    '<span class="k-tool-icon"><span class="k-selected-color"></span></span><span class="k-icon k-arrow-down"></span>' +
20094            '</div></li>',
20095
20096        comboBoxTemplate:
20097            '<li class="k-editor-combobox">' +
20098                '<select title="#= tooltip #" class="#= cssClass #"></select>' +
20099            '</li>',
20100
20101        dropDownListTemplate:
20102            '<li class="k-editor-selectbox">' +
20103                '<select title="#= tooltip #" class="#= cssClass #"></select>' +
20104            '</li>',
20105
20106        focusable: ".k-colorpicker,a.k-tool-icon:not(.k-state-disabled),.k-selectbox, .k-combobox .k-input",
20107
20108        wrapTextarea: function(textarea) {
20109
20110            var w = textarea.width(),
20111                h = textarea.height(),
20112                template = EditorUtils.editorWrapperTemplate,
20113                editorWrap = $(template).insertBefore(textarea).width(w).height(h),
20114                editArea = editorWrap.find(".k-editable-area");
20115
20116            textarea.appendTo(editArea).addClass("k-content k-raw-content").hide();
20117
20118            return textarea.closest(".k-editor");
20119        },
20120
20121        renderTools: function(editor, tools) {
20122            var editorTools = {},
20123                currentTool, tool, i,
20124                nativeTools = editor._nativeTools,
20125                template,
20126                options,
20127                toolsArea = $(editor.element).closest(".k-editor").find(".k-editor-toolbar");
20128
20129            if (tools) {
20130                for (i = 0; i < tools.length; i++) {
20131                    currentTool = tools[i];
20132
20133                    if ($.isPlainObject(currentTool)) {
20134                        options = extend({ cssClass: "k-custom", type: "button", tooltip: "" }, currentTool);
20135
20136                        if (options.name) {
20137                            options.cssClass = "k-" + options.name;
20138                        }
20139
20140                        if (!options.template) {
20141                            if (options.type == "button") {
20142                                options.template = EditorUtils.buttonTemplate;
20143                            }
20144                        }
20145                    } else if (editor._tools[currentTool]) {
20146                        editorTools[currentTool] = editor._tools[currentTool];
20147                        options = editorTools[currentTool].options;
20148                    }
20149
20150                    template = options.template;
20151
20152                    if (template) {
20153
20154                        if (template.getHtml) {
20155                            template = template.getHtml();
20156                        } else {
20157                            if (!$.isFunction(template)) {
20158                                template = kendo.template(template);
20159                            }
20160
20161                            template = template(options);
20162                        }
20163
20164                        tool = $(template).appendTo(toolsArea);
20165
20166                        if (options.type == "button" && options.exec) {
20167                            tool.find(".k-tool-icon").click($.proxy(options.exec, editor.element[0]));
20168                        }
20169                    }
20170                }
20171            }
20172
20173            for (i = 0; i < nativeTools.length; i++) {
20174                editorTools[nativeTools[i]] = editor._tools[nativeTools[i]];
20175            }
20176
20177            editor.options.tools = editorTools;
20178        },
20179
20180        createContentElement: function(textarea, stylesheets) {
20181            var iframe, wnd, doc,
20182                rtlStyle = textarea.closest(".k-rtl").length ? "direction:rtl;" : "";
20183
20184            textarea.hide();
20185            iframe = $("<iframe />", { src: 'javascript:"<html></html>"', frameBorder: "0" })
20186                            .css("display", "")
20187                            .addClass("k-content")
20188                            .insertBefore(textarea)[0];
20189
20190            wnd = iframe.contentWindow || iframe;
20191            doc = wnd.document || iframe.contentDocument;
20192
20193            doc.designMode = "On";
20194            doc.open();
20195            doc.write(
20196                    "<!DOCTYPE html><html><head>" +
20197                    "<meta charset='utf-8' />" +
20198                    "<style>" +
20199                        "html,body{padding:0;margin:0;font-family:Verdana,Geneva,sans-serif;background:#fff;}" +
20200                        "html{font-size:100%}body{font-size:.75em;line-height:1.5;padding-top:1px;margin-top:-1px;" +
20201                        rtlStyle +
20202                        "}" +
20203                        "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}" +
20204                        "p{margin:0 0 1em;padding:0 .2em}.k-marker{display:none;}.k-paste-container{position:absolute;
20204left:-10000px;width:1px;height:1px;overflow:hidden}" +
20205                        "ul,ol{padding-left:2.5em}" +
20206                        "a{color:#00a}" +
20207                        "code{font-size:1.23em}" +
20208                    "</style>" +
20209                    $.map(stylesheets, function(href){
20210                        return "<link rel='stylesheet' href='" + href + "'>";
20211                    }).join("") +
20212                    "</head><body spellcheck='false'></body></html>"
20213                );
20214
20215            doc.close();
20216
20217            return wnd;
20218        },
20219
20220        initializeContentElement: function(editor) {
20221            var isFirstKeyDown = true;
20222
20223            editor.window = EditorUtils.createContentElement($(editor.textarea), editor.options.stylesheets);
20224            editor.document = editor.window.contentDocument || editor.window.document;
20225            editor.body = editor.document.body;
20226
20227            $(editor.document)
20228                .bind({
20229                    keydown: function (e) {
20230                        if (e.keyCode === keys.F10) {
20231                            // Handling with timeout to avoid the default IE menu
20232                            setTimeout(function() {
20233                                var TABINDEX = "tabIndex",
20234                                    element = editor.wrapper,
20235                                    tabIndex = element.attr(TABINDEX);
20236
20237                                // Chrome can't focus something which has already been focused
20238                                element.attr(TABINDEX, tabIndex || 0).focus().find("li:has(" + focusable + ")").first().focus();
20239
20240                                if (!tabIndex && tabIndex !== 0) {
20241                                   element.removeAttr(TABINDEX);
20242                                }
20243
20244                            }, 100);
20245
20246                            e.preventDefault();
20247                            return;
20248                        }
20249
20250                        var toolName = editor.keyboard.toolFromShortcut(editor.options.tools, e);
20251
20252                        if (toolName) {
20253                            e.preventDefault();
20254                            if (!/undo|redo/.test(toolName)) {
20255                                editor.keyboard.endTyping(true);
20256                            }
20257                            editor.exec(toolName);
20258                            return false;
20259                        }
20260
20261                        if (editor.keyboard.isTypingKey(e) && editor.pendingFormats.hasPending()) {
20262                            if (isFirstKeyDown) {
20263                                isFirstKeyDown = false;
20264                            } else {
20265                                var range = editor.getRange();
20266                                editor.pendingFormats.apply(range);
20267                                editor.selectRange(range);
20268                            }
20269                        }
20270
20271                        editor.keyboard.clearTimeout();
20272
20273                        editor.keyboard.keydown(e);
20274                    },
20275                    keyup: function (e) {
20276                        var selectionCodes = [8, 9, 33, 34, 35, 36, 37, 38, 39, 40, 40, 45, 46];
20277
20278                        if ($.browser.mozilla && e.keyCode == keys.BACKSPACE) {
20279                            fixBackspace(editor, e);
20280                        }
20281
20282                        if ($.inArray(e.keyCode, selectionCodes) > -1 || (e.keyCode == 65 && e.ctrlKey && !e.altKey && !e.shiftKey)) {
20283                            editor.pendingFormats.clear();
20284                            select(editor);
20285                        }
20286
20287                        if (editor.keyboard.isTypingKey(e)) {
20288                            if (editor.pendingFormats.hasPending()) {
20289                                var range = editor.getRange();
20290                                editor.pendingFormats.apply(range);
20291                                editor.selectRange(range);
20292                            }
20293                        } else {
20294                            isFirstKeyDown = true;
20295                        }
20296
20297                        editor.keyboard.keyup(e);
20298                    },
20299                    mousedown: function(e) {
20300                        editor.pendingFormats.clear();
20301
20302                        var target = $(e.target);
20303
20304                        if (!$.browser.gecko && e.which == 2 && target.is("a[href]")) {
20305                            window.open(target.attr("href"), "_new");
20306                        }
20307                    },
20308                    mouseup: function () {
20309                        select(editor);
20310                    }
20311                });
20312
20313            $(editor.window)
20314                .bind("blur", function () {
20315                    var old = editor.textarea.value,
20316                        value = editor.encodedValue();
20317
20318                    editor.update();
20319
20320                    if (value != old) {
20321                        editor.trigger("change");
20322                    }
20323                });
20324
20325            $(editor.body)
20326                .bind("cut paste", function (e) {
20327                      editor.clipboard["on" + e.type](e);
20328                  });
20329        },
20330
20331        fixBackspace: function(editor, e) {
20332
20333            var range = editor.getRange(),
20334                startContainer = range.startContainer,
20335                dom = kendo.ui.editor.Dom,
20336                startContainerChildCount = startContainer.childNodes.length;
20337
20338            if (startContainer == editor.body.firstChild ||
20339                !dom.isBlock(startContainer) ||
20340                (startContainerChildCount > 0 && !(startContainerChildCount == 1 && dom.is(startContainer.firstChild, "br")))) {
20341                return;
20342            }
20343
20344            var previousBlock = startContainer.previousSibling;
20345
20346            while (previousBlock && !dom.isBlock(previousBlock)) {
20347                previousBlock = previousBlock.previousSibling;
20348            }
20349
20350            if (!previousBlock) {
20351                return;
20352            }
20353
20354            var walker = editor.document.createTreeWalker(previousBlock, window.NodeFilter.SHOW_TEXT, null, false);
20355
20356            var textNode;
20357
20358            while (true) {
20359                textNode = walker.nextNode();
20360
20361                if (textNode) {
20362                    previousBlock = textNode;
20363                } else {
20364                    break;
20365                }
20366            }
20367
20368            range.setStart(previousBlock, dom.isDataNode(previousBlock) ? previousBlock.nodeValue.length : 0);
20369            range.collapse(true);
20370            kendo.ui.editor.RangeUtils.selectRange(range);
20371
20372            dom.remove(startContainer);
20373
20374            e.preventDefault();
20375        },
20376
20377        formatByName: function(name, format) {
20378            for (var i = 0; i < format.length; i++) {
20379                if ($.inArray(name, format[i].tags) >= 0) {
20380                    return format[i];
20381                }
20382            }
20383        },
20384
20385        registerTool: function(toolName, tool) {
20386            var tools = Editor.fn._tools;
20387            tools[toolName] = tool;
20388            if (tools[toolName].options && tools[toolName].options.template) {
20389                tools[toolName].options.template.options.cssClass = "k-" + toolName;
20390            }
20391        },
20392
20393        registerFormat: function(formatName, format) {
20394            kendo.ui.Editor.fn.options.formats[formatName] = format;
20395        }
20396    };
20397
20398    var select = EditorUtils.select,
20399        focusable = EditorUtils.focusable,
20400        wrapTextarea = EditorUtils.wrapTextarea,
20401        renderTools = EditorUtils.renderTools,
20402        initializeContentElement = EditorUtils.initializeContentElement,
20403        fixBackspace = EditorUtils.fixBackspace;
20404
20405    var localization = {
20406        bold: "Bold",
20407        italic: "Italic",
20408        underline: "Underline",
20409        strikethrough: "Strikethrough",
20410        superscript: "Superscript",
20411        subscript: "Subscript",
20412        justifyCenter: "Center text",
20413        justifyLeft: "Align text left",
20414        justifyRight: "Align text right",
20415        justifyFull: "Justify",
20416        insertUnorderedList: "Insert unordered list",
20417        insertOrderedList: "Insert ordered list",
20418        indent: "Indent",
20419        outdent: "Outdent",
20420        createLink: "Insert hyperlink",
20421        unlink: "Remove hyperlink",
20422        insertImage: "Insert image",
20423        insertHtml: "Insert HTML",
20424        fontName: "Select font family",
20425        fontNameInherit: "(inherited font)",
20426        fontSize: "Select font size",
20427        fontSizeInherit: "(inherited size)",
20428        formatBlock: "Format",
20429        style: "Styles",
20430        emptyFolder: "Empty Folder",
20431        uploadFile: "Upload",
20432        orderBy: "Arrange by:",
20433        orderBySize: "Size",
20434        orderByName: "Name",
20435        invalidFileType: "The selected file \"{0}\" is not valid. Supported file types are {1}.",
20436        deleteFile: 'Are you sure you want to delete "{0}"?',
20437        overwriteFile: 'A file with name "{0}" already exists in the current directory. Do you want to overwrite it?',
20438        directoryNotFound: "A directory with this name was not found."
20439    };
20440
20441    var supportedBrowser = !kendo.support.mobileOS || (kendo.support.mobileOS.ios && kendo.support.mobileOS.majorVersion >= 5);
20442
20443    var Editor = Widget.extend({
20444        init: function (element, options) {
20445            /* suppress initialization in mobile webkit devices (w/o proper contenteditable support) */
20446            if (!supportedBrowser) {
20447                return;
20448            }
20449
20450            var that = this,
20451                wrapper,
20452                value,
20453                editorNS = kendo.ui.editor;
20454
20455            Widget.fn.init.call(that, element, options);
20456
20457            that.options = deepExtend({}, that.options, options);
20458
20459            element = $(element);
20460
20461            element.closest("form").bind("submit", function () {
20462                that.update();
20463            });
20464
20465            for (var id in that._tools) {
20466                that._tools[id].name = id.toLowerCase();
20467            }
20468
20469            that.textarea = element.attr("autocomplete", "off")[0];
20470
20471            wrapper = that.wrapper = wrapTextarea(element);
20472
20473            renderTools(that, that.options.tools);
20474
20475            initializeContentElement(that);
20476
20477            that.keyboard = new editorNS.Keyboard([
20478                new editorNS.TypingHandler(that),
20479                new editorNS.SystemHandler(that)
20480            ]);
20481
20482            that.clipboard = new editorNS.Clipboard(this);
20483
20484            that.pendingFormats = new editorNS.PendingFormats(this);
20485
20486            that.undoRedoStack = new editorNS.UndoRedoStack();
20487
20488            if (options && options.value) {
20489                value = options.value;
20490            } else {
20491                // indented HTML introduces problematic ranges in IE
20492                value = element.val().replace(/[\r\n\v\f\t ]+/ig, " ");
20493            }
20494
20495            that.value(value);
20496
20497            function toolFromClassName(element) {
20498                var tool = $.grep(element.className.split(" "), function (x) {
20499                    return !/^k-(widget|tool-icon|state-hover|header|combobox|dropdown|selectbox|colorpicker)$/i.test(x);
20500                });
20501                return tool[0] ? tool[0].substring(2) : "custom";
20502            }
20503
20504            function appendShortcutSequence(localizedText, tool) {
20505                if (!tool.key) {
20506                    return localizedText;
20507                }
20508
20509                var res = localizedText + " (";
20510
20511                if (tool.ctrl) {
20512                    res += "Ctrl + ";
20513                }
20514
20515                if (tool.shift) {
20516                    res += "Shift + ";
20517                }
20518
20519                if (tool.alt) {
20520                    res += "Alt + ";
20521                }
20522
20523                res += tool.key + ")";
20524
20525                return res;
20526            }
20527
20528            var toolbarItems = ".k-editor-toolbar > li > *, .k-editor-toolbar > li select",
20529                buttons = ".k-editor-button .k-tool-icon",
20530                enabledButtons = buttons + ":not(.k-state-disabled)",
20531                disabledButtons = buttons + ".k-state-disabled";
20532
20533             wrapper.find(".k-combobox .k-input").keydown(function(e) {
20534                var combobox = $(this).closest(".k-combobox").data("kendoComboBox"),
20535                    key = e.keyCode;
20536
20537                if (key == keys.RIGHT || key == keys.LEFT) {
20538                    combobox.close();
20539                } else if (key == keys.DOWN) {
20540                    if (!combobox.dropDown.isOpened()) {
20541                        e.stopImmediatePropagation();
20542                        combobox.open();
20543                    }
20544                }
20545            });
20546
20547            wrapper
20548                .delegate(enabledButtons, "mouseenter", function() { $(this).addClass("k-state-hover"); })
20549                .delegate(enabledButtons, "mouseleave", function() { $(this).removeClass("k-state-hover"); })
20550                .delegate(buttons, "mousedown", false)
20551                .delegate(focusable, "keydown", function(e) {
20552                    var closestLi = $(this).closest("li"),
20553                    focusableTool = "li:has(" + focusable + ")",
20554                    focusElement,
20555                    keyCode = e.keyCode;
20556
20557                    if (keyCode == keys.RIGHT) {
20558                        focusElement = closestLi.nextAll(focusableTool).first().find(focusable);
20559                    } else if (keyCode == keys.LEFT) {
20560                        focusElement = closestLi.prevAll(focusableTool).last().find(focusable);
20561                    } else if (keyCode == keys.ESC) {
20562                        focusElement = that;
20563                    } else if (keyCode == keys.TAB && !(e.ctrlKey || e.altKey)) {
20564                        // skip tabbing to disabled tools, and focus the editing area when running out of tools
20565                        if (e.shiftKey) {
20566                            focusElement = closestLi.prevAll(focusableTool).last().find(focusable);
20567
20568                            if (focusElement.length) {
20569                                e.preventDefault();
20570                            } else {
20571                                return;
20572                            }
20573                        } else {
20574                            e.preventDefault();
20575
20576                            focusElement = closestLi.nextAll(focusableTool).first().find(focusable);
20577
20578                            if (!focusElement.length) {
20579                                focusElement = that;
20580                            }
20581                        }
20582                    }
20583
20584                    if (focusElement) {
20585                        focusElement.focus();
20586                    }
20587                })
20588                .delegate(enabledButtons, "click", function (e) {
20589                    e.preventDefault();
20590                    e.stopPropagation();
20591                    that.exec(toolFromClassName(this));
20592                })
20593                .delegate(disabledButtons, "click", function(e) { e.preventDefault(); })
20594                .find(toolbarItems)
20595                    .each(function () {
20596                        var toolName = toolFromClassName(this),
20597                            options = that.options,
20598                            tool = options.tools[toolName],
20599                            description = options.localization[toolName],
20600                            $this = $(this);
20601
20602                        if (!tool) {
20603                            return;
20604                        }
20605
20606                        if (toolName == "fontSize" || toolName == "fontName") {
20607                            var inheritText = options.localization[toolName + "Inherit"] || localization[toolName + "Inherit"];
20608                            options[toolName][0].Text = inheritText;
20609                            $this.find("input").val(inheritText).end()
20610                                 .find("span.k-input").text(inheritText).end();
20611                        }
20612
20613                        tool.initialize($this, {
20614                            title: appendShortcutSequence(description, tool),
20615                            editor: that
20616                        });
20617
20618                    });
20619
20620                that.bind("select", function() {
20621                    var range = that.getRange();
20622
20623                    var nodes = editorNS.RangeUtils.textNodes(range);
20624
20625                    if (!nodes.length) {
20626                        nodes = [range.startContainer];
20627                    }
20628
20629                    wrapper.find(toolbarItems)
20630                        .each(function () {
20631                            var tool = that.options.tools[toolFromClassName(this)];
20632                            if (tool) {
20633                                tool.update($(this), nodes, that.pendingFormats);
20634                            }
20635                        });
20636                });
20637
20638            $(document)
20639                .bind("DOMNodeInserted", function(e) {
20640                    var wrapper = that.wrapper;
20641
20642                    if ($.contains(e.target, wrapper[0]) || wrapper[0] == e.target) {
20643                        // preserve updated value before re-initializing
20644                        // don't use update() to prevent the editor from encoding the content too early
20645                        that.textarea.value = that.value();
20646                        wrapper.find("iframe").remove();
20647                        initializeContentElement(that);
20648                    }
20649                })
20650                .bind("mousedown", function(e) {
20651                    try {
20652                        if (that.keyboard.isTypingInProgress()) {
20653                            that.keyboard.endTyping(true);
20654                        }
20655
20656                        if (!that.selectionRestorePoint) {
20657                            that.selectionRestorePoint = new editorNS.RestorePoint(that.getRange());
20658                        }
20659                    } catch (e) { }
20660                });
20661
20662            kendo.notify(that);
20663        },
20664
20665        events: [
20666            "select",
20667            "change",
20668            "execute",
20669            "error",
20670            "paste"
20671        ],
20672
20673        options: {
20674            name: "Editor",
20675            localization: localization,
20676            formats: {},
20677            encoded: true,
20678            stylesheets: [],
20679            dialogOptions: {
20680                modal: true, resizable: false, draggable: true,
20681                animation: false
20682            },
20683            fontName: [
20684                { Text: localization.fontNameInherit,  Value: "inherit" },
20685                { Text: "Arial", Value: "Arial,Helvetica,sans-serif" },
20686                { Text: "Courier New", Value: "'Courier New',Courier,monospace" },
20687                { Text: "Georgia", Value: "Georgia,serif" },
20688                { Text: "Impact", Value: "Impact,Charcoal,sans-serif" },
20689                { Text: "Lucida Console", Value: "'Lucida Console',Monaco,monospace" },
20690                { Text: "Tahoma", Value: "Tahoma,Geneva,sans-serif" },
20691                { Text: "Times New Roman", Value: "'Times New Roman',Times,serif" },
20692                { Text: "Trebuchet MS", Value: "'Trebuchet MS',Helvetica,sans-serif" },
20693                { Text: "Verdana", Value: "Verdana,Geneva,sans-serif" }
20694            ],
20695            fontSize: [
20696                { Text: localization.fontSizeInherit,  Value: "inherit" },
20697                { Text: "1 (8pt)",  Value: "xx-small" },
20698                { Text: "2 (10pt)", Value: "x-small" },
20699                { Text: "3 (12pt)", Value: "small" },
20700                { Text: "4 (14pt)", Value: "medium" },
20701                { Text: "5 (18pt)", Value: "large" },
20702                { Text: "6 (24pt)", Value: "x-large" },
20703                { Text: "7 (36pt)", Value: "xx-large" }
20704            ],
20705            formatBlock: [
20706                { Text: "Paragraph", Value: "p" },
20707                { Text: "Quotation", Value: "blockquote" },
20708                { Text: "Heading 1", Value: "h1" },
20709                { Text: "Heading 2", Value: "h2" },
20710                { Text: "Heading 3", Value: "h3" },
20711                { Text: "Heading 4", Value: "h4" },
20712                { Text: "Heading 5", Value: "h5" },
20713                { Text: "Heading 6", Value: "h6" }
20714            ],
20715            tools: [
20716                "bold",
20717                "italic",
20718                "underline",
20719                "strikethrough",
20720                "fontName",
20721                "fontSize",
20722                "foreColor",
20723                "backColor",
20724                "justifyLeft",
20725                "justifyCenter",
20726                "justifyRight",
20727                "justifyFull",
20728                "insertUnorderedList",
20729                "insertOrderedList",
20730                "indent",
20731                "outdent",
20732                "formatBlock",
20733                "createLink",
20734                "unlink",
20735                "insertImage"
20736                //"style",
20737                //"subscript",
20738                //"superscript",
20739            ]
20740        },
20741
20742        _nativeTools: [
20743            "insertLineBreak",
20744            "insertParagraph",
20745            "redo",
20746            "undo"
20747        ],
20748
20749        _tools: {
20750            undo: { options: { key: "Z", ctrl: true } },
20751            redo: { options: { key: "Y", ctrl: true } }
20752        },
20753
20754        value: function (html) {
20755            var body = this.body,
20756                dom = kendo.ui.editor.Dom;
20757
20758            if (html === undefined) {
20759                return kendo.ui.editor.Serializer.domToXhtml(body);
20760            }
20761
20762            this.pendingFormats.clear();
20763
20764            // handle null value passed as a parameter
20765            html = (html || "")
20766                // Some browsers do not allow setting CDATA sections through innerHTML so we encode them
20767                .replace(/<!\[CDATA\[(.*)?\]\]>/g, "<!--[CDATA[$1]]-->")
20768                // Encode script tags to avoid execution and lost content (IE)
20769                .replace(/<script([^>]*)>(.*)?<\/script>/ig, "<telerik:script $1>$2<\/telerik:script>")
20770                // <img>\s+\w+ creates invalid nodes after cut in IE
20771                .replace(/(<\/?img[^>]*>)[\r\n\v\f\t ]+/ig, "$1");
20772
20773            if (!$.browser.msie) {
20774                // Add <br/>s to empty paragraphs in mozilla/chrome, to make them focusable
20775                html = html.replace(/<p([^>]*)>(\s*)?<\/p>/ig, '<p $1><br _moz_dirty="" /><\/p>');
20776            }
20777
20778            if ($.browser.msie && parseInt($.browser.version, 10) < 9) {
20779                // Internet Explorer removes comments from the beginning of the html
20780                html = "<br/>" + html;
20781
20782                var originalSrc = "originalsrc",
20783                    originalHref = "originalhref";
20784
20785                // IE < 8 makes href and src attributes absolute
20786                html = html.replace(/href\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalHref + '="$1"');
20787                html = html.replace(/src\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalSrc + '="$1"');
20788
20789                body.innerHTML = html;
20790                dom.remove(body.firstChild);
20791
20792                $(body).find("telerik\\:script,script,link,img,a").each(function () {
20793                    var node = this;
20794                    if (node[originalHref]) {
20795                        node.setAttribute("href", node[originalHref]);
20796                        node.removeAttribute(originalHref);
20797                    }
20798                    if (node[originalSrc]) {
20799                        node.setAttribute("src", node[originalSrc]);
20800                        node.removeAttribute(originalSrc);
20801                    }
20802                });
20803            } else {
20804                body.innerHTML = html;
20805                if ($.browser.msie) {
20806                    // having unicode characters creates denormalized DOM tree in IE9
20807                    dom.normalize(body);
20808                }
20809            }
20810
20811            this.selectionRestorePoint = null;
20812            this.update();
20813        },
20814
20815        focus: function () {
20816            this.window.focus();
20817        },
20818
20819        update: function (value) {
20820            this.textarea.value = value || this.options.encoded ? this.encodedValue() : this.value();
20821        },
20822
20823        encodedValue: function () {
20824            return kendo.ui.editor.Dom.encode(this.value());
20825        },
20826
20827        createRange: function (document) {
20828            return kendo.ui.editor.RangeUtils.createRange(document || this.document);
20829        },
20830
20831        getSelection: function () {
20832            return kendo.ui.editor.SelectionUtils.selectionFromDocument(this.document);
20833        },
20834
20835        selectRange: function(range) {
20836            this.focus();
20837            var selection = this.getSelection();
20838            selection.removeAllRanges();
20839            selection.addRange(range);
20840        },
20841
20842        getRange: function () {
20843            var selection = this.getSelection(),
20844                range = selection.rangeCount > 0 ? selection.getRangeAt(0) : this.createRange(),
20845                doc = this.document;
20846
20847            if (range.startContainer == doc && range.endContainer == doc && !range.startOffset && !range.endOffset) {
20848                range.setStart(this.body, 0);
20849                range.collapse(true);
20850            }
20851
20852            return range;
20853        },
20854
20855        selectedHtml: function() {
20856            return kendo.ui.editor.Serializer.domToXhtml(this.getRange().cloneContents());
20857        },
20858
20859        paste: function (html) {
20860            this.clipboard.paste(html);
20861        },
20862
20863        exec: function (name, params) {
20864            var that = this,
20865                range, body, id,
20866                tool = "", pendingTool;
20867
20868            name = name.toLowerCase();
20869
20870            // restore selection
20871            if (!that.keyboard.isTypingInProgress()) {
20872                that.focus();
20873
20874                range = that.getRange();
20875                body = that.document.body;
20876            }
20877
20878            // exec tool
20879            for (id in that.options.tools) {
20880                if (id.toLowerCase() == name) {
20881                    tool = that.options.tools[id];
20882                    break;
20883                }
20884            }
20885
20886            if (tool) {
20887                range = that.getRange();
20888
20889                if (!/undo|redo/i.test(name) && tool.willDelayExecution(range)) {
20890                    // clone our tool to apply params only once
20891                    pendingTool = $.extend({}, tool);
20892                    $.extend(pendingTool.options, { params: params });
20893                    that.pendingFormats.toggle(pendingTool);
20894                    select(that);
20895                    return;
20896                }
20897
20898                var command = tool.command ? tool.command(extend({ range: range }, params)) : null;
20899
20900                that.trigger("execute", { name: name, command: command });
20901
20902                if (/undo|redo/i.test(name)) {
20903                    that.undoRedoStack[name]();
20904                } else if (command) {
20905                    if (!command.managesUndoRedo) {
20906                        that.undoRedoStack.push(command);
20907                    }
20908
20909                    command.editor = that;
20910                    command.exec();
20911
20912                    if (command.async) {
20913                        command.change = $.proxy(function () { select(that); }, that);
20914                        return;
20915                    }
20916                }
20917
20918                select(that);
20919            }
20920        }
20921    });
20922
20923    kendo.ui.plugin(Editor);
20924
20925    var Tool = Class.extend({
20926        init: function(options) {
20927            this.options = options;
20928        },
20929
20930        initialize: function($ui, options) {
20931            $ui.attr({ unselectable: "on", title: options.title });
20932        },
20933
20934        command: function (commandArguments) {
20935            return new this.options.command(commandArguments);
20936        },
20937
20938        update: function() {
20939        },
20940
20941        willDelayExecution: function() {
20942            return false;
20943        }
20944
20945    });
20946
20947    Tool.exec = function (editor, name, value) {
20948        editor.exec(name, { value: value });
20949    };
20950
20951    var FormatTool = Tool.extend({
20952        init: function (options) {
20953            Tool.fn.init.call(this, options);
20954        },
20955
20956        command: function (commandArguments) {
20957            var that = this;
20958            return new kendo.ui.editor.FormatCommand(extend(commandArguments, {
20959                    formatter: that.options.formatter
20960                }));
20961        },
20962
20963        update: function($ui, nodes, pendingFormats) {
20964            var isPending = pendingFormats.isPending(this.name),
20965                isFormatted = this.options.finder.isFormatted(nodes),
20966                isActive = isPending ? !isFormatted : isFormatted;
20967
20968            $ui.toggleClass("k-state-active", isActive);
20969        }
20970    });
20971
20972    // Exports ================================================================
20973
20974    extend(kendo.ui, {
20975        editor:{
20976            ToolTemplate: ToolTemplate,
20977            EditorUtils: EditorUtils,
20978            Tool: Tool,
20979            FormatTool: FormatTool
20980        }
20981    });
20982
20983})(jQuery);
20984(function($) {
20985
20986var kendo = window.kendo,
20987    map = $.map,
20988    extend = $.extend,
20989    STYLE = "style",
20990    FLOAT = "float",
20991    CSSFLOAT = "cssFloat",
20992    STYLEFLOAT = "styleFloat",
20993    CLASS = "class",
20994    KMARKER = "k-marker";
20995
20996function makeMap(items) {
20997    var obj = {},
20998        i, len;
20999
21000    for (i = 0, len = items.length; i < len; i++) {
21001        obj[items[i]] = true;
21002    }
21003    return obj;
21004}
21005
21006var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed".split(",")),
21007    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(","),
21008    block = makeMap(blockElements),
21009    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(","),
21010    inline = makeMap(inlineElements),
21011    fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected".split(","));
21012
21013var normalize = function (node) {
21014    if (node.nodeType == 1) {
21015        node.normalize();
21016    }
21017};
21018
21019if ($.browser.msie && parseInt($.browser.version, 10) >= 8) {
21020    normalize = function(parent) {
21021        if (parent.nodeType == 1 && parent.firstChild) {
21022            var prev = parent.firstChild,
21023                node = prev;
21024
21025            while (true) {
21026                node = node.nextSibling;
21027
21028                if (!node) {
21029                    break;
21030                }
21031
21032                if (node.nodeType == 3 && prev.nodeType == 3) {
21033                    node.nodeValue = prev.nodeValue + node.nodeValue;
21034                    Dom.remove(prev);
21035                }
21036
21037                prev = node;
21038            }
21039        }
21040    };
21041}
21042
21043var whitespace = /^\s+$/,
21044    rgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i,
21045    amp = /&/g,
21046    openTag = /</g,
21047    closeTag = />/g,
21048    nbsp = /\u00a0/g,
21049    bom = /\ufeff/g;
21050    var cssAttributes =
21051           ("color,padding-left,padding-right,padding-top,padding-bottom," +
21052            "background-color,background-attachment,background-image,background-position,background-repeat," +
21053            "border-top-style,border-top-width,border-top-color," +
21054            "border-bottom-style,border-bottom-width,border-bottom-color," +
21055            "border-left-style,border-left-width,border-left-color," +
21056            "border-right-style,border-right-width,border-right-color," +
21057            "font-family,font-size,font-style,font-variant,font-weight,line-height"
21058           ).split(",");
21059
21060var Dom = {
21061    findNodeIndex: function(node) {
21062        var i = 0;
21063
21064        while (true) {
21065            node = node.previousSibling;
21066
21067            if (!node) {
21068                break;
21069            }
21070
21071            i++;
21072        }
21073
21074        return i;
21075    },
21076
21077    isDataNode: function(node) {
21078        return node && node.nodeValue !== null && node.data !== null;
21079    },
21080
21081    isAncestorOf: function(parent, node) {
21082        try {
21083            return !Dom.isDataNode(parent) && ($.contains(parent, Dom.isDataNode(node) ? node.parentNode : node) || node.parentNode == parent);
21084        } catch (e) {
21085            return false;
21086        }
21087    },
21088
21089    isAncestorOrSelf: function(root, node) {
21090        return Dom.isAncestorOf(root, node) || root == node;
21091    },
21092
21093    findClosestAncestor: function(root, node) {
21094        if (Dom.isAncestorOf(root, node)) {
21095            while (node && node.parentNode != root) {
21096                node = node.parentNode;
21097            }
21098        }
21099
21100        return node;
21101    },
21102
21103    getNodeLength: function(node) {
21104        return Dom.isDataNode(node) ? node.length : node.childNodes.length;
21105    },
21106
21107    splitDataNode: function(node, offset) {
21108        var newNode = node.cloneNode(false);
21109        node.deleteData(offset, node.length);
21110        newNode.deleteData(0, offset);
21111        Dom.insertAfter(newNode, node);
21112    },
21113
21114    attrEquals: function(node, attributes) {
21115        for (var key in attributes) {
21116            var value = node[key];
21117
21118            if (key == FLOAT) {
21119                value = node[$.support.cssFloat ? CSSFLOAT : STYLEFLOAT];
21120            }
21121
21122            if (typeof value == "object") {
21123                if (!Dom.attrEquals(value, attributes[key])) {
21124                    return false;
21125                }
21126            } else if (value != attributes[key]) {
21127                return false;
21128            }
21129        }
21130
21131        return true;
21132    },
21133
21134    blockParentOrBody: function(node) {
21135        return Dom.parentOfType(node, blockElements) || node.ownerDocument.body;
21136    },
21137
21138    blockParents: function(nodes) {
21139        var blocks = [],
21140            i, len;
21141
21142        for (i = 0, len = nodes.length; i < len; i++) {
21143            var block = Dom.parentOfType(nodes[i], Dom.blockElements);
21144            if (block && $.inArray(block, blocks) < 0) {
21145                blocks.push(block);
21146            }
21147        }
21148
21149        return blocks;
21150    },
21151
21152    windowFromDocument: function(document) {
21153        return document.defaultView || document.parentWindow;
21154    },
21155
21156    normalize: normalize,
21157    blockElements: blockElements,
21158    inlineElements: inlineElements,
21159    empty: empty,
21160    fillAttrs: fillAttrs,
21161
21162    toHex: function (color) {
21163        var matches = rgb.exec(color);
21164
21165        if (!matches) {
21166            return color;
21167        }
21168
21169        return "#" + map(matches.slice(1), function (x) {
21170            x = parseInt(x, 10).toString(16);
21171            return x.length > 1 ? x : "0" + x;
21172        }).join("");
21173    },
21174
21175    encode: function (value) {
21176        return value.replace(amp, "&amp;")
21177                .replace(openTag, "&lt;")
21178                .replace(closeTag, "&gt;")
21179                .replace(nbsp, "&nbsp;");
21180    },
21181
21182    name: function (node) {
21183        return node.nodeName.toLowerCase();
21184    },
21185
21186    significantChildNodes: function(node) {
21187        return $.grep(node.childNodes, function(child) {
21188            return child.nodeType != 3 || !Dom.isWhitespace(child);
21189        });
21190    },
21191
21192    lastTextNode: function(node) {
21193        var result = null;
21194
21195        if (node.nodeType == 3) {
21196            return node;
21197        }
21198
21199        for (var child = node.lastChild; child; child = child.previousSibling) {
21200            result = Dom.lastTextNode(child);
21201
21202            if (result) {
21203                return result;
21204            }
21205        }
21206
21207        return result;
21208    },
21209
21210    is: function (node, nodeName) {
21211        return Dom.name(node) == nodeName;
21212    },
21213
21214    isMarker: function(node) {
21215        return node.className == KMARKER;
21216    },
21217
21218    isWhitespace: function(node) {
21219        return whitespace.test(node.nodeValue);
21220    },
21221
21222    isBlock: function(node) {
21223        return block[Dom.name(node)];
21224    },
21225
21226    isEmpty: function(node) {
21227        return empty[Dom.name(node)];
21228    },
21229
21230    isInline: function(node) {
21231        return inline[Dom.name(node)];
21232    },
21233
21234    scrollTo: function (node) {
21235        node.ownerDocument.body.scrollTop = $(Dom.isDataNode(node) ? node.parentNode : node).offset().top;
21236    },
21237
21238    insertAt: function (parent, newElement, position) {
21239        parent.insertBefore(newElement, parent.childNodes[position] || null);
21240    },
21241
21242    insertBefore: function (newElement, referenceElement) {
21243        if (referenceElement.parentNode) {
21244            return referenceElement.parentNode.insertBefore(newElement, referenceElement);
21245        } else {
21246            return referenceElement;
21247        }
21248    },
21249
21250    insertAfter: function (newElement, referenceElement) {
21251        return referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
21252    },
21253
21254    remove: function (node) {
21255        node.parentNode.removeChild(node);
21256    },
21257
21258    trim: function (parent) {
21259        for (var i = parent.childNodes.length - 1; i >= 0; i--) {
21260            var node = parent.childNodes[i];
21261            if (Dom.isDataNode(node)) {
21262                if (!node.nodeValue.replace(bom, "").length) {
21263                    Dom.remove(node);
21264                }
21265
21266                if (Dom.isWhitespace(node)) {
21267                    Dom.insertBefore(node, parent);
21268                }
21269            } else if (node.className != KMARKER) {
21270                Dom.trim(node);
21271                if (!node.childNodes.length && !Dom.isEmpty(node)) {
21272                    Dom.remove(node);
21273                }
21274            }
21275        }
21276
21277        return parent;
21278    },
21279
21280    parentOfType: function (node, tags) {
21281        do {
21282            node = node.parentNode;
21283        } while (node && !(Dom.ofType(node, tags)));
21284
21285        return node;
21286    },
21287
21288    ofType: function (node, tags) {
21289        return $.inArray(Dom.name(node), tags) >= 0;
21290    },
21291
21292    changeTag: function (referenceElement, tagName) {
21293        var newElement = Dom.create(referenceElement.ownerDocument, tagName),
21294            attributes = referenceElement.attributes,
21295            i, len, name, value, attribute;
21296
21297        for (i = 0, len = attributes.length; i < len; i++) {
21298            attribute = attributes[i];
21299            if (attribute.specified) {
21300                // IE < 8 cannot set class or style via setAttribute
21301                name = attribute.nodeName;
21302                value = attribute.nodeValue;
21303                if (name == CLASS) {
21304                    newElement.className = value;
21305                } else if (name == STYLE) {
21306                    newElement.style.cssText = referenceElement.style.cssText;
21307                } else {
21308                    newElement.setAttribute(name, value);
21309                }
21310            }
21311        }
21312
21313        while (referenceElement.firstChild) {
21314            newElement.appendChild(referenceElement.firstChild);
21315        }
21316
21317        Dom.insertBefore(newElement, referenceElement);
21318        Dom.remove(referenceElement);
21319        return newElement;
21320    },
21321
21322    wrap: function (node, wrapper) {
21323        Dom.insertBefore(wrapper, node);
21324        wrapper.appendChild(node);
21325        return wrapper;
21326    },
21327
21328    unwrap: function (node) {
21329        var parent = node.parentNode;
21330        while (node.firstChild) {
21331            parent.insertBefore(node.firstChild, node);
21332        }
21333
21334        parent.removeChild(node);
21335    },
21336
21337    create: function (document, tagName, attributes) {
21338        return Dom.attr(document.createElement(tagName), attributes);
21339    },
21340
21341    attr: function (element, attributes) {
21342        attributes = extend({}, attributes);
21343
21344        if (attributes && STYLE in attributes) {
21345            Dom.style(element, attributes.style);
21346            delete attributes.style;
21347        }
21348        return extend(element, attributes);
21349    },
21350
21351    style: function (node, value) {
21352        $(node).css(value || {});
21353    },
21354
21355    unstyle: function (node, value) {
21356        for (var key in value) {
21357            if (key == FLOAT) {
21358                key = $.support.cssFloat ? CSSFLOAT : STYLEFLOAT;
21359            }
21360
21361            node.style[key] = "";
21362        }
21363
21364        if (node.style.cssText === "") {
21365            node.removeAttribute(STYLE);
21366        }
21367    },
21368
21369    inlineStyle: function(document, name, attributes) {
21370        var span = $(Dom.create(document, name, attributes)),
21371            style;
21372
21373        document.body.appendChild(span[0]);
21374
21375        style = map(cssAttributes, function(value) {
21376            if ($.browser.msie && value == "line-height" && span.css(value) == "1px") {
21377                return "line-height:1.5";
21378            } else {
21379                return value + ":" + span.css(value);
21380            }
21381        }).join(";");
21382
21383        span.remove();
21384
21385        return style;
21386    },
21387
21388    removeClass: function(node, classNames) {
21389        var className = " " + node.className + " ",
21390            classes = classNames.split(" "),
21391            i, len;
21392
21393        for (i = 0, len = classes.length; i < len; i++) {
21394            className = className.replace(" " + classes[i] + " ", " ");
21395        }
21396
21397        className = $.trim(className);
21398
21399        if (className.length) {
21400            node.className = className;
21401        } else {
21402            node.removeAttribute(CLASS);
21403        }
21404    },
21405
21406    commonAncestor: function () {
21407        var count = arguments.length,
21408            paths = [],
21409            minPathLength = Infinity,
21410            output = null,
21411            i, ancestors, node, first, j;
21412
21413        if (!count) {
21414            return null;
21415        }
21416
21417        if (count == 1) {
21418            return arguments[0];
21419        }
21420
21421        for (i = 0; i < count; i++) {
21422            ancestors = [];
21423            node = arguments[i];
21424            while (node) {
21425                ancestors.push(node);
21426                node = node.parentNode;
21427            }
21428            paths.push(ancestors.reverse());
21429            minPathLength = Math.min(minPathLength, ancestors.length);
21430        }
21431
21432        if (count == 1) {
21433            return paths[0][0];
21434        }
21435
21436        for (i = 0; i < minPathLength; i++) {
21437            first = paths[0][i];
21438
21439            for (j = 1; j < count; j++) {
21440                if (first != paths[j][i]) {
21441                    return output;
21442                }
21443            }
21444
21445            output = first;
21446        }
21447        return output;
21448    }
21449};
21450
21451kendo.ui.editor.Dom = Dom;
21452
21453})(jQuery);
21454(function($, undefined) {
21455
21456// Imports ================================================================
21457var kendo = window.kendo,
21458    Editor = kendo.ui.editor,
21459    dom = Editor.Dom,
21460    extend = $.extend;
21461
21462var fontSizeMappings = 'xx-small,x-small,small,medium,large,x-large,xx-large'.split(','),
21463    quoteRe = /"/g,
21464    brRe = /<br[^>]*>/i,
21465    emptyPRe = /<p><\/p>/i;
21466
21467var Serializer = {
21468    domToXhtml: function(root) {
21469        var result = [];
21470        var tagMap = {
21471            'telerik:script': { start: function (node) { result.push('<script'); attr(node); result.push('>'); }, end: function () { result.push('</script>'); } },
21472            b: { start: function () { result.push('<strong>'); }, end: function () { result.push('</strong>'); } },
21473            i: { start: function () { result.push('<em>'); }, end: function () { result.push('</em>'); } },
21474            u: { start: function () { result.push('<span style="text-decoration:underline;">'); }, end: function () { result.push('</span>'); } },
21475            font: {
21476                start: function (node) {
21477                    result.push('<span style="');
21478
21479                    var color = node.getAttribute('color');
21480                    var size = fontSizeMappings[node.getAttribute('size')];
21481                    var face = node.getAttribute('face');
21482
21483                    if (color) {
21484                        result.push('color:');
21485                        result.push(dom.toHex(color));
21486                        result.push(';');
21487                    }
21488
21489                    if (face) {
21490                        result.push('font-face:');
21491                        result.push(face);
21492                        result.push(';');
21493                    }
21494
21495                    if (size) {
21496                        result.push('font-size:');
21497                        result.push(size);
21498                        result.push(';');
21499                    }
21500
21501                    result.push('">');
21502                },
21503                end: function (node) {
21504                    result.push('</span>');
21505                }
21506            }
21507        };
21508
21509        function attr(node) {
21510            var specifiedAttributes = [],
21511                attributes = node.attributes,
21512                attribute, i, l,
21513                trim = $.trim;
21514
21515            if (dom.is(node, 'img')) {
21516                var width = node.style.width,
21517                    height = node.style.height,
21518                    $node = $(node);
21519
21520                if (width) {
21521                    $node.attr('width', parseInt(width, 10));
21522                    dom.unstyle(node, { width: undefined });
21523                }
21524
21525                if (height) {
21526                    $node.attr('height', parseInt(height, 10));
21527                    dom.unstyle(node, { height: undefined });
21528                }
21529            }
21530
21531            for (i = 0, l = attributes.length; i < l; i++) {
21532                attribute = attributes[i];
21533                var name = attribute.nodeName;
21534                // In IE < 8 the 'value' attribute is not returned as 'specified'. The same goes for type="text"
21535                if (attribute.specified || (name == 'value' && !node.value) || (name == 'type' && attribute.nodeValue == 'text')) {
21536                    if (name.indexOf('_moz') < 0 && name != 'complete') {
21537                        specifiedAttributes.push(attribute);
21538                    }
21539                }
21540            }
21541
21542            if (!specifiedAttributes.length) {
21543                return;
21544            }
21545
21546            specifiedAttributes.sort(function (a, b) {
21547                return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0;
21548            });
21549
21550            for (i = 0, l = specifiedAttributes.length; i < l; i++) {
21551                attribute = specifiedAttributes[i];
21552                var attributeName = attribute.nodeName;
21553                var attributeValue = attribute.nodeValue;
21554
21555                result.push(' ');
21556                result.push(attributeName);
21557                result.push('="');
21558                if (attributeName == 'style') {
21559                    // In IE < 8 the style attribute does not return proper nodeValue
21560                    var css = trim(attributeValue || node.style.cssText).split(';');
21561
21562                    for (var cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
21563                        var pair = css[cssIndex];
21564                        if (pair.length) {
21565                            var propertyAndValue = pair.split(':');
21566                            var property = trim(propertyAndValue[0].toLowerCase()),
21567                                value = trim(propertyAndValue[1]);
21568
21569                            if (property == "font-size-adjust" || property == "font-stretch") {
21570                                continue;
21571                            }
21572
21573                            if (property.indexOf('color') >= 0) {
21574                                value = dom.toHex(value);
21575                            }
21576
21577                            if (property.indexOf('font') >= 0) {
21578                                value = value.replace(quoteRe, "'");
21579                            }
21580
21581                            result.push(property);
21582                            result.push(':');
21583                            result.push(value);
21584                            result.push(';');
21585                        }
21586                    }
21587                } else if (attributeName == 'src' || attributeName == 'href') {
21588                    result.push(node.getAttribute(attributeName, 2));
21589                } else {
21590                    result.push(dom.fillAttrs[attributeName] ? attributeName : attributeValue);
21591                }
21592
21593                result.push('"');
21594            }
21595        }
21596
21597        function children(node, skip) {
21598            for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
21599                child(childNode, skip);
21600            }
21601        }
21602
21603        function child(node, skip) {
21604            var nodeType = node.nodeType,
21605                tagName, mapper,
21606                parent, value, previous;
21607
21608            if (nodeType == 1) {
21609                tagName = dom.name(node);
21610
21611                if (!tagName || (node.attributes._moz_dirty && dom.is(node, 'br'))) {
21612                    return;
21613                }
21614
21615                mapper = tagMap[tagName];
21616
21617                if (mapper) {
21618                    mapper.start(node);
21619                    children(node);
21620                    mapper.end(node);
21621                    return;
21622                }
21623
21624                result.push('<');
21625                result.push(tagName);
21626
21627                attr(node);
21628
21629                if (dom.empty[tagName]) {
21630                    result.push(' />');
21631                } else {
21632                    result.push('>');
21633                    children(node, skip || dom.is(node, 'pre'));
21634                    result.push('</');
21635                    result.push(tagName);
21636                    result.push('>');
21637                }
21638            } else if (nodeType == 3) {
21639                value = node.nodeValue;
21640
21641                if (!skip && $.support.leadingWhitespace) {
21642                    parent = node.parentNode;
21643                    previous = node.previousSibling;
21644
21645                    if (!previous) {
21646                         previous = (dom.isInline(parent) ? parent : node).previousSibling;
21647                    }
21648
21649                    if (!previous || previous.innerHTML === "" || dom.isBlock(previous)) {
21650                        value = value.replace(/^[\r\n\v\f\t ]+/, '');
21651                    }
21652
21653                    value = value.replace(/ +/, ' ');
21654                }
21655
21656                result.push(dom.encode(value));
21657
21658            } else if (nodeType == 4) {
21659                result.push('<![CDATA[');
21660                result.push(node.data);
21661                result.push(']]>');
21662            } else if (nodeType == 8) {
21663                if (node.data.indexOf('[CDATA[') < 0) {
21664                    result.push('<!--');
21665                    result.push(node.data);
21666                    result.push('-->');
21667                } else {
21668                    result.push('<!');
21669                    result.push(node.data);
21670                    result.push('>');
21671                }
21672            }
21673        }
21674
21675        children(root);
21676
21677        result = result.join('');
21678
21679        // if serialized dom contains only whitespace elements, consider it empty (required field validation)
21680        if (result.replace(brRe, "").replace(emptyPRe, "") === "") {
21681            return "";
21682        }
21683
21684        return result;
21685    }
21686
21687};
21688
21689extend(Editor, {
21690    Serializer: Serializer
21691});
21692
21693})(jQuery);
21694(function($) {
21695
21696    // Imports ================================================================
21697    var kendo = window.kendo,
21698        Class = kendo.Class,
21699        extend = $.extend,
21700        Editor = kendo.ui.editor,
21701        dom = Editor.Dom,
21702        findNodeIndex = dom.findNodeIndex,
21703        isDataNode = dom.isDataNode,
21704        findClosestAncestor = dom.findClosestAncestor,
21705        getNodeLength = dom.getNodeLength,
21706        normalize = dom.normalize;
21707
21708var SelectionUtils = {
21709    selectionFromWindow: function(window) {
21710        if ($.browser.msie && $.browser.version < 9) {
21711            return new W3CSelection(window.document);
21712        }
21713
21714        return window.getSelection();
21715    },
21716
21717    selectionFromRange: function(range) {
21718        var rangeDocument = RangeUtils.documentFromRange(range);
21719        return SelectionUtils.selectionFromDocument(rangeDocument);
21720    },
21721
21722    selectionFromDocument: function(document) {
21723        return SelectionUtils.selectionFromWindow(dom.windowFromDocument(document));
21724    }
21725};
21726
21727var W3CRange = Class.extend({
21728    init: function(doc) {
21729        $.extend(this, {
21730            ownerDocument: doc, /* not part of the spec; used when cloning ranges, traversing the dom and creating fragments */
21731            startContainer: doc,
21732            endContainer: doc,
21733            commonAncestorContainer: doc,
21734            startOffset: 0,
21735            endOffset: 0,
21736            collapsed: true
21737        });
21738    },
21739
21740    // Positioning Methods
21741    setStart: function (node, offset) {
21742        this.startContainer = node;
21743        this.startOffset = offset;
21744        updateRangeProperties(this);
21745        fixIvalidRange(this, true);
21746    },
21747
21748    setEnd: function (node, offset) {
21749        this.endContainer = node;
21750        this.endOffset = offset;
21751        updateRangeProperties(this);
21752        fixIvalidRange(this, false);
21753    },
21754
21755    setStartBefore: function (node) {
21756        this.setStart(node.parentNode, findNodeIndex(node));
21757    },
21758
21759    setStartAfter: function (node) {
21760        this.setStart(node.parentNode, findNodeIndex(node) + 1);
21761    },
21762
21763    setEndBefore: function (node) {
21764        this.setEnd(node.parentNode, findNodeIndex(node));
21765    },
21766
21767    setEndAfter: function (node) {
21768        this.setEnd(node.parentNode, findNodeIndex(node) + 1);
21769    },
21770
21771    selectNode: function (node) {
21772        this.setStartBefore(node);
21773        this.setEndAfter(node);
21774    },
21775
21776    selectNodeContents: function (node) {
21777        this.setStart(node, 0);
21778        this.setEnd(node, node[node.nodeType === 1 ? 'childNodes' : 'nodeValue'].length);
21779    },
21780
21781    collapse: function (toStart) {
21782        var that = this;
21783
21784        if (toStart) {
21785            that.setEnd(that.startContainer, that.startOffset);
21786        } else {
21787            that.setStart(that.endContainer, that.endOffset);
21788        }
21789    },
21790
21791    // Editing Methods
21792
21793    deleteContents: function () {
21794        var that = this,
21795            range = that.cloneRange();
21796
21797        if (that.startContainer != that.commonAncestorContainer) {
21798            that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
21799        }
21800
21801        that.collapse(true);
21802
21803        (function deleteSubtree(iterator) {
21804            while (iterator.next()) {
21805                if (iterator.hasPartialSubtree()) {
21806                    deleteSubtree(iterator.getSubtreeIterator());
21807                } else {
21808                    iterator.remove();
21809                }
21810            }
21811        })(new RangeIterator(range));
21812    },
21813
21814    cloneContents: function () {
21815        // clone subtree
21816        var document = RangeUtils.documentFromRange(this);
21817        return (function cloneSubtree(iterator) {
21818                var node, frag = document.createDocumentFragment();
21819
21820                while (node = iterator.next()) {
21821                    node = node.cloneNode(!iterator.hasPartialSubtree());
21822
21823                    if (iterator.hasPartialSubtree()) {
21824                        node.appendChild(cloneSubtree(iterator.getSubtreeIterator()));
21825                    }
21826
21827                    frag.appendChild(node);
21828                }
21829
21830                return frag;
21831        })(new RangeIterator(this));
21832    },
21833
21834    extractContents: function () {
21835        var that = this,
21836            range = that.cloneRange();
21837
21838        if (that.startContainer != that.commonAncestorContainer) {
21839            that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
21840        }
21841
21842        that.collapse(true);
21843
21844        var document = RangeUtils.documentFromRange(that);
21845
21846        return (function extractSubtree(iterator) {
21847            var node, frag = document.createDocumentFragment();
21848
21849            while (node = iterator.next()) {
21850                if (iterator.hasPartialSubtree()) {
21851                    node = node.cloneNode(false);
21852                    node.appendChild(extractSubtree(iterator.getSubtreeIterator()));
21853                } else {
21854                    iterator.remove(that.originalRange);
21855                }
21856
21857                frag.appendChild(node);
21858            }
21859
21860            return frag;
21861        })(new RangeIterator(range));
21862    },
21863
21864    insertNode: function (node) {
21865        var that = this;
21866
21867        if (isDataNode(that.startContainer)) {
21868            if (that.startOffset != that.startContainer.nodeValue.length) {
21869                dom.splitDataNode(that.startContainer, that.startOffset);
21870            }
21871
21872            dom.insertAfter(node, that.startContainer);
21873        } else {
21874            dom.insertAt(that.startContainer, node, that.startOffset);
21875        }
21876
21877        that.setStart(that.startContainer, that.startOffset);
21878    },
21879
21880    cloneRange: function () {
21881        // fast copy
21882        return $.extend(new W3CRange(this.ownerDocument), {
21883            startContainer: this.startContainer,
21884            endContainer: this.endContainer,
21885            commonAncestorContainer: this.commonAncestorContainer,
21886            startOffset: this.startOffset,
21887            endOffset: this.endOffset,
21888            collapsed: this.collapsed,
21889
21890            originalRange: this /* not part of the spec; used to update the original range when calling extractContents() on clones */
21891        });
21892    },
21893
21894    // used for debug purposes
21895    toString: function () {
21896        var startNodeName = this.startContainer.nodeName,
21897            endNodeName = this.endContainer.nodeName;
21898
21899        return [startNodeName == "#text" ? this.startContainer.nodeValue : startNodeName, '(', this.startOffset, ') : ',
21900                endNodeName == "#text" ? this.endContainer.nodeValue : endNodeName, '(', this.endOffset, ')'].join('');
21901    }
21902});
21903
21904/* can be used in Range.compareBoundaryPoints if we need it one day */
21905function compareBoundaries(start, end, startOffset, endOffset) {
21906    if (start == end) {
21907        return endOffset - startOffset;
21908    }
21909
21910    // end is child of start
21911    var container = end;
21912    while (container && container.parentNode != start) {
21913        container = container.parentNode;
21914    }
21915
21916    if (container) {
21917        return findNodeIndex(container) - startOffset;
21918    }
21919
21920    // start is child of end
21921    container = start;
21922    while (container && container.parentNode != end) {
21923        container = container.parentNode;
21924    }
21925
21926    if (container) {
21927        return endOffset - findNodeIndex(container) - 1;
21928    }
21929
21930    // deep traversal
21931    var root = dom.commonAncestor(start, end);
21932    var startAncestor = start;
21933
21934    while (startAncestor && startAncestor.parentNode != root) {
21935        startAncestor = startAncestor.parentNode;
21936    }
21937
21938    if (!startAncestor) {
21939        startAncestor = root;
21940    }
21941
21942    var endAncestor = end;
21943    while (endAncestor && endAncestor.parentNode != root) {
21944        endAncestor = endAncestor.parentNode;
21945    }
21946
21947    if (!endAncestor) {
21948        endAncestor = root;
21949    }
21950
21951    if (startAncestor == endAncestor) {
21952        return 0;
21953    }
21954
21955    return findNodeIndex(endAncestor) - findNodeIndex(startAncestor);
21956}
21957
21958function fixIvalidRange(range, toStart) {
21959    function isInvalidRange(range) {
21960        try {
21961            return compareBoundaries(range.startContainer, range.endContainer, range.startOffset, range.endOffset) < 0;
21962        } catch (ex) {
21963            // range was initially invalid (e.g. when cloned from invalid range) - it must be fixed
21964            return true;
21965        }
21966    }
21967
21968    if (isInvalidRange(range)) {
21969        if (toStart) {
21970            range.commonAncestorContainer = range.endContainer = range.startContainer;
21971            range.endOffset = range.startOffset;
21972        } else {
21973            range.commonAncestorContainer = range.startContainer = range.endContainer;
21974            range.startOffset = range.endOffset;
21975        }
21976
21977        range.collapsed = true;
21978    }
21979}
21980
21981function updateRangeProperties(range) {
21982    range.collapsed = range.startContainer == range.endContainer && range.startOffset == range.endOffset;
21983
21984    var node = range.startContainer;
21985    while (node && node != range.endContainer && !dom.isAncestorOf(node, range.endContainer)) {
21986        node = node.parentNode;
21987    }
21988
21989    range.commonAncestorContainer = node;
21990}
21991
21992var RangeIterator = Class.extend({
21993    init: function(range) {
21994        $.extend(this, {
21995            range: range,
21996            _current: null,
21997            _next: null,
21998            _end: null
21999        });
22000
22001        if (range.collapsed) {
22002            return;
22003        }
22004
22005        var root = range.commonAncestorContainer;
22006
22007        this._next = range.startContainer == root && !isDataNode(range.startContainer) ?
22008        range.startContainer.childNodes[range.startOffset] :
22009        findClosestAncestor(root, range.startContainer);
22010
22011        this._end = range.endContainer == root && !isDataNode(range.endContainer) ?
22012        range.endContainer.childNodes[range.endOffset] :
22013        findClosestAncestor(root, range.endContainer).nextSibling;
22014    },
22015
22016    hasNext: function () {
22017        return !!this._next;
22018    },
22019
22020    next: function () {
22021        var that = this,
22022            current = that._current = that._next;
22023        that._next = that._current && that._current.nextSibling != that._end ?
22024        that._current.nextSibling : null;
22025
22026        if (isDataNode(that._current)) {
22027            if (that.range.endContainer == that._current) {
22028                current = current.cloneNode(true);
22029                current.deleteData(that.range.endOffset, current.length - that.range.endOffset);
22030            }
22031
22032            if (that.range.startContainer == that._current) {
22033                current = current.cloneNode(true);
22034                current.deleteData(0, that.range.startOffset);
22035            }
22036        }
22037
22038        return current;
22039    },
22040
22041    traverse: function (callback) {
22042        var that = this,
22043            current;
22044
22045        function next() {
22046            that._current = that._next;
22047            that._next = that._current && that._current.nextSibling != that._end ? that._current.nextSibling : null;
22048            return that._current;
22049        }
22050
22051        while (current = next()) {
22052            if (that.hasPartialSubtree()) {
22053                that.getSubtreeIterator().traverse(callback);
22054            } else {
22055                callback(current);
22056            }
22057        }
22058
22059        return current;
22060    },
22061
22062    remove: function (originalRange) {
22063        var that = this,
22064            inStartContainer = that.range.startContainer == that._current,
22065            inEndContainer = that.range.endContainer == that._current,
22066            start, end, delta;
22067
22068        if (isDataNode(that._current) && (inStartContainer || inEndContainer)) {
22069            start = inStartContainer ? that.range.startOffset : 0;
22070            end = inEndContainer ? that.range.endOffset : that._current.length;
22071            delta = end - start;
22072
22073            if (originalRange && (inStartContainer || inEndContainer)) {
22074                if (that._current == originalRange.startContainer && start <= originalRange.startOffset) {
22075                    originalRange.startOffset -= delta;
22076                }
22077
22078                if (that._current == originalRange.endContainer && end <= originalRange.endOffset) {
22079                    originalRange.endOffset -= delta;
22080                }
22081            }
22082
22083            that._current.deleteData(start, delta);
22084        } else {
22085            var parent = that._current.parentNode;
22086
22087            if (originalRange && (that.range.startContainer == parent || that.range.endContainer == parent)) {
22088                var nodeIndex = findNodeIndex(that._current);
22089
22090                if (parent == originalRange.startContainer && nodeIndex <= originalRange.startOffset) {
22091                    originalRange.startOffset -= 1;
22092                }
22093
22094                if (parent == originalRange.endContainer && nodeIndex < originalRange.endOffset) {
22095                    originalRange.endOffset -= 1;
22096                }
22097            }
22098
22099            dom.remove(that._current);
22100        }
22101    },
22102
22103    hasPartialSubtree: function () {
22104        return !isDataNode(this._current) &&
22105        (dom.isAncestorOrSelf(this._current, this.range.startContainer) ||
22106            dom.isAncestorOrSelf(this._current, this.range.endContainer));
22107    },
22108
22109    getSubtreeIterator: function () {
22110        var that = this,
22111            subRange = that.range.cloneRange();
22112
22113        subRange.selectNodeContents(that._current);
22114
22115        if (dom.isAncestorOrSelf(that._current, that.range.startContainer)) {
22116            subRange.setStart(that.range.startContainer, that.range.startOffset);
22117        }
22118
22119        if (dom.isAncestorOrSelf(that._current, that.range.endContainer)) {
22120            subRange.setEnd(that.range.endContainer, that.range.endOffset);
22121        }
22122
22123        return new RangeIterator(subRange);
22124    }
22125});
22126
22127var W3CSelection = Class.extend({
22128    init: function(doc) {
22129        this.ownerDocument = doc;
22130        this.rangeCount = 1;
22131    },
22132
22133    addRange: function (range) {
22134        var textRange = this.ownerDocument.body.createTextRange();
22135
22136        // end container should be adopted first in order to prevent selection with negative length
22137        adoptContainer(textRange, range, false);
22138        adoptContainer(textRange, range, true);
22139
22140        textRange.select();
22141    },
22142
22143    removeAllRanges: function () {
22144        this.ownerDocument.selection.empty();
22145    },
22146
22147    getRangeAt: function () {
22148        var textRange, range = new W3CRange(this.ownerDocument), selection = this.ownerDocument.selection, element;
22149
22150        try {
22151            textRange = selection.createRange();
22152            element = textRange.item ? textRange.item(0) : textRange.parentElement();
22153            if (element.ownerDocument != this.ownerDocument) {
22154                return range;
22155            }
22156        } catch (ex) {
22157            return range;
22158        }
22159
22160        if (selection.type == 'Control') {
22161            range.selectNode(textRange.item(0));
22162        } else {
22163            adoptEndPoint(textRange, range, true);
22164            adoptEndPoint(textRange, range, false);
22165
22166            if (range.startContainer.nodeType == 9) {
22167                range.setStart(range.endContainer, range.startOffset);
22168            }
22169
22170            if (range.endContainer.nodeType == 9) {
22171                range.setEnd(range.startContainer, range.endOffset);
22172            }
22173
22174            if (textRange.compareEndPoints('StartToEnd', textRange) === 0) {
22175                range.collapse(false);
22176            }
22177
22178            var startContainer = range.startContainer,
22179                endContainer = range.endContainer,
22180                body = this.ownerDocument.body;
22181
22182            if (!range.collapsed && range.startOffset === 0 && range.endOffset == getNodeLength(range.endContainer) &&  // check for full body selection
22183                !(startContainer == endContainer && isDataNode(startContainer) && startContainer.parentNode == body)) { // but not when single textnode is selected
22184                var movedStart = false,
22185                    movedEnd = false;
22186
22187                while (findNodeIndex(startContainer) === 0 && startContainer == startContainer.parentNode.firstChild && startContainer != body) {
22188                    startContainer = startContainer.parentNode;
22189                    movedStart = true;
22190                }
22191
22192                while (findNodeIndex(endContainer) == getNodeLength(endContainer.parentNode) - 1 && endContainer == endContainer.parentNode.lastChild && endContainer != body) {
22193                    endContainer = endContainer.parentNode;
22194                    movedEnd = true;
22195                }
22196
22197                if (startContainer == body && endContainer == body && movedStart && movedEnd) {
22198                    range.setStart(startContainer, 0);
22199                    range.setEnd(endContainer, getNodeLength(body));
22200                }
22201            }
22202        }
22203
22204        return range;
22205    }
22206});
22207
22208function adoptContainer(textRange, range, start) {
22209    // find anchor node and offset
22210    var container = range[start ? 'startContainer' : 'endContainer'];
22211    var offset = range[start ? 'startOffset' : 'endOffset'], textOffset = 0;
22212    var anchorNode = isDataNode(container) ? container : container.childNodes[offset] || null;
22213    var anchorParent = isDataNode(container) ? container.parentNode : container;
22214    // visible data nodes need a text offset
22215    if (container.nodeType == 3 || container.nodeType == 4) {
22216        textOffset = offset;
22217    }
22218
22219    // create a cursor element node to position range (since we can't select text nodes)
22220    var cursorNode = anchorParent.insertBefore(dom.create(range.ownerDocument, 'a'), anchorNode);
22221
22222    var cursor = range.ownerDocument.body.createTextRange();
22223    cursor.moveToElementText(cursorNode);
22224    dom.remove(cursorNode);
22225    cursor[start ? 'moveStart' : 'moveEnd']('character', textOffset);
22226    cursor.collapse(false);
22227    textRange.setEndPoint(start ? 'StartToStart' : 'EndToStart', cursor);
22228}
22229
22230function adoptEndPoint(textRange, range, start) {
22231    var cursorNode = dom.create(range.ownerDocument, 'a'), cursor = textRange.duplicate();
22232    cursor.collapse(start);
22233    var parent = cursor.parentElement();
22234    do {
22235        parent.insertBefore(cursorNode, cursorNode.previousSibling);
22236        cursor.moveToElementText(cursorNode);
22237    } while (cursor.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', textRange) > 0 && cursorNode.previousSibling);
22238
22239    cursor.setEndPoint(start ? 'EndToStart' : 'EndToEnd', textRange);
22240
22241    var target = cursorNode.nextSibling;
22242
22243    if (!target) {
22244        // at end of text node
22245        target = cursorNode.previousSibling;
22246
22247        if (target && isDataNode(target)) { // in case of collapsed range in empty tag
22248            range.setEnd(target, target.nodeValue.length);
22249            dom.remove(cursorNode);
22250        } else {
22251            range.selectNodeContents(parent);
22252            dom.remove(cursorNode);
22253            range.endOffset -= 1; // cursorNode was in parent
22254        }
22255
22256        return;
22257    }
22258
22259    dom.remove(cursorNode);
22260
22261    if (isDataNode(target)) {
22262        range[start ? 'setStart' : 'setEnd'](target, cursor.text.length);
22263    } else {
22264        range[start ? 'setStartBefore' : 'setEndBefore'](target);
22265    }
22266}
22267
22268var RangeEnumerator = Class.extend({
22269    init: function(range) {
22270        this.enumerate = function () {
22271            var nodes = [];
22272
22273            function visit(node) {
22274                if (dom.is(node, 'img') || (node.nodeType == 3 && !dom.isWhitespace(node))) {
22275                    nodes.push(node);
22276                } else {
22277                    node = node.firstChild;
22278                    while (node) {
22279                        visit(node);
22280                        node = node.nextSibling;
22281                    }
22282                }
22283            }
22284
22285            new RangeIterator(range).traverse(visit);
22286
22287            return nodes;
22288        };
22289    }
22290});
22291
22292var RestorePoint = Class.extend({
22293    init: function(range) {
22294        var that = this;
22295        that.range = range;
22296        that.rootNode = RangeUtils.documentFromRange(range);
22297        that.body = that.rootNode.body;
22298        that.html = that.body.innerHTML;
22299
22300        that.startContainer = that.nodeToPath(range.startContainer);
22301        that.endContainer = that.nodeToPath(range.endContainer);
22302        that.startOffset = that.offset(range.startContainer, range.startOffset);
22303        that.endOffset = that.offset(range.endContainer, range.endOffset);
22304    },
22305
22306    index: function(node) {
22307        var result = 0,
22308            lastType = node.nodeType;
22309
22310        while (node = node.previousSibling) {
22311            var nodeType = node.nodeType;
22312
22313            if (nodeType != 3 || lastType != nodeType) {
22314                result ++;
22315            }
22316
22317            lastType = nodeType;
22318        }
22319
22320        return result;
22321    },
22322
22323    offset: function(node, value) {
22324        if (node.nodeType == 3) {
22325            while ((node = node.previousSibling) && node.nodeType == 3) {
22326                value += node.nodeValue.length;
22327            }
22328        }
22329
22330        return value;
22331    },
22332
22333    nodeToPath: function(node) {
22334        var path = [];
22335
22336        while (node != this.rootNode) {
22337            path.push(this.index(node));
22338            node = node.parentNode;
22339        }
22340
22341        return path;
22342    },
22343
22344    toRangePoint: function(range, start, path, denormalizedOffset) {
22345        var node = this.rootNode,
22346            length = path.length,
22347            offset = denormalizedOffset;
22348
22349        while (length--) {
22350            node = node.childNodes[path[length]];
22351        }
22352
22353        while (node.nodeType == 3 && node.nodeValue.length < offset) {
22354            offset -= node.nodeValue.length;
22355            node = node.nextSibling;
22356        }
22357
22358        range[start ? 'setStart' : 'setEnd'](node, offset);
22359    },
22360
22361    toRange: function () {
22362        var that = this,
22363            result = that.range.cloneRange();
22364
22365        that.toRangePoint(result, true, that.startContainer, that.startOffset);
22366        that.toRangePoint(result, false, that.endContainer, that.endOffset);
22367
22368        return result;
22369    }
22370
22371});
22372
22373var Marker = Class.extend({
22374    init: function() {
22375        this.caret = null;
22376    },
22377
22378    addCaret: function (range) {
22379        var that = this;
22380
22381        that.caret = dom.create(RangeUtils.documentFromRange(range), 'span', { className: 'k-marker' });
22382        range.insertNode(that.caret);
22383        range.selectNode(that.caret);
22384        return that.caret;
22385    },
22386
22387    removeCaret: function (range) {
22388        var that = this,
22389            previous = that.caret.previousSibling,
22390            startOffset = 0;
22391
22392        if (previous) {
22393            startOffset = isDataNode(previous) ? previous.nodeValue.length : findNodeIndex(previous);
22394        }
22395
22396        var container = that.caret.parentNode;
22397        var containerIndex = previous ? findNodeIndex(previous) : 0;
22398
22399        dom.remove(that.caret);
22400        normalize(container);
22401
22402        var node = container.childNodes[containerIndex];
22403
22404        if (isDataNode(node)) {
22405            range.setStart(node, startOffset);
22406        }
22407        else if (node) {
22408            var textNode = dom.lastTextNode(node);
22409            if (textNode) {
22410                range.setStart(textNode, textNode.nodeValue.length);
22411            } else {
22412                range[previous ? 'setStartAfter' : 'setStartBefore'](node);
22413            }
22414        } else {
22415            if (!$.browser.msie && !container.innerHTML) {
22416                container.innerHTML = '<br _moz_dirty="" />';
22417            }
22418
22419            range.selectNodeContents(container);
22420        }
22421        range.collapse(true);
22422    },
22423
22424    add: function (range, expand) {
22425        var that = this;
22426
22427        if (expand && range.collapsed) {
22428            that.addCaret(range);
22429            range = RangeUtils.expand(range);
22430        }
22431
22432        var rangeBoundary = range.cloneRange();
22433
22434        rangeBoundary.collapse(false);
22435        that.end = dom.create(RangeUtils.documentFromRange(range), 'span', { className: 'k-marker' });
22436        rangeBoundary.insertNode(that.end);
22437
22438        rangeBoundary = range.cloneRange();
22439        rangeBoundary.collapse(true);
22440        that.start = that.end.cloneNode(true);
22441        rangeBoundary.insertNode(that.start);
22442
22443        range.setStartBefore(that.start);
22444        range.setEndAfter(that.end);
22445
22446        normalize(range.commonAncestorContainer);
22447
22448        return range;
22449    },
22450
22451    remove: function (range) {
22452        var that = this,
22453            start = that.start,
22454            end = that.end,
22455            shouldNormalizeStart,
22456            shouldNormalizeEnd,
22457            shouldNormalize;
22458
22459        normalize(range.commonAncestorContainer);
22460
22461        while (!start.nextSibling && start.parentNode) {
22462            start = start.parentNode;
22463        }
22464
22465        while (!end.previousSibling && end.parentNode) {
22466            end = end.parentNode;
22467        }
22468
22469        // merely accessing the siblings will solve range issues in IE
22470        shouldNormalizeStart = (start.previousSibling && start.previousSibling.nodeType == 3) &&
22471                               (start.nextSibling && start.nextSibling.nodeType == 3);
22472
22473        shouldNormalizeEnd = (end.previousSibling && end.previousSibling.nodeType == 3) &&
22474                             (end.nextSibling && end.nextSibling.nodeType == 3);
22475
22476        shouldNormalize = shouldNormalizeStart && shouldNormalizeEnd;
22477
22478        start = start.nextSibling;
22479        end = end.previousSibling;
22480
22481        var collapsed = false;
22482        var collapsedToStart = false;
22483        // collapsed range
22484        if (start == that.end) {
22485            collapsedToStart = !!that.start.previousSibling;
22486            start = end = that.start.previousSibling || that.end.nextSibling;
22487            collapsed = true;
22488        }
22489
22490        dom.remove(that.start);
22491        dom.remove(that.end);
22492
22493        if (!start || !end) {
22494            range.selectNodeContents(range.commonAncestorContainer);
22495            range.collapse(true);
22496            return;
22497        }
22498
22499        var startOffset = collapsed ? isDataNode(start) ? start.nodeValue.length : start.childNodes.length : 0;
22500        var endOffset = isDataNode(end) ? end.nodeValue.length : end.childNodes.length;
22501
22502        if (start.nodeType == 3) {
22503            while (start.previousSibling && start.previousSibling.nodeType == 3) {
22504                start = start.previousSibling;
22505                startOffset += start.nodeValue.length;
22506            }
22507        }
22508
22509        if (end.nodeType == 3) {
22510            while (end.previousSibling && end.previousSibling.nodeType == 3) {
22511                end = end.previousSibling;
22512                endOffset += end.nodeValue.length;
22513            }
22514        }
22515
22516        var startIndex = findNodeIndex(start), startParent = start.parentNode;
22517        var endIndex = findNodeIndex(end), endParent = end.parentNode;
22518
22519        for (var startPointer = start; startPointer.previousSibling; startPointer = startPointer.previousSibling) {
22520            if (startPointer.nodeType == 3 && startPointer.previousSibling.nodeType == 3) {
22521                startIndex--;
22522            }
22523        }
22524
22525        for (var endPointer = end; endPointer.previousSibling; endPointer = endPointer.previousSibling) {
22526            if (endPointer.nodeType == 3 && endPointer.previousSibling.nodeType == 3) {
22527                endIndex--;
22528            }
22529        }
22530
22531        normalize(startParent);
22532
22533        if (start.nodeType == 3) {
22534            start = startParent.childNodes[startIndex];
22535        }
22536
22537        normalize(endParent);
22538        if (end.nodeType == 3) {
22539            end = endParent.childNodes[endIndex];
22540        }
22541
22542        if (collapsed) {
22543            if (start.nodeType == 3) {
22544                range.setStart(start, startOffset);
22545            } else {
22546                range[collapsedToStart ? 'setStartAfter' : 'setStartBefore'](start);
22547            }
22548
22549            range.collapse(true);
22550
22551        } else {
22552            if (start.nodeType == 3) {
22553                range.setStart(start, startOffset);
22554            } else {
22555                range.setStartBefore(start);
22556            }
22557
22558            if (end.nodeType == 3) {
22559                range.setEnd(end, endOffset);
22560            } else {
22561                range.setEndAfter(end);
22562            }
22563        }
22564        if (that.caret) {
22565            that.removeCaret(range);
22566        }
22567    }
22568
22569});
22570
22571var boundary = /[\u0009-\u000d]|\u0020|\u00a0|\ufeff|\.|,|;|:|!|\(|\)|\?/;
22572
22573var RangeUtils = {
22574    nodes: function(range) {
22575        var nodes = RangeUtils.textNodes(range);
22576        if (!nodes.length) {
22577            range.selectNodeContents(range.commonAncestorContainer);
22578            nodes = RangeUtils.textNodes(range);
22579            if (!nodes.length) {
22580                nodes = dom.significantChildNodes(range.commonAncestorContainer);
22581            }
22582        }
22583        return nodes;
22584    },
22585
22586    textNodes: function(range) {
22587        return new RangeEnumerator(range).enumerate();
22588    },
22589
22590    documentFromRange: function(range) {
22591        var startContainer = range.startContainer;
22592        return startContainer.nodeType == 9 ? startContainer : startContainer.ownerDocument;
22593    },
22594
22595    createRange: function(document) {
22596        if ($.browser.msie && $.browser.version < 9) {
22597            return new W3CRange(document);
22598        }
22599
22600        return document.createRange();
22601    },
22602
22603    selectRange: function(range) {
22604        var image = RangeUtils.image(range);
22605        if (image) {
22606            range.setStartAfter(image);
22607            range.setEndAfter(image);
22608        }
22609        var selection = SelectionUtils.selectionFromRange(range);
22610        selection.removeAllRanges();
22611        selection.addRange(range);
22612    },
22613
22614    split: function(range, node, trim) {
22615        function partition(start) {
22616            var partitionRange = range.cloneRange();
22617            partitionRange.collapse(start);
22618            partitionRange[start ? 'setStartBefore' : 'setEndAfter'](node);
22619            var contents = partitionRange.extractContents();
22620            if (trim) {
22621                contents = dom.trim(contents);
22622            }
22623            dom[start ? 'insertBefore' : 'insertAfter'](contents, node);
22624        }
22625        partition(true);
22626        partition(false);
22627    },
22628
22629    getMarkers: function(range) {
22630        var markers = [];
22631
22632        new RangeIterator(range).traverse(function (node) {
22633            if (node.className == 'k-marker') {
22634                markers.push(node);
22635            }
22636        });
22637
22638        return markers;
22639    },
22640
22641    image: function (range) {
22642        var nodes = [];
22643
22644        new RangeIterator(range).traverse(function (node) {
22645            if (dom.is(node, 'img')) {
22646                nodes.push(node);
22647            }
22648        });
22649
22650        if (nodes.length == 1) {
22651            return nodes[0];
22652        }
22653    },
22654
22655    expand: function (range) {
22656        var result = range.cloneRange();
22657
22658        var startContainer = result.startContainer.childNodes[result.startOffset === 0 ? 0 : result.startOffset - 1];
22659        var endContainer = result.endContainer.childNodes[result.endOffset];
22660
22661        if (!isDataNode(startContainer) || !isDataNode(endContainer)) {
22662            return result;
22663        }
22664
22665        var beforeCaret = startContainer.nodeValue;
22666        var afterCaret = endContainer.nodeValue;
22667
22668        if (!beforeCaret || !afterCaret) {
22669            return result;
22670        }
22671
22672        var startOffset = beforeCaret.split('').reverse().join('').search(boundary);
22673        var endOffset = afterCaret.search(boundary);
22674
22675        if (!startOffset || !endOffset) {
22676            return result;
22677        }
22678
22679        endOffset = endOffset == -1 ? afterCaret.length : endOffset;
22680        startOffset = startOffset == -1 ? 0 : beforeCaret.length - startOffset;
22681
22682        result.setStart(startContainer, startOffset);
22683        result.setEnd(endContainer, endOffset);
22684
22685        return result;
22686    },
22687
22688    isExpandable: function (range) {
22689        var node = range.startContainer;
22690        var rangeDocument = RangeUtils.documentFromRange(range);
22691
22692        if (node == rangeDocument || node == rangeDocument.body) {
22693            return false;
22694        }
22695
22696        var result = range.cloneRange();
22697
22698        var value = node.nodeValue;
22699        if (!value) {
22700            return false;
22701        }
22702
22703        var beforeCaret = value.substring(0, result.startOffset);
22704        var afterCaret = value.substring(result.startOffset);
22705
22706        var startOffset = 0, endOffset = 0;
22707
22708        if (beforeCaret) {
22709            startOffset = beforeCaret.split('').reverse().join('').search(boundary);
22710        }
22711
22712        if (afterCaret) {
22713            endOffset = afterCaret.search(boundary);
22714        }
22715
22716        return startOffset && endOffset;
22717    }
22718};
22719
22720extend(Editor, {
22721    SelectionUtils: SelectionUtils,
22722    W3CRange: W3CRange,
22723    RangeIterator: RangeIterator,
22724    W3CSelection: W3CSelection,
22725    RangeEnumerator: RangeEnumerator,
22726    RestorePoint: RestorePoint,
22727    Marker: Marker,
22728    RangeUtils: RangeUtils
22729});
22730
22731})(jQuery);
22732(function($) {
22733
22734    // Imports ================================================================
22735    var kendo = window.kendo,
22736        Class = kendo.Class,
22737        Editor = kendo.ui.editor,
22738        EditorUtils = Editor.EditorUtils,
22739        registerTool = EditorUtils.registerTool,
22740        dom = Editor.Dom,
22741        RangeUtils = Editor.RangeUtils,
22742        selectRange = RangeUtils.selectRange,
22743        Tool = Editor.Tool,
22744        ToolTemplate = Editor.ToolTemplate,
22745        RestorePoint = Editor.RestorePoint,
22746        Marker = Editor.Marker,
22747        extend = $.extend;
22748
22749var Command = Class.extend({
22750    init: function(options) {
22751        var that = this;
22752        that.options = options;
22753        that.restorePoint = new RestorePoint(options.range);
22754        that.marker = new Marker();
22755        that.formatter = options.formatter;
22756    },
22757
22758    getRange: function () {
22759        return this.restorePoint.toRange();
22760    },
22761
22762    lockRange: function (expand) {
22763        return this.marker.add(this.getRange(), expand);
22764    },
22765
22766    releaseRange: function (range) {
22767        this.marker.remove(range);
22768        selectRange(range);
22769    },
22770
22771    undo: function () {
22772        var point = this.restorePoint;
22773        point.body.innerHTML = point.html;
22774        selectRange(point.toRange());
22775    },
22776
22777    redo: function () {
22778        this.exec();
22779    },
22780
22781    exec: function () {
22782        var that = this,
22783        range = that.lockRange(true);
22784        that.formatter.editor = that.editor;
22785        that.formatter.toggle(range);
22786        that.releaseRange(range);
22787    }
22788});
22789
22790var GenericCommand = Class.extend({
22791    init: function(startRestorePoint, endRestorePoint) {
22792        this.body = startRestorePoint.body;
22793        this.startRestorePoint = startRestorePoint;
22794        this.endRestorePoint = endRestorePoint;
22795    },
22796
22797    redo: function () {
22798        this.body.innerHTML = this.endRestorePoint.html;
22799        selectRange(this.endRestorePoint.toRange());
22800    },
22801
22802    undo: function () {
22803        this.body.innerHTML = this.startRestorePoint.html;
22804        selectRange(this.startRestorePoint.toRange());
22805    }
22806});
22807
22808var InsertHtmlCommand = Command.extend({
22809    init: function(options) {
22810        Command.fn.init.call(this, options);
22811
22812        this.managesUndoRedo = true;
22813    },
22814
22815    exec: function() {
22816        var editor = this.editor;
22817        var range = editor.getRange();
22818        var startRestorePoint = new RestorePoint(range);
22819
22820        editor.clipboard.paste(this.options.value || '');
22821        editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
22822
22823        editor.focus();
22824    }
22825});
22826
22827var InsertHtmlTool = Tool.extend({
22828    initialize: function(ui, initOptions) {
22829        var editor = initOptions.editor;
22830
22831        new Editor.SelectBox(ui, {
22832            dataSource: editor.options.insertHtml || [],
22833            dataTextField: "text",
22834            dataValueField: "value",
22835            change: function (e) {
22836                Tool.exec(editor, 'insertHtml', this.value());
22837            },
22838            title: editor.options.localization.insertHtml,
22839            highlightFirst: false
22840        });
22841    },
22842
22843    command: function (commandArguments) {
22844        return new InsertHtmlCommand(commandArguments);
22845    },
22846
22847    update: function(ui, nodes) {
22848        var selectbox = ui.data("kendoSelectBox") || ui.find("select").data("kendoSelectBox");
22849        selectbox.close();
22850        selectbox.value(selectbox.options.title);
22851    }
22852});
22853
22854var UndoRedoStack = Class.extend({
22855    init: function() {
22856        this.stack = [];
22857        this.currentCommandIndex = -1;
22858    },
22859
22860    push: function (command) {
22861        var that = this;
22862
22863        that.stack = that.stack.slice(0, that.currentCommandIndex + 1);
22864        that.currentCommandIndex = that.stack.push(command) - 1;
22865    },
22866
22867    undo: function () {
22868        if (this.canUndo()) {
22869            this.stack[this.currentCommandIndex--].undo();
22870        }
22871    },
22872
22873    redo: function () {
22874        if (this.canRedo()) {
22875            this.stack[++this.currentCommandIndex].redo();
22876        }
22877    },
22878
22879    canUndo: function () {
22880        return this.currentCommandIndex >= 0;
22881    },
22882
22883    canRedo: function () {
22884        return this.currentCommandIndex != this.stack.length - 1;
22885    }
22886});
22887
22888var TypingHandler = Class.extend({
22889    init: function(editor) {
22890        this.editor = editor;
22891    },
22892
22893    keydown: function (e) {
22894        var that = this,
22895            editor = that.editor,
22896            keyboard = editor.keyboard,
22897            isTypingKey = keyboard.isTypingKey(e);
22898
22899        if (isTypingKey && !keyboard.isTypingInProgress()) {
22900            var range = editor.getRange();
22901            that.startRestorePoint = new RestorePoint(range);
22902
22903            keyboard.startTyping(function () {
22904                editor.selectionRestorePoint = that.endRestorePoint = new RestorePoint(editor.getRange());
22905                editor.undoRedoStack.push(new GenericCommand(that.startRestorePoint, that.endRestorePoint));
22906            });
22907
22908            return true;
22909        }
22910
22911        return false;
22912    },
22913
22914    keyup: function (e) {
22915        var keyboard = this.editor.keyboard;
22916
22917        if (keyboard.isTypingInProgress()) {
22918            keyboard.endTyping();
22919            return true;
22920        }
22921
22922        return false;
22923    }
22924});
22925
22926var SystemHandler = Class.extend({
22927    init: function(editor) {
22928        this.editor = editor;
22929        this.systemCommandIsInProgress = false;
22930    },
22931
22932    createUndoCommand: function () {
22933        var that = this;
22934
22935        that.endRestorePoint = new RestorePoint(that.editor.getRange());
22936        that.editor.undoRedoStack.push(new GenericCommand(that.startRestorePoint, that.endRestorePoint));
22937        that.startRestorePoint = that.endRestorePoint;
22938    },
22939
22940    changed: function () {
22941        if (this.startRestorePoint) {
22942            return this.startRestorePoint.html != this.editor.body.innerHTML;
22943        }
22944
22945        return false;
22946    },
22947
22948    keydown: function (e) {
22949        var that = this,
22950            editor = that.editor,
22951            keyboard = editor.keyboard;
22952
22953        if (keyboard.isModifierKey(e)) {
22954
22955            if (keyboard.isTypingInProgress()) {
22956                keyboard.endTyping(true);
22957            }
22958
22959            that.startRestorePoint = new RestorePoint(editor.getRange());
22960            return true;
22961        }
22962
22963        if (keyboard.isSystem(e)) {
22964            that.systemCommandIsInProgress = true;
22965
22966            if (that.changed()) {
22967                that.systemCommandIsInProgress = false;
22968                that.createUndoCommand();
22969            }
22970
22971            return true;
22972        }
22973
22974        return false;
22975    },
22976
22977    keyup: function (e) {
22978        var that = this;
22979
22980        if (that.systemCommandIsInProgress && that.changed()) {
22981            that.systemCommandIsInProgress = false;
22982            that.createUndoCommand(e);
22983            return true;
22984        }
22985
22986        return false;
22987    }
22988});
22989
22990var Keyboard = Class.extend({
22991    init: function(handlers) {
22992        this.handlers = handlers;
22993        this.typingInProgress = false;
22994    },
22995
22996    isCharacter: function(keyCode) {
22997        return (keyCode >= 48 && keyCode <= 90) || (keyCode >= 96 && keyCode <= 111) ||
22998               (keyCode >= 186 && keyCode <= 192) || (keyCode >= 219 && keyCode <= 222);
22999    },
23000
23001    toolFromShortcut: function (tools, e) {
23002        var key = String.fromCharCode(e.keyCode),
23003            toolName,
23004            toolOptions;
23005
23006        for (toolName in tools) {
23007            toolOptions = $.extend({ ctrl: false, alt: false, shift: false }, tools[toolName].options);
23008
23009            if ((toolOptions.key == key || toolOptions.key == e.keyCode) &&
23010                toolOptions.ctrl == e.ctrlKey &&
23011                toolOptions.alt == e.altKey &&
23012                toolOptions.shift == e.shiftKey) {
23013                return toolName;
23014            }
23015        }
23016    },
23017
23018    isTypingKey: function (e) {
23019        var keyCode = e.keyCode;
23020        return (this.isCharacter(keyCode) && !e.ctrlKey && !e.altKey) ||
23021               keyCode == 32 || keyCode == 13 || keyCode == 8 ||
23022               (keyCode == 46 && !e.shiftKey && !e.ctrlKey && !e.altKey);
23023    },
23024
23025    isModifierKey: function (e) {
23026        var keyCode = e.keyCode;
23027        return (keyCode == 17 && !e.shiftKey && !e.altKey) ||
23028               (keyCode == 16 && !e.ctrlKey && !e.altKey) ||
23029               (keyCode == 18 && !e.ctrlKey && !e.shiftKey);
23030    },
23031
23032    isSystem: function (e) {
23033        return e.keyCode == 46 && e.ctrlKey && !e.altKey && !e.shiftKey;
23034    },
23035
23036    startTyping: function (callback) {
23037        this.onEndTyping = callback;
23038        this.typingInProgress = true;
23039    },
23040
23041    stopTyping: function() {
23042        this.typingInProgress = false;
23043        if (this.onEndTyping) {
23044            this.onEndTyping();
23045        }
23046    },
23047
23048    endTyping: function (force) {
23049        var that = this;
23050        that.clearTimeout();
23051        if (force) {
23052            that.stopTyping();
23053        } else {
23054            that.timeout = window.setTimeout($.proxy(that.stopTyping, that), 1000);
23055        }
23056    },
23057
23058    isTypingInProgress: function () {
23059        return this.typingInProgress;
23060    },
23061
23062    clearTimeout: function () {
23063        window.clearTimeout(this.timeout);
23064    },
23065
23066    notify: function(e, what) {
23067        var i, handlers = this.handlers;
23068
23069        for (i = 0; i < handlers.length; i++) {
23070            if (handlers[i][what](e)) {
23071                break;
23072            }
23073        }
23074    },
23075
23076    keydown: function (e) {
23077        this.notify(e, 'keydown');
23078    },
23079
23080    keyup: function (e) {
23081        this.notify(e, 'keyup');
23082    }
23083});
23084
23085var Clipboard = Class.extend({
23086    init: function(editor) {
23087        this.editor = editor;
23088        this.cleaners = [new MSWordFormatCleaner()];
23089    },
23090
23091    htmlToFragment: function(html) {
23092        var editor = this.editor,
23093            doc = editor.document,
23094            container = dom.create(doc, 'div'),
23095            fragment = doc.createDocumentFragment();
23096
23097        container.innerHTML = html;
23098
23099        while (container.firstChild) {
23100            fragment.appendChild(container.firstChild);
23101        }
23102
23103        return fragment;
23104    },
23105
23106    isBlock: function(html) {
23107        return (/<(div|p|ul|ol|table|h[1-6])/i).test(html);
23108    },
23109
23110    oncut: function(e) {
23111        var editor = this.editor,
23112            startRestorePoint = new RestorePoint(editor.getRange());
23113        setTimeout(function() {
23114            editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
23115        });
23116    },
23117
23118    onpaste: function(e) {
23119        var editor = this.editor,
23120            range = editor.getRange(),
23121            bom = "\ufeff",
23122            startRestorePoint = new RestorePoint(range),
23123            clipboardNode = dom.create(editor.document, 'div', {className:'k-paste-container', innerHTML: bom });
23124
23125        editor.body.appendChild(clipboardNode);
23126
23127        if (editor.body.createTextRange) {
23128            e.preventDefault();
23129            var r = editor.createRange();
23130            r.selectNodeContents(clipboardNode);
23131            editor.selectRange(r);
23132            var textRange = editor.body.createTextRange();
23133            textRange.moveToElementText(clipboardNode);
23134            $(editor.body).unbind('paste');
23135            textRange.execCommand('Paste');
23136            $(editor.body).bind('paste', $.proxy(arguments.callee, this));
23137        } else {
23138            var clipboardRange = editor.createRange();
23139            clipboardRange.selectNodeContents(clipboardNode);
23140            selectRange(clipboardRange);
23141        }
23142
23143        setTimeout(function() {
23144            var html, args = { html: "" };
23145
23146            selectRange(range);
23147            dom.remove(clipboardNode);
23148
23149            if (clipboardNode.lastChild && dom.is(clipboardNode.lastChild, 'br')) {
23150                dom.remove(clipboardNode.lastChild);
23151            }
23152
23153            html = clipboardNode.innerHTML;
23154
23155            if (html != bom) {
23156                args.html = html;
23157            }
23158
23159            editor.trigger("paste", args);
23160            editor.clipboard.paste(args.html, true);
23161            editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
23162            Editor.EditorUtils.select(editor);
23163        });
23164    },
23165
23166    splittableParent: function(block, node) {
23167        var parentNode, body;
23168
23169        if (block) {
23170            return dom.parentOfType(node, ['p', 'ul', 'ol']) || node.parentNode;
23171        }
23172
23173        parentNode = node.parentNode;
23174        body = node.ownerDocument.body;
23175
23176        if (dom.isInline(parentNode)) {
23177            while (parentNode.parentNode != body && !dom.isBlock(parentNode.parentNode)) {
23178                parentNode = parentNode.parentNode;
23179            }
23180        }
23181
23182        return parentNode;
23183    },
23184
23185    paste: function (html, clean) {
23186        var editor = this.editor,
23187            i, l;
23188
23189        for (i = 0, l = this.cleaners.length; i < l; i++) {
23190            if (this.cleaners[i].applicable(html)) {
23191                html = this.cleaners[i].clean(html);
23192            }
23193        }
23194
23195        if (clean) {
23196            // remove br elements which immediately precede block elements
23197            html = html.replace(/(<br>(\s|&nbsp;)*)+(<\/?(div|p|li|col|t))/ig, "$3");
23198            // remove empty inline elements
23199            html = html.replace(/<(a|span)[^>]*><\/\1>/ig, "");
23200        }
23201
23202        // It is possible in IE to copy just <li> tags
23203        html = html.replace(/^<li/i, '<ul><li').replace(/li>$/g, 'li></ul>');
23204
23205        var block = this.isBlock(html);
23206
23207        var range = editor.getRange();
23208        range.deleteContents();
23209
23210        if (range.startContainer == editor.document) {
23211            range.selectNodeContents(editor.body);
23212        }
23213
23214        var marker = new Marker();
23215        var caret = marker.addCaret(range);
23216
23217        var parent = this.splittableParent(block, caret);
23218        var unwrap = false;
23219
23220        if (!/body|td/.test(dom.name(parent)) && (block || dom.isInline(parent))) {
23221            range.selectNode(caret);
23222            RangeUtils.split(range, parent, true);
23223            unwrap = true;
23224        }
23225
23226        var fragment = this.htmlToFragment(html);
23227
23228        if (fragment.firstChild && fragment.firstChild.className === "k-paste-container") {
23229            var fragmentsHtml = [];
23230            for (i = 0, l = fragment.childNodes.length; i < l; i++) {
23231                fragmentsHtml.push(fragment.childNodes[i].innerHTML);
23232            }
23233
23234            fragment = this.htmlToFragment(fragmentsHtml.join('<br />'));
23235        }
23236
23237        range.insertNode(fragment);
23238
23239        parent = this.splittableParent(block, caret);
23240        if (unwrap) {
23241            while (caret.parentNode != parent) {
23242                dom.unwrap(caret.parentNode);
23243            }
23244
23245            dom.unwrap(caret.parentNode);
23246        }
23247
23248        dom.normalize(range.commonAncestorContainer);
23249        caret.style.display = 'inline';
23250        dom.scrollTo(caret);
23251        marker.removeCaret(range);
23252        selectRange(range);
23253    }
23254});
23255
23256var MSWordFormatCleaner = Class.extend({
23257    init: function() {
23258        this.replacements = [
23259            /<\?xml[^>]*>/gi, '',
23260            /<!--(.|\n)*?-->/g, '', /* comments */
23261            /&quot;/g, "'", /* encoded quotes (in attributes) */
23262            /(?:<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',
23263            /<br><br>/g, '<BR><BR>',
23264            /<br>/g, ' ',
23265            /<table([^>]*)>(\s|&nbsp;)+<t/gi, '<table$1><t',
23266            /<tr[^>]*>(\s|&nbsp;)*<\/tr>/gi, '',
23267            /<tbody[^>]*>(\s|&nbsp;)*<\/tbody>/gi, '',
23268            /<table[^>]*>(\s|&nbsp;)*<\/table>/gi, '',
23269            /<BR><BR>/g, '<br>',
23270            /^\s*(&nbsp;)+/gi, '',
23271            /(&nbsp;|<br[^>]*>)+\s*$/gi, '',
23272            /mso-[^;"]*;?/ig, '', /* office-related CSS attributes */
23273            /<(\/?)b(\s[^>]*)?>/ig, '<$1strong$2>',
23274            /<(\/?)i(\s[^>]*)?>/ig, '<$1em$2>',
23275            /<\/?(meta|link|style|o:|v:|x:)[^>]*>((?:.|\n)*?<\/(meta|link|style|o:|v:|x:)[^>]*>)?/ig, '', /* external references and namespaced tags */
23276            /style=(["|'])\s*\1/g, '' /* empty style attributes */
23277        ];
23278    },
23279
23280    applicable: function(html) {
23281        return (/class="?Mso|style="[^"]*mso-/i).test(html);
23282    },
23283
23284    listType: function(html) {
23285        if (/^[\u2022\u00b7\u00a7\u00d8o]\u00a0+/.test(html)) {
23286            return 'ul';
23287        }
23288
23289        if (/^\s*\w+[\.\)]\u00a0{2,}/.test(html)) {
23290            return 'ol';
23291        }
23292    },
23293
23294    lists: function(html) {
23295        var placeholder = dom.create(document, 'div', {innerHTML: html});
23296        var blockChildren = $(dom.blockElements.join(','), placeholder);
23297
23298        var lastMargin = -1, lastType, levels = {'ul':{}, 'ol':{}}, li = placeholder;
23299
23300        for (var i = 0; i < blockChildren.length; i++) {
23301            var p = blockChildren[i];
23302            html = p.innerHTML.replace(/<\/?\w+[^>]*>/g, '').replace(/&nbsp;/g, '\u00a0');
23303            var type = this.listType(html);
23304
23305            if (!type || dom.name(p) != 'p') {
23306                if (!p.innerHTML) {
23307                    dom.remove(p);
23308                } else {
23309                    levels = {'ul':{}, 'ol':{}};
23310                    li = placeholder;
23311                    lastMargin = -1;
23312                }
23313                continue;
23314            }
23315
23316            var margin = parseFloat(p.style.marginLeft || 0);
23317            var list = levels[type][margin];
23318
23319            if (margin > lastMargin || !list) {
23320                list = dom.create(document, type);
23321
23322                if (li == placeholder) {
23323                    dom.insertBefore(list, p);
23324                } else {
23325                    li.appendChild(list);
23326                }
23327
23328                levels[type][margin] = list;
23329            }
23330
23331            if (lastType != type) {
23332                for (var key in levels) {
23333                    for (var child in levels[key]) {
23334                        if ($.contains(list, levels[key][child])) {
23335                            delete levels[key][child];
23336                        }
23337                    }
23338                }
23339            }
23340
23341            dom.remove(p.firstChild);
23342            li = dom.create(document, 'li', {innerHTML:p.innerHTML});
23343            list.appendChild(li);
23344            dom.remove(p);
23345            lastMargin = margin;
23346            lastType = type;
23347        }
23348        return placeholder.innerHTML;
23349    },
23350
23351    stripEmptyAnchors: function(html) {
23352        return html.replace(/<a([^>]*)>\s*<\/a>/ig, function(a, attributes) {
23353            if (!attributes || attributes.indexOf("href") < 0) {
23354                return "";
23355            }
23356
23357            return a;
23358        });
23359    },
23360
23361    clean: function(html) {
23362        var that = this,
23363            replacements = that.replacements,
23364            i, l;
23365
23366        for (i = 0, l = replacements.length; i < l; i += 2) {
23367            html = html.replace(replacements[i], replacements[i+1]);
23368        }
23369
23370        html = that.stripEmptyAnchors(html);
23371        html = that.lists(html);
23372        html = html.replace(/\s+class="?[^"\s>]*"?/ig, '');
23373
23374        return html;
23375    }
23376});
23377
23378extend(Editor, {
23379    Command: Command,
23380    GenericCommand: GenericCommand,
23381    InsertHtmlCommand: InsertHtmlCommand,
23382    InsertHtmlTool: InsertHtmlTool,
23383    UndoRedoStack: UndoRedoStack,
23384    TypingHandler: TypingHandler,
23385    SystemHandler: SystemHandler,
23386    Keyboard: Keyboard,
23387    Clipboard: Clipboard,
23388    MSWordFormatCleaner: MSWordFormatCleaner
23389});
23390
23391registerTool("insertHtml", new InsertHtmlTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Insert HTML", initialValue: "Insert HTML"})}));
23392
23393})(jQuery);
23394(function($) {
23395
23396var kendo = window.kendo,
23397    Class = kendo.Class,
23398    Editor = kendo.ui.editor,
23399    formats = kendo.ui.Editor.fn.options.formats,
23400    EditorUtils = Editor.EditorUtils,
23401    Tool = Editor.Tool,
23402    ToolTemplate = Editor.ToolTemplate,
23403    FormatTool = Editor.FormatTool,
23404    dom = Editor.Dom,
23405    RangeUtils = Editor.RangeUtils,
23406    extend = $.extend,
23407    registerTool = Editor.EditorUtils.registerTool,
23408    registerFormat = Editor.EditorUtils.registerFormat,
23409    KMARKER = "k-marker";
23410
23411var InlineFormatFinder = Class.extend({
23412    init: function(format) {
23413        this.format = format;
23414    },
23415
23416    numberOfSiblings: function(referenceNode) {
23417        var textNodesCount = 0,
23418            elementNodesCount = 0,
23419            markerCount = 0,
23420            parentNode = referenceNode.parentNode,
23421            node;
23422
23423        for (node = parentNode.firstChild; node; node = node.nextSibling) {
23424            if (node != referenceNode) {
23425                if (node.className == KMARKER) {
23426                    markerCount++;
23427                } else if (node.nodeType == 3) {
23428                    textNodesCount++;
23429                } else {
23430                    elementNodesCount++;
23431                }
23432            }
23433        }
23434
23435        if (markerCount > 1 && parentNode.firstChild.className == KMARKER && parentNode.lastChild.className == KMARKER) {
23436            // full node selection
23437            return 0;
23438        } else {
23439            return elementNodesCount + textNodesCount;
23440        }
23441    },
23442
23443    findSuitable: function (sourceNode, skip) {
23444        if (!skip && this.numberOfSiblings(sourceNode) > 0) {
23445            return null;
23446        }
23447
23448        return dom.parentOfType(sourceNode, this.format[0].tags);
23449    },
23450
23451    findFormat: function (sourceNode) {
23452        var format = this.format,
23453            attrEquals = dom.attrEquals,
23454            i, len, node, tags, attributes;
23455
23456        for (i = 0, len = format.length; i < len; i++) {
23457            node = sourceNode;
23458            tags = format[i].tags;
23459            attributes = format[i].attr;
23460
23461            if (node && dom.ofType(node, tags) && attrEquals(node, attributes)) {
23462                return node;
23463            }
23464
23465            while (node) {
23466                node = dom.parentOfType(node, tags);
23467                if (node && attrEquals(node, attributes)) {
23468                    return node;
23469                }
23470            }
23471        }
23472
23473        return null;
23474    },
23475
23476    isFormatted: function (nodes) {
23477        var i, len;
23478
23479        for (i = 0, len = nodes.length; i < len; i++) {
23480            if (this.findFormat(nodes[i])) {
23481                return true;
23482            }
23483        }
23484        return false;
23485    }
23486});
23487
23488var InlineFormatter = Class.extend({
23489    init: function(format, values) {
23490        var that = this;
23491        that.finder = new InlineFormatFinder(format);
23492        that.attributes = extend({}, format[0].attr, values);
23493        that.tag = format[0].tags[0];
23494    },
23495
23496    wrap: function(node) {
23497        return dom.wrap(node, dom.create(node.ownerDocument, this.tag, this.attributes));
23498    },
23499
23500    activate: function(range, nodes) {
23501        var that = this;
23502        if (that.finder.isFormatted(nodes)) {
23503            that.split(range);
23504            that.remove(nodes);
23505        } else {
23506            that.apply(nodes);
23507        }
23508    },
23509
23510    toggle: function (range) {
23511        var nodes = RangeUtils.textNodes(range);
23512
23513        if (nodes.length > 0) {
23514            this.activate(range, nodes);
23515        }
23516    },
23517
23518    apply: function (nodes) {
23519        var that = this,
23520        formatNodes = [],
23521        i, l, node, formatNode;
23522
23523        for (i = 0, l = nodes.length; i < l; i++) {
23524            node = nodes[i];
23525            formatNode = that.finder.findSuitable(node);
23526            if (formatNode) {
23527                dom.attr(formatNode, that.attributes);
23528            } else {
23529                formatNode = that.wrap(node);
23530            }
23531
23532            formatNodes.push(formatNode);
23533        }
23534
23535        that.consolidate(formatNodes);
23536    },
23537
23538    remove: function (nodes) {
23539        var that = this,
23540        i, l, formatNode;
23541
23542        for (i = 0, l = nodes.length; i < l; i++) {
23543            formatNode = that.finder.findFormat(nodes[i]);
23544            if (formatNode) {
23545                if (that.attributes && that.attributes.style) {
23546                    dom.unstyle(formatNode, that.attributes.style);
23547                    if (!formatNode.style.cssText) {
23548                        dom.unwrap(formatNode);
23549                    }
23550                } else {
23551                    dom.unwrap(formatNode);
23552                }
23553            }
23554        }
23555    },
23556
23557    split: function (range) {
23558        var nodes = RangeUtils.textNodes(range),
23559        l = nodes.length,
23560        i, formatNode;
23561
23562        if (l > 0) {
23563            for (i = 0; i < l; i++) {
23564                formatNode = this.finder.findFormat(nodes[i]);
23565                if (formatNode) {
23566                    RangeUtils.split(range, formatNode, true);
23567                }
23568            }
23569        }
23570    },
23571
23572    consolidate: function (nodes) {
23573        var node, last;
23574
23575        while (nodes.length > 1) {
23576            node = nodes.pop();
23577            last = nodes[nodes.length - 1];
23578
23579            if (node.previousSibling && node.previousSibling.className == KMARKER) {
23580                last.appendChild(node.previousSibling);
23581            }
23582
23583            if (node.tagName == last.tagName && node.previousSibling == last && node.style.cssText == last.style.cssText) {
23584                while (node.firstChild) {
23585                    last.appendChild(node.firstChild);
23586                }
23587                dom.remove(node);
23588            }
23589        }
23590    }
23591});
23592
23593var GreedyInlineFormatFinder = InlineFormatFinder.extend({
23594    init: function(format, greedyProperty) {
23595        var that = this;
23596        that.format = format;
23597        that.greedyProperty = greedyProperty;
23598        InlineFormatFinder.fn.init.call(that, format);
23599    },
23600
23601    getInlineCssValue: function(node) {
23602        var attributes = node.attributes,
23603            trim = $.trim,
23604            i, l, attribute, name, attributeValue, css, pair, cssIndex, len, propertyAndValue, property, value;
23605
23606        if (!attributes) {
23607            return;
23608        }
23609
23610        for (i = 0, l = attributes.length; i < l; i++) {
23611            attribute = attributes[i];
23612            name = attribute.nodeName;
23613            attributeValue = attribute.nodeValue;
23614
23615            if (attribute.specified && name == "style") {
23616
23617                css = trim(attributeValue || node.style.cssText).split(";");
23618
23619                for (cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
23620                    pair = css[cssIndex];
23621                    if (pair.length) {
23622                        propertyAndValue = pair.split(":");
23623                        property = trim(propertyAndValue[0].toLowerCase());
23624                        value = trim(propertyAndValue[1]);
23625
23626                        if (property != this.greedyProperty) {
23627                            continue;
23628                        }
23629
23630                        return property.indexOf("color") >= 0 ? dom.toHex(value) : value;
23631                    }
23632                }
23633            }
23634        }
23635
23636        return;
23637    },
23638
23639    getFormatInner: function (node) {
23640        var $node = $(dom.isDataNode(node) ? node.parentNode : node),
23641            parents = $node.parents().andSelf(),
23642            i, len, value;
23643
23644        for (i = 0, len = parents.length; i < len; i++) {
23645            value = this.greedyProperty == "className" ? parents[i].className : this.getInlineCssValue(parents[i]);
23646            if (value) {
23647                return value;
23648            }
23649        }
23650
23651        return "inherit";
23652    },
23653
23654    getFormat: function (nodes) {
23655        var result = this.getFormatInner(nodes[0]),
23656        i, len;
23657
23658        for (i = 1, len = nodes.length; i < len; i++) {
23659            if (result != this.getFormatInner(nodes[i])) {
23660                return "";
23661            }
23662        }
23663
23664        return result;
23665    },
23666
23667    isFormatted: function (nodes) {
23668        return this.getFormat(nodes) !== "";
23669    }
23670});
23671
23672var GreedyInlineFormatter = InlineFormatter.extend({
23673    init: function(format, values, greedyProperty) {
23674        var that = this;
23675
23676        InlineFormatter.fn.init.call(that, format, values);
23677
23678        that.greedyProperty = greedyProperty;
23679        that.values = values;
23680        that.finder = new GreedyInlineFormatFinder(format, greedyProperty);
23681    },
23682
23683    activate: function(range, nodes) {
23684        var that = this;
23685        that.split(range);
23686
23687        if (that.greedyProperty) {
23688            var camelCase = that.greedyProperty.replace(/-([a-z])/, function(all, letter) { return letter.toUpperCase(); });
23689            that[that.values.style[camelCase] == "inherit" ? "remove" : "apply"](nodes);
23690        } else {
23691            that.apply(nodes);
23692        }
23693    }
23694});
23695
23696function inlineFormatWillDelayExecution (range) {
23697    return range.collapsed && !RangeUtils.isExpandable(range);
23698}
23699
23700var InlineFormatTool = FormatTool.extend({
23701    init: function(options) {
23702        FormatTool.fn.init.call(this, extend(options, {
23703            finder: new InlineFormatFinder(options.format),
23704            formatter: function () { return new InlineFormatter(options.format); }
23705        }));
23706
23707        this.willDelayExecution = inlineFormatWillDelayExecution;
23708    }
23709});
23710
23711var FontTool = Tool.extend({
23712    init: function(options) {
23713        var that = this;
23714        Tool.fn.init.call(that, options);
23715
23716        // IE has single selection hence we are using select box instead of combobox
23717        that.options = options;
23718        that.type = ($.browser.msie || kendo.support.touch) ? "kendoDropDownList" : "kendoComboBox";
23719        that.format = [{ tags: ["span"] }],
23720        that.finder = new GreedyInlineFormatFinder(that.format, options.cssAttr);
23721    },
23722
23723    command: function (commandArguments) {
23724        var options = this.options,
23725            format = this.format,
23726            style = {};
23727
23728        return new Editor.FormatCommand(extend(commandArguments, {
23729            formatter: function () {
23730                style[options.domAttr] = commandArguments.value;
23731
23732                return new GreedyInlineFormatter(format, { style: style }, options.cssAttr);
23733            }
23734        }));
23735    },
23736
23737    willDelayExecution: inlineFormatWillDelayExecution,
23738
23739    update: function(ui, nodes, pendingFormats) {
23740        var that = this,
23741            list = ui.data(that.type),
23742            pendingFormat = pendingFormats.getPending(that.name),
23743            format = (pendingFormat && pendingFormat.params) ? pendingFormat.params.value : that.finder.getFormat(nodes);
23744
23745        list.close();
23746        list.value(format);
23747    },
23748
23749    initialize: function (ui, initOptions) {
23750        var editor = initOptions.editor,
23751            toolName = this.options.name;
23752
23753        ui[this.type]({
23754            dataTextField: "Text",
23755            dataValueField: "Value",
23756            dataSource: editor.options[toolName],
23757            change: function (e) {
23758                Tool.exec(editor, toolName, this.value());
23759            },
23760            highlightFirst: false
23761        });
23762
23763        ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");
23764
23765        ui.data(this.type).value("inherit");
23766    }
23767
23768});
23769
23770var ColorTool = Tool.extend({
23771    init: function(options) {
23772        Tool.fn.init.call(this, options);
23773
23774        this.options = options;
23775        this.format = [{ tags: dom.inlineElements }];
23776    },
23777
23778    update: function(ui) {
23779        ui.data("kendoColorPicker").close();
23780    },
23781
23782    command: function (commandArguments) {
23783        var options = this.options,
23784            format = this.format,
23785            style = {};
23786
23787        return new Editor.FormatCommand(extend(commandArguments, {
23788            formatter: function () {
23789                style[options.domAttr] = commandArguments.value;
23790
23791                return new GreedyInlineFormatter(format, { style: style }, options.cssAttr);
23792            }
23793        }));
23794    },
23795
23796    willDelayExecution: inlineFormatWillDelayExecution,
23797
23798    initialize: function(ui, initOptions) {
23799        var editor = initOptions.editor,
23800            toolName = this.name;
23801
23802        new Editor.ColorPicker(ui, {
23803            value: "#000000",
23804            change: function (e) {
23805                Tool.exec(editor, toolName, e.value);
23806            }
23807        });
23808    }
23809});
23810
23811var StyleTool = Tool.extend({
23812    init: function(options) {
23813        var that = this;
23814        Tool.fn.init.call(that, options);
23815
23816        that.format = [{ tags: ["span"] }];
23817        that.finder = new GreedyInlineFormatFinder(that.format, "className");
23818    },
23819
23820    command: function (commandArguments) {
23821        return new Editor.FormatCommand(extend(commandArguments, {
23822            formatter: function () {
23823                return new GreedyInlineFormatter(this.format, { className: commandArguments.value });
23824            }
23825        }));
23826    },
23827
23828    update: function(ui, nodes) {
23829        var list = ui.data("kendoDropDownList");
23830        list.close();
23831        list.value(this.finder.getFormat(nodes));
23832    },
23833
23834    initiliaze: function(ui, initOptions) {
23835        var editor = initOptions.editor;
23836
23837        ui.kendoDropDownList({
23838            data: editor.style,
23839            title: editor.options.localization.style,
23840            itemCreate: function (e) {
23841                var style = dom.inlineStyle(editor.document, "span", {className : e.dataItem.Value});
23842
23843                e.html = '<span unselectable="on" style="display:block;' + style +'">' + e.html + '</span>';
23844            },
23845            change: function (e) {
23846                Tool.exec(editor, "style", e.value);
23847            }
23848        });
23849    }
23850
23851});
23852
23853extend(Editor, {
23854    InlineFormatFinder: InlineFormatFinder,
23855    InlineFormatter: InlineFormatter,
23856    GreedyInlineFormatFinder: GreedyInlineFormatFinder,
23857    GreedyInlineFormatter: GreedyInlineFormatter,
23858    InlineFormatTool: InlineFormatTool,
23859    FontTool: FontTool,
23860    ColorTool: ColorTool,
23861    StyleTool: StyleTool
23862});
23863
23864registerTool("style", new Editor.StyleTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Indent", initialValue: "Styles"})}));
23865
23866registerFormat("bold", [ { tags: ["strong"] }, { tags: ["span"], attr: { style: { fontWeight: "bold"}} } ]);
23867registerTool("bold", new InlineFormatTool({ key: "B", ctrl: true, format: formats.bold, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Bold"}) }));
23868
23869registerFormat("italic", [ { tags: ["em"] }, { tags: ["span"], attr: { style: { fontStyle: "italic"}} } ]);
23870registerTool("italic", new InlineFormatTool({ key: "I", ctrl: true, format: formats.italic, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Italic"})}));
23871
23872registerFormat("underline", [ { tags: ["span"], attr: { style: { textDecoration: "underline"}} } ]);
23873registerTool("underline", new InlineFormatTool({ key: "U", ctrl: true, format: formats.underline, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Underline"})}));
23874
23875registerFormat("strikethrough", [ { tags: ["del"] }, { tags: ["span"], attr: { style: { textDecoration: "line-through"}} } ]);
23876registerTool("strikethrough", new InlineFormatTool({format: formats.strikethrough, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Strikethrough"})}));
23877
23878registerFormat("superscript", [ { tags: ["sup"] } ]);
23879registerTool("superscript", new InlineFormatTool({format: formats.superscript, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Superscript"})}));
23880
23881registerFormat("subscript", [ { tags: ["sub"] } ]);
23882registerTool("subscript", new InlineFormatTool({format: formats.subscript, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Subscript"})}));
23883
23884registerTool("foreColor", new ColorTool({cssAttr:"color", domAttr:"color", name:"foreColor", template: new ToolTemplate({template: EditorUtils.colorPickerTemplate, title: "Color"})}));
23885
23886registerTool("backColor", new ColorTool({cssAttr:"background-color", domAttr: "backgroundColor", name:"backColor", template: new ToolTemplate({template: EditorUtils.colorPickerTemplate, title: "Background Color"})}));
23887
23888registerTool("fontName", new FontTool({cssAttr:"font-family", domAttr: "fontFamily", name:"fontName", template: new ToolTemplate({template: EditorUtils.comboBoxTemplate, title: "Font Name", initialValue: "(inherited font)"})}));
23889
23890registerTool("fontSize", new FontTool({cssAttr:"font-size", domAttr:"fontSize", name:"fontSize", template: new ToolTemplate({template: EditorUtils.comboBoxTemplate, title: "Font Size", initialValue: "(inherited size)"})}));
23891
23892})(jQuery);
23893(function($) {
23894
23895var kendo = window.kendo,
23896    Class = kendo.Class,
23897    extend = $.extend,
23898    Editor = kendo.ui.editor,
23899    formats = kendo.ui.Editor.fn.options.formats,
23900    dom = Editor.Dom,
23901    Command = Editor.Command,
23902    Tool = Editor.Tool,
23903    ToolTemplate = Editor.ToolTemplate,
23904    FormatTool = Editor.FormatTool,
23905    EditorUtils = Editor.EditorUtils,
23906    registerTool = EditorUtils.registerTool,
23907    registerFormat = EditorUtils.registerFormat,
23908    RangeUtils = Editor.RangeUtils;
23909
23910var BlockFormatFinder = Class.extend({
23911    init: function(format) {
23912        this.format = format;
23913    },
23914
23915    contains: function(node, children) {
23916        var i, len, child;
23917
23918        for (i = 0, len = children.length; i < len; i++) {
23919            child = children[i];
23920            if (!child || !dom.isAncestorOrSelf(node, child)) {
23921                return false;
23922            }
23923        }
23924
23925        return true;
23926    },
23927
23928    findSuitable: function (nodes) {
23929        var format = this.format,
23930            suitable = [],
23931            i, len, candidate;
23932
23933        for (i = 0, len = nodes.length; i < len; i++) {
23934            candidate = dom.ofType(nodes[i], format[0].tags) ? nodes[i] : dom.parentOfType(nodes[i], format[0].tags);
23935            if (!candidate) {
23936                return [];
23937            }
23938
23939            if ($.inArray(candidate, suitable) < 0) {
23940                suitable.push(candidate);
23941            }
23942        }
23943
23944        for (i = 0, len = suitable.length; i < len; i++) {
23945            if (this.contains(suitable[i], suitable)) {
23946                return [suitable[i]];
23947            }
23948        }
23949
23950        return suitable;
23951    },
23952
23953    findFormat: function (sourceNode) {
23954        var format = this.format,
23955            i, len, node, tags, attributes;
23956
23957        for (i = 0, len = format.length; i < len; i++) {
23958            node = sourceNode;
23959            tags = format[i].tags;
23960            attributes = format[i].attr;
23961
23962            while (node) {
23963                if (dom.ofType(node, tags) && dom.attrEquals(node, attributes)) {
23964                    return node;
23965                }
23966
23967                node = node.parentNode;
23968            }
23969        }
23970        return null;
23971    },
23972
23973    getFormat: function (nodes) {
23974        var that = this,
23975            findFormat = function(node) {
23976                    return that.findFormat(dom.isDataNode(node) ? node.parentNode : node);
23977                },
23978            result = findFormat(nodes[0]),
23979            i, len;
23980
23981        if (!result) {
23982            return "";
23983        }
23984
23985        for (i = 1, len = nodes.length; i < len; i++) {
23986            if (result != findFormat(nodes[i])) {
23987                return "";
23988            }
23989        }
23990
23991        return result.nodeName.toLowerCase();
23992    },
23993
23994    isFormatted: function (nodes) {
23995        for (var i = 0, len = nodes.length; i < len; i++) {
23996            if (!this.findFormat(nodes[i])) {
23997                return false;
23998            }
23999        }
24000
24001        return true;
24002    }
24003});
24004
24005var BlockFormatter = Class.extend({
24006    init: function (format, values) {
24007        this.format = format;
24008        this.values = values;
24009        this.finder = new BlockFormatFinder(format);
24010    },
24011
24012    wrap: function(tag, attributes, nodes) {
24013        var commonAncestor = nodes.length == 1 ? dom.blockParentOrBody(nodes[0]) : dom.commonAncestor.apply(null, nodes);
24014
24015        if (dom.isInline(commonAncestor)) {
24016            commonAncestor = dom.blockParentOrBody(commonAncestor);
24017        }
24018
24019        var ancestors = dom.significantChildNodes(commonAncestor),
24020            position = dom.findNodeIndex(ancestors[0]),
24021            wrapper = dom.create(commonAncestor.ownerDocument, tag, attributes),
24022            i, ancestor;
24023
24024        for (i = 0; i < ancestors.length; i++) {
24025            ancestor = ancestors[i];
24026            if (dom.isBlock(ancestor)) {
24027                dom.attr(ancestor, attributes);
24028
24029                if (wrapper.childNodes.length) {
24030                    dom.insertBefore(wrapper, ancestor);
24031                    wrapper = wrapper.cloneNode(false);
24032                }
24033
24034                position = dom.findNodeIndex(ancestor) + 1;
24035
24036                continue;
24037            }
24038
24039            wrapper.appendChild(ancestor);
24040        }
24041
24042        if (wrapper.firstChild) {
24043            dom.insertAt(commonAncestor, wrapper, position);
24044        }
24045    },
24046
24047    apply: function (nodes) {
24048        var that = this,
24049            formatNodes = dom.is(nodes[0], "img") ? [nodes[0]] : that.finder.findSuitable(nodes),
24050            formatToApply = formatNodes.length ? EditorUtils.formatByName(dom.name(formatNodes[0]), that.format) : that.format[0],
24051            tag = formatToApply.tags[0],
24052            attributes = extend({}, formatToApply.attr, that.values),
24053            i, len;
24054
24055        if (formatNodes.length) {
24056            for (i = 0, len = formatNodes.length; i < len; i++) {
24057                dom.attr(formatNodes[i], attributes);
24058            }
24059        } else {
24060            that.wrap(tag, attributes, nodes);
24061        }
24062    },
24063
24064    remove: function (nodes) {
24065        var i, l, formatNode, namedFormat;
24066
24067        for (i = 0, l = nodes.length; i < l; i++) {
24068            formatNode = this.finder.findFormat(nodes[i]);
24069            if (formatNode) {
24070                if (dom.ofType(formatNode, ["p", "img", "li"])) {
24071                    namedFormat = EditorUtils.formatByName(dom.name(formatNode), this.format);
24072                    if (namedFormat.attr.style) {
24073                        dom.unstyle(formatNode, namedFormat.attr.style);
24074                    }
24075                    if (namedFormat.attr.className) {
24076                        dom.removeClass(formatNode, namedFormat.attr.className);
24077                    }
24078                } else {
24079                    dom.unwrap(formatNode);
24080                }
24081            }
24082        }
24083    },
24084
24085    toggle: function (range) {
24086        var that = this,
24087            nodes = RangeUtils.nodes(range);
24088
24089        if (that.finder.isFormatted(nodes)) {
24090            that.remove(nodes);
24091        } else {
24092            that.apply(nodes);
24093        }
24094    }
24095});
24096
24097var GreedyBlockFormatter = Class.extend({
24098    init: function (format, values) {
24099        var that = this;
24100        that.format = format;
24101        that.values = values;
24102        that.finder = new BlockFormatFinder(format);
24103    },
24104
24105    apply: function (nodes) {
24106        var format = this.format,
24107            blocks = dom.blockParents(nodes),
24108            formatTag = format[0].tags[0],
24109            i, len, list, formatter, range;
24110
24111        if (blocks.length) {
24112            for (i = 0, len = blocks.length; i < len; i++) {
24113                if (dom.is(blocks[i], "li")) {
24114                    list = blocks[i].parentNode;
24115                    formatter = new Editor.ListFormatter(list.nodeName.toLowerCase(), formatTag);
24116                    range = this.editor.createRange();
24117                    range.selectNode(blocks[i]);
24118                    formatter.toggle(range);
24119                } else {
24120                    dom.changeTag(blocks[i], formatTag);
24121                }
24122            }
24123        } else {
24124            new BlockFormatter(format, this.values).apply(nodes);
24125        }
24126    },
24127
24128    toggle: function (range) {
24129        var nodes = RangeUtils.textNodes(range);
24130        if (!nodes.length) {
24131            range.selectNodeContents(range.commonAncestorContainer);
24132            nodes = RangeUtils.textNodes(range);
24133            if (!nodes.length) {
24134                nodes = dom.significantChildNodes(range.commonAncestorContainer);
24135            }
24136        }
24137
24138        this.apply(nodes);
24139    }
24140});
24141
24142var FormatCommand = Command.extend({
24143    init: function (options) {
24144        options.formatter = options.formatter();
24145        Command.fn.init.call(this, options);
24146    }
24147});
24148
24149var BlockFormatTool = FormatTool.extend({
24150    init: function (options) {
24151        FormatTool.fn.init.call(this, extend(options, {
24152            finder: new BlockFormatFinder(options.format),
24153            formatter: function () {
24154                return new BlockFormatter(options.format);
24155            }
24156        }));
24157    }
24158});
24159
24160var FormatBlockTool = Tool.extend({
24161    init: function (options) {
24162        Tool.fn.init.call(this, options);
24163        this.finder = new BlockFormatFinder([{ tags: dom.blockElements }]);
24164    },
24165
24166    command: function (commandArguments) {
24167        return new FormatCommand(extend(commandArguments, {
24168            formatter: function () { return new GreedyBlockFormatter([{ tags: [commandArguments.value] }], {}); }
24169        }));
24170    },
24171
24172    update: function(ui, nodes) {
24173        var list;
24174        if (ui.is("select")) {
24175            list = ui.data("kendoSelectBox");
24176        } else {
24177            list = ui.find("select").data("kendoSelectBox");
24178        }
24179        list.close();
24180        list.value(this.finder.getFormat(nodes));
24181    },
24182
24183    initialize: function(ui, initOptions) {
24184        var editor = initOptions.editor,
24185            toolName = "formatBlock";
24186
24187        new Editor.SelectBox(ui, {
24188            dataTextField: "Text",
24189            dataValueField: "Value",
24190            dataSource: editor.options.formatBlock,
24191            title: editor.options.localization.formatBlock,
24192            change: function (e) {
24193                Tool.exec(editor, toolName, this.value());
24194            },
24195            highlightFirst: false
24196        });
24197
24198        ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");
24199    }
24200});
24201
24202extend(Editor, {
24203    BlockFormatFinder: BlockFormatFinder,
24204    BlockFormatter: BlockFormatter,
24205    GreedyBlockFormatter: GreedyBlockFormatter,
24206    FormatCommand: FormatCommand,
24207    BlockFormatTool: BlockFormatTool,
24208    FormatBlockTool: FormatBlockTool
24209});
24210
24211registerTool("formatBlock", new FormatBlockTool({template: new ToolTemplate({template: EditorUtils.dropDownListTemplate, title: "Format Block", initialValue: "Select Block Type"})}));
24212
24213registerFormat("justifyLeft", [ { tags: dom.blockElements, attr: { style: { textAlign: "left"}} }, { tags: ["img"], attr: { style: { "float": "left"}} } ]);
24214registerTool("justifyLeft", new BlockFormatTool({format: formats.justifyLeft, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Left"})}));
24215
24216registerFormat("justifyCenter", [ { tags: dom.blockElements, attr: { style: { textAlign: "center"}} }, { tags: ["img"], attr: { style: { display: "block", marginLeft: "auto", marginRight: "auto"}} } ]);
24217registerTool("justifyCenter", new BlockFormatTool({format: formats.justifyCenter, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Center"})}));
24218
24219registerFormat("justifyRight", [ { tags: dom.blockElements, attr: { style: { textAlign: "right"}} }, { tags: ["img"], attr: { style: { "float": "right"}} } ]);
24220registerTool("justifyRight", new BlockFormatTool({format: formats.justifyRight, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Right"})}));
24221
24222registerFormat("justifyFull", [ { tags: dom.blockElements, attr: { style: { textAlign: "justify"}} } ]);
24223registerTool("justifyFull", new BlockFormatTool({format: formats.justifyFull, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Justify Full"})}));
24224
24225})(jQuery);
24226(function($) {
24227
24228// Imports ================================================================
24229var kendo = window.kendo,
24230    extend = $.extend,
24231    Editor = kendo.ui.editor,
24232    dom = Editor.Dom,
24233    Command = Editor.Command,
24234    Tool = Editor.Tool,
24235    BlockFormatter = Editor.BlockFormatter,
24236    normalize = dom.normalize,
24237    RangeUtils = Editor.RangeUtils,
24238    registerTool = Editor.EditorUtils.registerTool;
24239
24240var ParagraphCommand = Command.extend({
24241    init: function(options) {
24242        this.options = options;
24243        Command.fn.init.call(this, options);
24244    },
24245
24246    exec: function () {
24247        var range = this.getRange(),
24248            doc = RangeUtils.documentFromRange(range),
24249            parent, previous, next,
24250            container,
24251            emptyParagraphContent = $.browser.msie ? '' : '<br _moz_dirty="" />',
24252            paragraph, marker, li, heading, rng,
24253            // necessary while the emptyParagraphContent is empty under IE
24254            blocks = 'p,h1,h2,h3,h4,h5,h6'.split(','),
24255            startInBlock = dom.parentOfType(range.startContainer, blocks),
24256            endInBlock = dom.parentOfType(range.endContainer, blocks),
24257            shouldTrim = (startInBlock && !endInBlock) || (!startInBlock && endInBlock);
24258
24259        function clean(node) {
24260            if (node.firstChild && dom.is(node.firstChild, 'br')) {
24261                dom.remove(node.firstChild);
24262            }
24263
24264            if (dom.isDataNode(node) && !node.nodeValue) {
24265                node = node.parentNode;
24266            }
24267
24268            if (node && !dom.is(node, 'img')) {
24269                while (node.firstChild && node.firstChild.nodeType == 1) {
24270                    node = node.firstChild;
24271                }
24272
24273                if (!node.innerHTML) {
24274                    node.innerHTML = emptyParagraphContent;
24275                }
24276            }
24277        }
24278
24279        range.deleteContents();
24280
24281        marker = dom.create(doc, 'a');
24282        range.insertNode(marker);
24283
24284        if (!marker.parentNode) {
24285            // inserting paragraph in Firefox full body range
24286            container = range.commonAncestorContainer;
24287            container.innerHTML = "";
24288            container.appendChild(marker);
24289        }
24290
24291        normalize(marker.parentNode);
24292
24293        li = dom.parentOfType(marker, ['li']);
24294        heading = dom.parentOfType(marker, 'h1,h2,h3,h4,h5,h6'.split(','));
24295
24296        if (li) {
24297            rng = range.cloneRange();
24298            rng.selectNode(li);
24299
24300            // hitting 'enter' in empty li
24301            if (!RangeUtils.textNodes(rng).length) {
24302                paragraph = dom.create(doc, 'p');
24303
24304                if (li.nextSibling) {
24305                    RangeUtils.split(rng, li.parentNode);
24306                }
24307
24308                dom.insertAfter(paragraph, li.parentNode);
24309                dom.remove(li.parentNode.childNodes.length == 1 ? li.parentNode : li);
24310                paragraph.innerHTML = emptyParagraphContent;
24311                next = paragraph;
24312            }
24313        } else if (heading && !marker.nextSibling) {
24314            paragraph = dom.create(doc, 'p');
24315
24316            dom.insertAfter(paragraph, heading);
24317            paragraph.innerHTML = emptyParagraphContent;
24318            dom.remove(marker);
24319            next = paragraph;
24320        }
24321
24322        if (!next) {
24323            if (!(li || heading)) {
24324                new BlockFormatter([{ tags: ['p']}]).apply([marker]);
24325            }
24326
24327            range.selectNode(marker);
24328
24329            parent = dom.parentOfType(marker, [li ? 'li' : heading ? dom.name(heading) : 'p']);
24330
24331            RangeUtils.split(range, parent, shouldTrim);
24332
24333            previous = parent.previousSibling;
24334
24335            if (dom.is(previous, 'li') && previous.firstChild && !dom.is(previous.firstChild, 'br')) {
24336                previous = previous.firstChild;
24337            }
24338
24339            next = parent.nextSibling;
24340
24341            if (dom.is(next, 'li') && next.firstChild && !dom.is(next.firstChild, 'br')) {
24342                next = next.firstChild;
24343            }
24344
24345            dom.remove(parent);
24346
24347            clean(previous);
24348            clean(next);
24349
24350            // normalize updates the caret display in Gecko
24351            normalize(previous);
24352        }
24353
24354        normalize(next);
24355
24356        if (dom.is(next, 'img')) {
24357            range.setStartBefore(next);
24358        } else {
24359            range.selectNodeContents(next);
24360
24361            var textNode = RangeUtils.textNodes(range)[0];
24362
24363            if (textNode) {
24364                range.selectNodeContents(textNode);
24365            }
24366        }
24367
24368        range.collapse(true);
24369
24370        dom.scrollTo(next);
24371
24372        RangeUtils.selectRange(range);
24373    }
24374
24375});
24376
24377var NewLineCommand = Command.extend({
24378    init: function(options) {
24379        this.options = options;
24380        Command.fn.init.call(this, options);
24381    },
24382
24383    exec: function () {
24384        var range = this.getRange();
24385        range.deleteContents();
24386        var br = dom.create(RangeUtils.documentFromRange(range), 'br');
24387        range.insertNode(br);
24388        normalize(br.parentNode);
24389
24390        if (!$.browser.msie && (!br.nextSibling || dom.isWhitespace(br.nextSibling))) {
24391            //Gecko and WebKit cannot put the caret after only one br.
24392            var filler = br.cloneNode(true);
24393            filler.setAttribute('_moz_dirty', '');
24394            dom.insertAfter(filler, br);
24395        }
24396        range.setStartAfter(br);
24397        range.collapse(true);
24398        RangeUtils.selectRange(range);
24399    }
24400});
24401
24402extend(Editor, {
24403    ParagraphCommand: ParagraphCommand,
24404    NewLineCommand: NewLineCommand
24405});
24406
24407registerTool("insertLineBreak", new Tool({ key: 13, shift: true, command: NewLineCommand }));
24408registerTool("insertParagraph", new Tool({ key: 13, command: ParagraphCommand }));
24409
24410})(jQuery);
24411(function($) {
24412
24413// Imports ================================================================
24414var kendo = window.kendo,
24415    Class = kendo.Class,
24416    extend = $.extend,
24417    Editor = kendo.ui.editor,
24418    dom = Editor.Dom,
24419    RangeUtils = Editor.RangeUtils,
24420    EditorUtils = Editor.EditorUtils,
24421    Command = Editor.Command,
24422    ToolTemplate = Editor.ToolTemplate,
24423    FormatTool = Editor.FormatTool,
24424    BlockFormatFinder = Editor.BlockFormatFinder,
24425    textNodes = RangeUtils.textNodes,
24426    registerTool = Editor.EditorUtils.registerTool;
24427
24428var ListFormatFinder = BlockFormatFinder.extend({
24429    init: function(tag) {
24430        this.tag = tag;
24431        var tags = this.tags = [tag == 'ul' ? 'ol' : 'ul', tag];
24432
24433        BlockFormatFinder.fn.init.call(this, [{ tags: tags}]);
24434    },
24435
24436    isFormatted: function (nodes) {
24437        var formatNodes = [], formatNode;
24438
24439        for (var i = 0; i < nodes.length; i++) {
24440            if ((formatNode = this.findFormat(nodes[i])) && dom.name(formatNode) == this.tag) {
24441                formatNodes.push(formatNode);
24442            }
24443        }
24444
24445        if (formatNodes.length < 1) {
24446            return false;
24447        }
24448
24449        if (formatNodes.length != nodes.length) {
24450            return false;
24451        }
24452
24453        // check if sequential lists are selected
24454        for (i = 0; i < formatNodes.length; i++) {
24455            if (formatNodes[i].parentNode != formatNode.parentNode) {
24456                break;
24457            }
24458
24459            if (formatNodes[i] != formatNode) {
24460                return false;
24461            }
24462        }
24463
24464        return true;
24465    },
24466
24467    findSuitable: function (nodes) {
24468        var candidate = dom.parentOfType(nodes[0], this.tags);
24469
24470        if (candidate && dom.name(candidate) == this.tag) {
24471            return candidate;
24472        }
24473
24474        return null;
24475    }
24476
24477});
24478
24479var ListFormatter = Class.extend({
24480    init: function(tag, unwrapTag) {
24481        var that = this;
24482        that.finder = new ListFormatFinder(tag);
24483        that.tag = tag;
24484        that.unwrapTag = unwrapTag;
24485    },
24486
24487    wrap: function(list, nodes) {
24488        var li = dom.create(list.ownerDocument, "li"),
24489            i, node;
24490
24491        for (i = 0; i < nodes.length; i++) {
24492            node = nodes[i];
24493
24494            if (dom.is(node, 'li')) {
24495                list.appendChild(node);
24496                continue;
24497            }
24498
24499            if (dom.is(node, 'ul') || dom.is(node, 'ol')) {
24500                while (node.firstChild) {
24501                    list.appendChild(node.firstChild);
24502                }
24503                continue;
24504            }
24505
24506            if (dom.is(node, "td")) {
24507                while (node.firstChild) {
24508                    li.appendChild(node.firstChild);
24509                }
24510                list.appendChild(li);
24511                node.appendChild(list);
24512                list = list.cloneNode(false);
24513                li = li.cloneNode(false);
24514                continue;
24515            }
24516
24517            li.appendChild(node);
24518
24519            if (dom.isBlock(node)) {
24520                list.appendChild(li);
24521                dom.unwrap(node);
24522                li = li.cloneNode(false);
24523            }
24524        }
24525
24526        if (li.firstChild) {
24527            list.appendChild(li);
24528        }
24529    },
24530
24531    containsAny: function(parent, nodes) {
24532        for (var i = 0; i < nodes.length; i++) {
24533            if (dom.isAncestorOrSelf(parent, nodes[i])) {
24534                return true;
24535            }
24536        }
24537
24538        return false;
24539    },
24540
24541    suitable: function (candidate, nodes) {
24542        if (candidate.className == "k-marker") {
24543            var sibling = candidate.nextSibling;
24544
24545            if (sibling && dom.isBlock(sibling)) {
24546                return false;
24547            }
24548
24549            sibling = candidate.previousSibling;
24550
24551            if (sibling && dom.isBlock(sibling)) {
24552                return false;
24553            }
24554        }
24555
24556        return this.containsAny(candidate, nodes) || dom.isInline(candidate) || candidate.nodeType == 3;
24557    },
24558
24559    split: function (range) {
24560        var nodes = textNodes(range),
24561            start, end;
24562
24563        if (nodes.length) {
24564            start = dom.parentOfType(nodes[0], ['li']);
24565            end = dom.parentOfType(nodes[nodes.length - 1], ['li']);
24566            range.setStartBefore(start);
24567            range.setEndAfter(end);
24568
24569            for (var i = 0, l = nodes.length; i < l; i++) {
24570                var formatNode = this.finder.findFormat(nodes[i]);
24571                if (formatNode) {
24572                    var parents = $(formatNode).parents("ul,ol");
24573                    if (parents[0]) {
24574                        RangeUtils.split(range, parents.last()[0], true);
24575                    } else {
24576                        RangeUtils.split(range, formatNode, true);
24577                    }
24578                }
24579            }
24580        }
24581    },
24582
24583    merge: function(tag, formatNode) {
24584        var prev = formatNode.previousSibling, next;
24585
24586        while (prev && (prev.className == "k-marker" || (prev.nodeType == 3 && dom.isWhitespace(prev)))) {
24587            prev = prev.previousSibling;
24588        }
24589
24590        // merge with previous list
24591        if (prev && dom.name(prev) == tag) {
24592            while(formatNode.firstChild) {
24593                prev.appendChild(formatNode.firstChild);
24594            }
24595            dom.remove(formatNode);
24596            formatNode = prev;
24597        }
24598
24599        next = formatNode.nextSibling;
24600        while (next && (next.className == "k-marker" || (next.nodeType == 3 && dom.isWhitespace(next)))) {
24601            next = next.nextSibling;
24602        }
24603
24604        // merge with next list
24605        if (next && dom.name(next) == tag) {
24606            while(formatNode.lastChild) {
24607                next.insertBefore(formatNode.lastChild, next.firstChild);
24608            }
24609            dom.remove(formatNode);
24610        }
24611    },
24612
24613    applyOnSection: function (section, nodes) {
24614        var tag = this.tag,
24615            commonAncestor;
24616
24617        if (nodes.length == 1) {
24618            commonAncestor = dom.parentOfType(nodes[0], ["ul","ol"]);
24619        } else {
24620            commonAncestor = dom.commonAncestor.apply(null, nodes);
24621        }
24622
24623        if (!commonAncestor) {
24624            commonAncestor = dom.parentOfType(nodes[0], ["td"]) || nodes[0].ownerDocument.body;
24625        }
24626
24627        if (dom.isInline(commonAncestor)) {
24628            commonAncestor = dom.blockParentOrBody(commonAncestor);
24629        }
24630
24631        var ancestors = [];
24632
24633        var formatNode = this.finder.findSuitable(nodes);
24634
24635        if (!formatNode) {
24636            formatNode = new ListFormatFinder(tag == "ul" ? "ol" : "ul").findSuitable(nodes);
24637        }
24638
24639        var childNodes = dom.significantChildNodes(commonAncestor);
24640
24641        if (!childNodes.length) {
24642            childNodes = nodes;
24643        }
24644
24645        if (/table|tbody/.test(dom.name(commonAncestor))) {
24646            childNodes = $.map(nodes, function(node) {
24647                return dom.parentOfType(node, ["td"]);
24648            });
24649        }
24650
24651        function pushAncestor() {
24652            ancestors.push(this);
24653        }
24654
24655        for (var i = 0; i < childNodes.length; i++) {
24656            var child = childNodes[i];
24657            var nodeName = dom.name(child);
24658            if (this.suitable(child, nodes) && (!formatNode || !dom.isAncestorOrSelf(formatNode, child))) {
24659
24660                if (formatNode && (nodeName == "ul" || nodeName == "ol")) {
24661                    // merging lists
24662                    //Array.prototype.push.apply(ancestors, $.toArray(child.childNodes));
24663                    $.each(child.childNodes, pushAncestor);
24664                    dom.remove(child);
24665                } else {
24666                    ancestors.push(child);
24667                }
24668            }
24669        }
24670
24671        if (ancestors.length == childNodes.length && commonAncestor != nodes[0].ownerDocument.body && !/table|tbody|tr|td/.test(dom.name(commonAncestor))) {
24672            ancestors = [commonAncestor];
24673        }
24674
24675        if (!formatNode) {
24676            formatNode = dom.create(commonAncestor.ownerDocument, tag);
24677            dom.insertBefore(formatNode, ancestors[0]);
24678        }
24679
24680        this.wrap(formatNode, ancestors);
24681
24682        if (!dom.is(formatNode, tag)) {
24683            dom.changeTag(formatNode, tag);
24684        }
24685
24686        this.merge(tag, formatNode);
24687    },
24688
24689    apply: function (nodes) {
24690        var i = 0,
24691            sections = [],
24692            lastSection,
24693            lastNodes,
24694            section;
24695
24696        // split nodes into sections that need to be different lists
24697        do {
24698            section = dom.parentOfType(nodes[i], ["td","body"]);
24699
24700            if (!lastSection || section != lastSection) {
24701                if (lastSection) {
24702                    sections.push({
24703                        section: lastSection,
24704                        nodes: lastNodes
24705                    });
24706                }
24707
24708                lastNodes = [nodes[i]];
24709                lastSection = section;
24710            } else {
24711                lastNodes.push(nodes[i]);
24712            }
24713
24714            i++;
24715        } while (i < nodes.length);
24716
24717        sections.push({
24718            section: lastSection,
24719            nodes: lastNodes
24720        });
24721
24722        for (i = 0; i < sections.length; i++) {
24723            this.applyOnSection(sections[i].section, sections[i].nodes);
24724        }
24725    },
24726
24727    unwrap: function(ul) {
24728        var fragment = ul.ownerDocument.createDocumentFragment(),
24729            unwrapTag = this.unwrapTag,
24730            parents,
24731            li,
24732            p,
24733            child;
24734
24735        for (li = ul.firstChild; li; li = li.nextSibling) {
24736            p = dom.create(ul.ownerDocument, unwrapTag || 'p');
24737
24738            while(li.firstChild) {
24739                child = li.firstChild;
24740
24741                if (dom.isBlock(child)) {
24742
24743                    if (p.firstChild) {
24744                        fragment.appendChild(p);
24745                        p = dom.create(ul.ownerDocument, unwrapTag || 'p');
24746                    }
24747
24748                    fragment.appendChild(child);
24749                } else {
24750                    p.appendChild(child);
24751                }
24752            }
24753
24754            if (p.firstChild) {
24755                fragment.appendChild(p);
24756            }
24757        }
24758
24759        parents = $(ul).parents('ul,ol');
24760
24761        if (parents[0]) {
24762            dom.insertAfter(fragment, parents.last()[0]);
24763            parents.last().remove();
24764        } else {
24765            dom.insertAfter(fragment, ul);
24766        }
24767
24768        dom.remove(ul);
24769    },
24770
24771    remove: function (nodes) {
24772        var formatNode;
24773        for (var i = 0, l = nodes.length; i < l; i++) {
24774            formatNode = this.finder.findFormat(nodes[i]);
24775
24776            if (formatNode) {
24777                this.unwrap(formatNode);
24778            }
24779        }
24780    },
24781
24782    toggle: function (range) {
24783        var that = this,
24784            nodes = textNodes(range),
24785            ancestor = range.commonAncestorContainer;
24786
24787        if (!nodes.length) {
24788            range.selectNodeContents(ancestor);
24789            nodes = textNodes(range);
24790            if (!nodes.length) {
24791                var text = ancestor.ownerDocument.createTextNode("");
24792                range.startContainer.appendChild(text);
24793                nodes = [text];
24794                range.selectNode(text.parentNode);
24795            }
24796        }
24797
24798        if (that.finder.isFormatted(nodes)) {
24799            that.split(range);
24800            that.remove(nodes);
24801        } else {
24802            that.apply(nodes);
24803        }
24804    }
24805
24806});
24807
24808var ListCommand = Command.extend({
24809    init: function(options) {
24810        options.formatter = new ListFormatter(options.tag);
24811        Command.fn.init.call(this, options);
24812    }
24813});
24814
24815var ListTool = FormatTool.extend({
24816    init: function(options) {
24817        this.options = options;
24818        FormatTool.fn.init.call(this, extend(options, {
24819            finder: new ListFormatFinder(options.tag)
24820        }));
24821    },
24822
24823    command: function (commandArguments) {
24824        return new ListCommand(extend(commandArguments, { tag: this.options.tag }));
24825    }
24826});
24827
24828extend(Editor, {
24829    ListFormatFinder: ListFormatFinder,
24830    ListFormatter: ListFormatter,
24831    ListCommand: ListCommand,
24832    ListTool: ListTool
24833});
24834
24835registerTool("insertUnorderedList", new ListTool({tag:'ul', template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Remove Link"})}));
24836registerTool("insertOrderedList", new ListTool({tag:'ol', template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Remove Link"})}));
24837
24838})(jQuery);
24839(function($, undefined) {
24840
24841var kendo = window.kendo,
24842    Class = kendo.Class,
24843    extend = $.extend,
24844    Editor = kendo.ui.editor,
24845    dom = Editor.Dom,
24846    RangeUtils = Editor.RangeUtils,
24847    EditorUtils = Editor.EditorUtils,
24848    Command = Editor.Command,
24849    Tool = Editor.Tool,
24850    ToolTemplate = Editor.ToolTemplate,
24851    InlineFormatter = Editor.InlineFormatter,
24852    InlineFormatFinder = Editor.InlineFormatFinder,
24853    textNodes = RangeUtils.textNodes,
24854    registerTool = Editor.EditorUtils.registerTool;
24855
24856var LinkFormatFinder = Class.extend({
24857    findSuitable: function (sourceNode) {
24858        return dom.parentOfType(sourceNode, ['a']);
24859    }
24860});
24861
24862var LinkFormatter = Class.extend({
24863    init: function() {
24864        this.finder = new LinkFormatFinder();
24865    },
24866
24867    apply: function (range, attributes) {
24868        var nodes = textNodes(range);
24869        if (attributes.innerHTML) {
24870            var markers = RangeUtils.getMarkers(range);
24871            var document = RangeUtils.documentFromRange(range);
24872            range.deleteContents();
24873            var a = dom.create(document, 'a', attributes);
24874            range.insertNode(a);
24875
24876            if (markers.length > 1) {
24877                dom.insertAfter(markers[markers.length - 1], a);
24878                dom.insertAfter(markers[1], a);
24879                dom[nodes.length > 0 ? 'insertBefore' : 'insertAfter'](markers[0], a);
24880            }
24881        } else {
24882            var formatter = new InlineFormatter([{ tags: ['a']}], attributes);
24883            formatter.finder = this.finder;
24884            formatter.apply(nodes);
24885        }
24886    }
24887});
24888
24889var UnlinkCommand = Command.extend({
24890    init: function(options) {
24891        options.formatter = /** @ignore */ {
24892            toggle : function(range) {
24893                new InlineFormatter([{ tags: ['a']}]).remove(textNodes(range));
24894            }
24895        };
24896        this.options = options;
24897        Command.fn.init.call(this, options);
24898    }
24899});
24900
24901var LinkCommand = Command.extend({
24902    init: function(options) {
24903        var cmd = this;
24904        cmd.options = options;
24905        Command.fn.init.call(cmd, options);
24906        cmd.attributes = null;
24907        cmd.async = true;
24908        cmd.formatter = new LinkFormatter();
24909    },
24910
24911    exec: function () {
24912        var range = this.getRange();
24913
24914        var collapsed = range.collapsed;
24915
24916        range = this.lockRange(true);
24917
24918        var nodes = textNodes(range);
24919
24920        var initialText = null;
24921
24922        var that = this;
24923
24924        function apply(e) {
24925            var href = $('#k-editor-link-url', dialog.element).val();
24926
24927            if (href && href != 'http://') {
24928                that.attributes = { href: href };
24929
24930                var title = $('#k-editor-link-title', dialog.element).val();
24931                if (title) {
24932                    that.attributes.title = title;
24933                }
24934
24935                var text = $('#k-editor-link-text', dialog.element).val();
24936                if (text !== initialText) {
24937                    that.attributes.innerHTML = text || href;
24938                }
24939
24940                var target = $('#k-editor-link-target', dialog.element).is(':checked');
24941                if (target) {
24942                    that.attributes.target = '_blank';
24943                }
24944
24945                that.formatter.apply(range, that.attributes);
24946            }
24947
24948            close(e);
24949
24950            if (that.change) {
24951                that.change();
24952            }
24953        }
24954
24955        function close(e) {
24956            e.preventDefault();
24957            dialog.destroy();
24958
24959            dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
24960
24961            that.releaseRange(range);
24962        }
24963
24964        var a = nodes.length ? that.formatter.finder.findSuitable(nodes[0]) : null;
24965
24966        var shouldShowText = nodes.length <= 1 || (nodes.length == 2 && collapsed);
24967
24968        var windowContent =
24969            '<div class="k-editor-dialog">' +
24970                '<ol>' +
24971                    '<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>' +
24972                    (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>' : '') +
24973                    '<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>' +
24974                    '<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>' +
24975                '</ol>' +
24976                '<div class="k-button-wrapper">' +
24977                    '<button class="k-dialog-insert k-button">Insert</button>' +
24978                    '&nbsp;or&nbsp;' +
24979                    '<a href="#" class="k-dialog-close k-link">Close</a>' +
24980                '</div>' +
24981            '</div>';
24982
24983        var dialog = $(windowContent).appendTo(document.body).kendoWindow($.extend({}, this.editor.options.dialogOptions, {
24984            title: "Insert link",
24985            close: close
24986        }))
24987            .hide()
24988            .find('.k-dialog-insert').click(apply).end()
24989            .find('.k-dialog-close').click(close).end()
24990            .find('.k-form-text-row input').keydown(function (e) {
24991                if (e.keyCode == 13) {
24992                    apply(e);
24993                } else if (e.keyCode == 27) {
24994                    close(e);
24995                }
24996            }).end()
24997            // IE < 8 returns absolute url if getAttribute is not used
24998            .find('#k-editor-link-url').val(a ? a.getAttribute('href', 2) : 'http://').end()
24999            .find('#k-editor-link-text').val(nodes.length > 0 ? (nodes.length == 1 ? nodes[0].nodeValue : nodes[0].nodeValue + nodes[1].nodeValue) : '').end()
25000            .find('#k-editor-link-title').val(a ? a.title : '').end()
25001            .find('#k-editor-link-target').attr('checked', a ? a.target == '_blank' : false).end()
25002            .show()
25003            .data('kendoWindow')
25004            .center();
25005
25006        if (shouldShowText && nodes.length > 0) {
25007            initialText = $('#k-editor-link-text', dialog.element).val();
25008        }
25009
25010        $('#k-editor-link-url', dialog.element).focus().select();
25011    },
25012
25013    redo: function () {
25014        var that = this,
25015            range = that.lockRange(true);
25016
25017        that.formatter.apply(range, that.attributes);
25018        that.releaseRange(range);
25019    }
25020
25021});
25022
25023var UnlinkTool = Tool.extend({
25024    init: function(options) {
25025        this.options = options;
25026        this.finder = new InlineFormatFinder([{tags:['a']}]);
25027
25028        Tool.fn.init.call(this, $.extend(options, {command:UnlinkCommand}));
25029    },
25030
25031    initialize: function($ui) {
25032        $ui.attr('unselectable', 'on')
25033           .addClass('k-state-disabled');
25034    },
25035
25036    update: function ($ui, nodes) {
25037        $ui.toggleClass('k-state-disabled', !this.finder.isFormatted(nodes))
25038            .removeClass('k-state-hover');
25039    }
25040});
25041
25042extend(kendo.ui.editor, {
25043    LinkFormatFinder: LinkFormatFinder,
25044    LinkFormatter: LinkFormatter,
25045    UnlinkCommand: UnlinkCommand,
25046    LinkCommand: LinkCommand,
25047    UnlinkTool: UnlinkTool
25048});
25049
25050registerTool("createLink", new Tool({ key: 'K', ctrl: true, command: LinkCommand, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Create Link"})}));
25051registerTool("unlink", new UnlinkTool({ key: 'K', ctrl: true, shift: true, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Remove Link"})}));
25052
25053})(jQuery);
25054(function($, undefined) {
25055
25056var kendo = window.kendo,
25057    extend = $.extend,
25058    Editor = kendo.ui.editor,
25059    EditorUtils = Editor.EditorUtils,
25060    dom = Editor.Dom,
25061    registerTool = EditorUtils.registerTool,
25062    ToolTemplate = Editor.ToolTemplate,
25063    RangeUtils = Editor.RangeUtils,
25064    Command = Editor.Command,
25065    keys = kendo.keys,
25066    INSERTIMAGE = "Insert Image",
25067    KEDITORIMAGEURL = "#k-editor-image-url",
25068    KEDITORIMAGETITLE = "#k-editor-image-title";
25069
25070var ImageCommand = Command.extend({
25071    init: function(options) {
25072        var that = this;
25073        Command.fn.init.call(that, options);
25074
25075        that.async = true;
25076        that.attributes = {};
25077    },
25078
25079    insertImage: function(img, range) {
25080        var attributes = this.attributes;
25081        if (attributes.src && attributes.src != "http://") {
25082            if (!img) {
25083                img = dom.create(RangeUtils.documentFromRange(range), "img", attributes);
25084                img.onload = img.onerror = function () {
25085                        img.removeAttribute("complete");
25086                        img.removeAttribute("width");
25087                        img.removeAttribute("height");
25088                    };
25089
25090                range.deleteContents();
25091                range.insertNode(img);
25092                range.setStartAfter(img);
25093                range.setEndAfter(img);
25094                RangeUtils.selectRange(range);
25095                return true;
25096            } else {
25097                dom.attr(img, attributes);
25098            }
25099        }
25100
25101        return false;
25102    },
25103
25104    redo: function () {
25105        var that = this,
25106            range = that.lockRange();
25107
25108        if (!that.insertImage(RangeUtils.image(range), range)) {
25109            that.releaseRange(range);
25110        }
25111    },
25112
25113    exec: function () {
25114        var that = this,
25115            range = that.lockRange(),
25116            applied = false,
25117            img = RangeUtils.image(range),
25118            windowContent, dialog;
25119
25120        function apply(e) {
25121            that.attributes = {
25122                src: $(KEDITORIMAGEURL, dialog.element).val(),
25123                alt: $(KEDITORIMAGETITLE, dialog.element).val()
25124            };
25125
25126            applied = that.insertImage(img, range);
25127
25128            close(e);
25129
25130            if (that.change) {
25131                that.change();
25132            }
25133        }
25134
25135        function close(e) {
25136            e.preventDefault();
25137            dialog.destroy();
25138
25139            dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
25140            if (!applied) {
25141                that.releaseRange(range);
25142            }
25143        }
25144
25145        function keyDown(e) {
25146            if (e.keyCode == keys.ENTER) {
25147                apply(e);
25148            } else if (e.keyCode == keys.ESC) {
25149                close(e);
25150            }
25151        }
25152
25153        //var fileBrowser = that.editor.fileBrowser;
25154        //var showBrowser = fileBrowser && fileBrowser.selectUrl !== undefined;
25155
25156        windowContent =
25157            '<div class="k-editor-dialog">' +
25158                '<ol>' +
25159                    '<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>' +
25160                    '<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>' +
25161                '</ol>' +
25162                '<div class="k-button-wrapper">' +
25163                    '<button class="k-dialog-insert k-button">Insert</button>' +
25164                    '&nbsp;or&nbsp;' +
25165                    '<a href="#" class="k-dialog-close k-link">Close</a>' +
25166                '</div>' +
25167            '</div>';
25168
25169        dialog = $(windowContent)
25170                .appendTo(document.body)
25171                .kendoWindow(extend({}, that.editor.options.dialogOptions, {
25172                    title: INSERTIMAGE,
25173                    close: close,
25174                    activate: function() {
25175                        //if (showBrowser) {
25176                            //new $t.imageBrowser(
25177                                //$(this).find(".k-image-browser"),
25178                                //extend(fileBrowser, {
25179                                    //apply: apply,
25180                                    //element: that.editor.element,
25181                                    //localization: that.editor.options.localization
25182                                //}));
25183                        //}
25184                    }
25185                }))
25186                .hide()
25187                .find(".k-dialog-insert").click(apply).end()
25188                .find(".k-dialog-close").click(close).end()
25189                .find(".k-form-text-row input").keydown(keyDown).end()
25190                //.toggleClass("k-imagebrowser", showBrowser)
25191                // IE < 8 returns absolute url if getAttribute is not used
25192                .find(KEDITORIMAGEURL).val(img ? img.getAttribute("src", 2) : "http://").end()
25193                .find(KEDITORIMAGETITLE).val(img ? img.alt : "").end()
25194                .show()
25195                .data("kendoWindow")
25196                .center();
25197
25198        $(KEDITORIMAGEURL, dialog.element).focus().select();
25199    }
25200
25201});
25202
25203kendo.ui.editor.ImageCommand = ImageCommand;
25204
25205registerTool("insertImage", new Editor.Tool({ command: ImageCommand, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: INSERTIMAGE}) }));
25206
25207})(jQuery);
25208(function($, undefined) {
25209
25210var kendo = window.kendo,
25211    Widget = kendo.ui.Widget,
25212    DropDownList = kendo.ui.DropDownList,
25213    Editor = kendo.ui.editor,
25214    dom = Editor.Dom,
25215    CHANGE = "change",
25216    KSTATESELECTED = "k-state-selected",
25217    SELECTEDCLASS = "." + KSTATESELECTED,
25218    SELECTEDCOLORCLASS = ".k-selected-color",
25219    UNSELECTABLE = "unselectable",
25220    BACKGROUNDCOLOR = "background-color",
25221    keys = kendo.keys,
25222    template = kendo.template(
25223'<div class="k-colorpicker-popup">' +
25224   '<ul class="k-reset">'+
25225        '# for(var i = 0; i < colors.length; i++) { #' +
25226            '<li class="k-item #= colors[i] == value ? "k-state-selected" : "" #">
25226' +
25227                '<div style="background-color:\\##= colors[i] #"></div>'+
25228            '</li>' +
25229        '# } #' +
25230   '</ul>' +
25231'</div>');
25232
25233var ColorPicker = Widget.extend({
25234    init: function(element, options) {
25235        var that = this;
25236
25237        Widget.fn.init.call(that, element, options);
25238
25239        element = that.element;
25240        options = that.options;
25241
25242        that._value = options.value;
25243
25244        that.popup = $(template({
25245                        colors: options.colors,
25246                        value: options.value.substring(1)
25247                     }))
25248                     .kendoPopup({
25249                        anchor: element,
25250                        toggleTarget: element.find(".k-icon")
25251                     })
25252                     .delegate(".k-item", "click", function(e) {
25253                         that.select($(e.currentTarget).find("div").css(BACKGROUNDCOLOR));
25254                     })
25255                     .find("*")
25256                     .attr(UNSELECTABLE, "on")
25257                     .end()
25258                     .data("kendoPopup");
25259
25260        element.attr("tabIndex", 0) // add the color picker to the tab order
25261               .keydown(function(e) {
25262                   that.keydown(e);
25263               })
25264               .focus(function () {
25265                   element.css("outline", "1px dotted #000");
25266               })
25267               .blur(function() {
25268                   element.css("outline", "");
25269               })
25270               .delegate(".k-tool-icon", "click", function() {
25271                   that.select();
25272               })
25273               .find("*")
25274               .attr(UNSELECTABLE, "on");
25275
25276        if (that._value) {
25277            element.find(SELECTEDCOLORCLASS).css(BACKGROUNDCOLOR, that._value);
25278        }
25279    },
25280
25281    options: {
25282        name: "ColorPicker",
25283        colors: "000000,7f7f7f,880015,ed1c24,ff7f27,fff200,22b14c,00a2e8,3f48cc,a349a4,ffffff,c3c3c3,b97a57,ffaec9,ffc90e,efe4b0,b5e61d,99d9ea,7092be,c8bfe7".split(","),
25284        value: null
25285    },
25286
25287    events: [
25288        CHANGE
25289    ],
25290
25291    select: function(color) {
25292        var that = this;
25293
25294        if (color) {
25295            color = dom.toHex(color);
25296            if (!that.trigger(CHANGE, { value: color })) {
25297                that.value(color);
25298                that.close();
25299            }
25300        } else {
25301            that.trigger(CHANGE, { value: that._value });
25302        }
25303    },
25304
25305    open: function() {
25306        this.popup.open();
25307    },
25308
25309    close: function() {
25310        this.popup.close();
25311    },
25312
25313    toggle: function() {
25314        this.popup.toggle();
25315    },
25316
25317    keydown: function(e) {
25318        var that = this,
25319            popup = that.popup.element,
25320            visible = that.popup.visible(),
25321            selected,
25322            next,
25323            prev,
25324            preventDefault = false,
25325            keyCode = e.keyCode;
25326
25327        if (keyCode == keys.DOWN) {
25328            if (!visible) {
25329                that.open();
25330            } else {
25331                selected = popup.find(SELECTEDCLASS);
25332
25333                if (selected[0]) {
25334                    next = selected.next();
25335                } else {
25336                    next = popup.find("li:first");
25337                }
25338
25339                if (next[0]) {
25340                    selected.removeClass(KSTATESELECTED);
25341                    next.addClass(KSTATESELECTED);
25342                }
25343            }
25344
25345            preventDefault = true;
25346        } else if (keyCode == keys.UP) {
25347            if (visible) {
25348                selected = popup.find(SELECTEDCLASS);
25349                prev = selected.prev();
25350
25351                if (prev[0]) {
25352                    selected.removeClass(KSTATESELECTED);
25353                    prev.addClass(KSTATESELECTED);
25354                }
25355            }
25356            preventDefault = true;
25357        } else if (keyCode == keys.TAB || keyCode == keys.RIGHT || keyCode == keys.LEFT) {
25358            that.close();
25359        } else if (keyCode == keys.ENTER) {
25360            popup.find(SELECTEDCLASS).click();
25361            preventDefault = true;
25362        }
25363
25364        if (preventDefault) {
25365            e.preventDefault();
25366        }
25367    },
25368
25369    value: function(value) {
25370        var that = this;
25371
25372        if (value === undefined) {
25373            return that._value;
25374        } else {
25375            value = dom.toHex(value);
25376
25377            that._value = value;
25378
25379            that.element.find(SELECTEDCOLORCLASS)
25380                        .css(BACKGROUNDCOLOR, value);
25381        }
25382    }
25383});
25384
25385var SelectBox = DropDownList.extend({
25386    init: function(element, options) {
25387        var that = this;
25388
25389        DropDownList.fn.init.call(that, element, options);
25390
25391        that.value(that.options.title);
25392    },
25393    options: {
25394        name: "SelectBox"
25395    },
25396    value: function(value) {
25397        var that = this,
25398            result = DropDownList.fn.value.call(that, value);
25399
25400        if (value === undefined) {
25401            return result;
25402        }
25403
25404        if (value !== DropDownList.fn.value.call(that)) {
25405           that.text(that.options.title);
25406           that._current.removeClass("k-state-selected");
25407           that.current(null);
25408           that._oldIndex = that.selectedIndex = -1;
25409        }
25410    }
25411});
25412
25413kendo.ui.editor.ColorPicker = ColorPicker;
25414kendo.ui.editor.SelectBox = SelectBox;
25415
25416})(jQuery);
25417(function($, undefined) {
25418
25419// Imports ================================================================
25420var kendo = window.kendo,
25421    Class = kendo.Class,
25422    extend = $.extend,
25423    Editor = kendo.ui.editor,
25424    dom = Editor.Dom,
25425    EditorUtils = Editor.EditorUtils,
25426    registerTool = EditorUtils.registerTool,
25427    Command = Editor.Command,
25428    Tool = Editor.Tool,
25429    ToolTemplate = Editor.ToolTemplate,
25430    RangeUtils = Editor.RangeUtils,
25431    blockElements = dom.blockElements,
25432    BlockFormatFinder = Editor.BlockFormatFinder,
25433    BlockFormatter = Editor.BlockFormatter;
25434
25435function indent(node, value) {
25436    var property = dom.name(node) != "td" ? "marginLeft" : "paddingLeft";
25437    if (value === undefined) {
25438        return node.style[property] || 0;
25439    } else {
25440        if (value > 0) {
25441            node.style[property] = value + "px";
25442        } else {
25443            node.style[property] = "";
25444
25445            if (!node.style.cssText) {
25446                node.removeAttribute("style");
25447            }
25448        }
25449    }
25450}
25451
25452var IndentFormatter = Class.extend({
25453    init: function() {
25454        this.finder = new BlockFormatFinder([{tags:dom.blockElements}]);
25455    },
25456
25457    apply: function (nodes) {
25458        var formatNodes = this.finder.findSuitable(nodes),
25459            targets = [],
25460            i, len, formatNode, parentList, sibling;
25461
25462        if (formatNodes.length) {
25463            for (i = 0, len = formatNodes.length; i < len; i++) {
25464                if (dom.is(formatNodes[i], "li")) {
25465                    if (!$(formatNodes[i]).index()) {
25466                        targets.push(formatNodes[i].parentNode);
25467                    } else if ($.inArray(formatNodes[i].parentNode, targets) < 0) {
25468                        targets.push(formatNodes[i]);
25469                    }
25470                } else {
25471                    targets.push(formatNodes[i]);
25472                }
25473            }
25474
25475            while (targets.length) {
25476                formatNode = targets.shift();
25477                if (dom.is(formatNode, "li")) {
25478                    parentList = formatNode.parentNode;
25479                    sibling = $(formatNode).prev("li");
25480                    var siblingList = sibling.find("ul,ol").last();
25481
25482                    var nestedList = $(formatNode).children("ul,ol")[0];
25483
25484                    if (nestedList && sibling[0]) {
25485                        if (siblingList[0]) {
25486                           siblingList.append(formatNode);
25487                           siblingList.append($(nestedList).children());
25488                           dom.remove(nestedList);
25489                        } else {
25490                            sibling.append(nestedList);
25491                            nestedList.insertBefore(formatNode, nestedList.firstChild);
25492                        }
25493                    } else {
25494                        nestedList = sibling.children("ul,ol")[0];
25495                        if (!nestedList) {
25496                            nestedList = dom.create(formatNode.ownerDocument, dom.name(parentList));
25497                            sibling.append(nestedList);
25498                        }
25499
25500                        while (formatNode && formatNode.parentNode == parentList) {
25501                            nestedList.appendChild(formatNode);
25502                            formatNode = targets.shift();
25503                        }
25504                    }
25505                } else {
25506                    var marginLeft = parseInt(indent(formatNode), 10) + 30;
25507                    indent(formatNode, marginLeft);
25508
25509                    for (var targetIndex = 0; targetIndex < targets.length; targetIndex++) {
25510                        if ($.contains(formatNode, targets[targetIndex])) {
25511                            targets.splice(targetIndex, 1);
25512                        }
25513                    }
25514                }
25515            }
25516        } else {
25517            var formatter = new BlockFormatter([{tags:"p"}], {style:{marginLeft:30}});
25518
25519            formatter.apply(nodes);
25520        }
25521    },
25522
25523    remove: function(nodes) {
25524        var formatNodes = this.finder.findSuitable(nodes),
25525            targetNode, i, len, list, listParent, siblings,
25526            formatNode, marginLeft;
25527
25528        for (i = 0, len = formatNodes.length; i < len; i++) {
25529            formatNode = $(formatNodes[i]);
25530
25531            if (formatNode.is("li")) {
25532                list = formatNode.parent();
25533                listParent = list.parent();
25534                // listParent will be ul or ol in case of invalid dom - <ul><li></li><ul><li></li></ul></ul>
25535                if (listParent.is("li,ul,ol") && !indent(list[0])) {
25536                    // skip already processed nodes
25537                    if (targetNode && $.contains(targetNode, listParent[0])) {
25538                        continue;
25539                    }
25540
25541                    siblings = formatNode.nextAll("li");
25542                    if (siblings.length) {
25543                        $(list[0].cloneNode(false)).appendTo(formatNode).append(siblings);
25544                    }
25545
25546                    if (listParent.is("li")) {
25547                        formatNode.insertAfter(listParent);
25548                    } else {
25549                        formatNode.appendTo(listParent);
25550                    }
25551
25552                    if (!list.children("li").length) {
25553                        list.remove();
25554                    }
25555
25556                    continue;
25557                } else {
25558                    if (targetNode == list[0]) {
25559                        // removing format on sibling LI elements
25560                        continue;
25561                    }
25562                    targetNode = list[0];
25563                }
25564            } else {
25565                targetNode = formatNodes[i];
25566            }
25567
25568            marginLeft = parseInt(indent(targetNode), 10) - 30;
25569            indent(targetNode, marginLeft);
25570        }
25571    }
25572
25573});
25574
25575var IndentCommand = Command.extend({
25576    init: function(options) {
25577        options.formatter = /** @ignore */ {
25578            toggle : function(range) {
25579                new IndentFormatter().apply(RangeUtils.nodes(range));
25580            }
25581        };
25582        Command.fn.init.call(this, options);
25583    }
25584});
25585
25586var OutdentCommand = Command.extend({
25587    init: function(options) {
25588        options.formatter = {
25589            toggle : function(range) {
25590                new IndentFormatter().remove(RangeUtils.nodes(range));
25591            }
25592        };
25593        Command.fn.init.call(this, options);
25594    }
25595});
25596
25597var OutdentTool = Tool.extend({
25598    init: function(options) {
25599        Tool.fn.init.call(this, extend(options, {command:OutdentCommand}));
25600
25601        this.finder = new BlockFormatFinder([{tags:blockElements}]);
25602    },
25603
25604    initialize: function(ui) {
25605        ui.attr("unselectable", "on")
25606           .addClass("k-state-disabled");
25607    },
25608
25609    update: function (ui, nodes) {
25610        var suitable = this.finder.findSuitable(nodes),
25611            isOutdentable, listParentsCount, i, len;
25612
25613        for (i = 0, len = suitable.length; i < len; i++) {
25614            isOutdentable = indent(suitable[i]);
25615
25616            if (!isOutdentable) {
25617                listParentsCount = $(suitable[i]).parents("ul,ol").length;
25618                isOutdentable = (dom.is(suitable[i], "li") && (listParentsCount > 1 || indent(suitable[i].parentNode))) ||
25619                                (dom.ofType(suitable[i], ["ul","ol"]) && listParentsCount > 0);
25620            }
25621
25622            if (isOutdentable) {
25623                ui.removeClass("k-state-disabled");
25624                return;
25625            }
25626        }
25627
25628        ui.addClass("k-state-disabled").removeClass("k-state-hover");
25629    }
25630});
25631
25632extend(Editor, {
25633    IndentFormatter: IndentFormatter,
25634    IndentCommand: IndentCommand,
25635    OutdentCommand: OutdentCommand,
25636    OutdentTool: OutdentTool
25637});
25638
25639registerTool("indent", new Tool({ command: IndentCommand, template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Indent"}) }));
25640registerTool("outdent", new OutdentTool({template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Outdent"})}));
25641
25642})(jQuery);
25643(function($) {
25644
25645// Imports ================================================================
25646var kendo = window.kendo,
25647    Class = kendo.Class,
25648    extend = $.extend,
25649    Editor = kendo.ui.editor,
25650    RangeUtils = Editor.RangeUtils,
25651    Marker = Editor.Marker;
25652
25653var PendingFormats = Class.extend({
25654    init: function(editor) {
25655        this.editor = editor;
25656        this.formats = [];
25657    },
25658
25659    apply: function(range) {
25660        if (!this.hasPending()) {
25661            return;
25662        }
25663
25664        var marker = new Marker();
25665
25666        marker.addCaret(range);
25667
25668        var caret = range.startContainer.childNodes[range.startOffset];
25669
25670        var target = caret.previousSibling;
25671
25672        /* under IE, target is a zero-length text node. go figure. */
25673        if (!target.nodeValue) {
25674            target = target.previousSibling;
25675        }
25676
25677        range.setStart(target, target.nodeValue.length - 1);
25678
25679        marker.add(range);
25680
25681        if (!RangeUtils.textNodes(range).length) {
25682            marker.remove(range);
25683            range.collapse(true);
25684            this.editor.selectRange(range);
25685            return;
25686        }
25687
25688        var textNode = marker.end.previousSibling.previousSibling;
25689
25690        var pendingFormat,
25691            formats = this.formats;
25692
25693        for (var i = 0; i < formats.length; i++) {
25694            pendingFormat = formats[i];
25695
25696            var command = pendingFormat.command(extend({ range: range }, pendingFormat.options.params));
25697            command.editor = this.editor;
25698            command.exec();
25699
25700            range.selectNode(textNode);
25701        }
25702
25703        marker.remove(range);
25704
25705        if (textNode.parentNode) {
25706            range.setStart(textNode, 1);
25707            range.collapse(true);
25708        }
25709
25710        this.clear();
25711
25712        this.editor.selectRange(range);
25713    },
25714
25715    hasPending: function() {
25716        return this.formats.length > 0;
25717    },
25718
25719    isPending: function(format) {
25720        return !!this.getPending(format);
25721    },
25722
25723    getPending: function(format) {
25724        var formats = this.formats;
25725        for (var i = 0; i < formats.length; i++) {
25726            if (formats[i].name == format) {
25727                return formats[i];
25728            }
25729        }
25730
25731        return;
25732    },
25733
25734    toggle: function(format) {
25735        var formats = this.formats;
25736
25737        for (var i = 0; i < formats.length; i++) {
25738            if (formats[i].name == format.name) {
25739                if (formats[i].params && formats[i].params.value != format.params.value) {
25740                    formats[i].params.value = format.params.value;
25741                } else {
25742                    formats.splice(i, 1);
25743                }
25744
25745                return;
25746            }
25747        }
25748
25749        formats.push(format);
25750    },
25751
25752    clear: function() {
25753        this.formats = [];
25754    }
25755
25756});
25757
25758extend(Editor, {
25759    PendingFormats: PendingFormats
25760});
25761
25762})(jQuery);
25763(function($, undefined) {
25764    /**
25765     * @name kendo.ui.NumericTextBox.Description
25766     *
25767     * @section
25768     * <p>
25769     *    The NumericTextBox widget can convert an INPUT element into a numeric, percentage or currency textbox.
25770     *    The type is defined depending on the specified format. The widget renders spin buttons and with their help you can
25771     *    increment/decrement the value with a predefined step. The NumericTextBox widget accepts only numeric entries.
25772     *    The widget uses <em>kendo.culture.current</em> culture in order to determine number precision and other culture
25773     *    specific properties.
25774     * </p>
25775     *
25776     * <h3>Getting Started</h3>
25777     *
25778     * @exampleTitle Creating a NumericTextBox from existing INPUT element
25779     * @example
25780     * <input id="textBox" />
25781     *
25782     * @exampleTitle NumericTextBox initialization
25783     * @example
25784     * $(document).ready(function(){
25785     *  $("#textBox").kendoNumericTextBox();
25786     * });
25787     *
25788     * @section
25789     * <p>
25790     *  When a <b>NumericTextBox</b> is initialized, it will automatically
25791     *  wraps the input element with span element and will render spin
25792     *  buttons.
25793     * </p>
25794     * <h3>Configuring NumericTextBox behaviors</h3>
25795     * <p>
25796     *  The <b>NumericTextBox</b> provides configuration options that can be
25797     *  easily set during initialization. Among the properties that can be
25798     *  controlled:
25799     * </p>
25800     * <ul>
25801     *  <li>Value of the <b>NumericTextBox</b></li>
25802     *  <li>Minimum and/or maximum values</li>
25803     *  <li>Increment step</li>
25804     *  <li>Precision of the number</li>
25805     *  <li>Number format (any valid number format is allowed)</li>
25806     * </ul>
25807     * <p>
25808     *  To see a full list of available properties and values, review the
25809     *  Slider Configuration API documentation tab.
25810     * </p>
25811     * @exampleTitle Customizing NumericTextBox defaults
25812     * @example
25813     *  $("#textbox").kendoNumericTextBox({
25814     *      value: 10,
25815     *      min: -10,
25816     *      max: 100,
25817     *      step: 0.75,
25818     *      format: "n",
25819     *      decimals: 3
25820     *  });
25821     * @section
25822     * @exampleTitle Create Currency NumericTextBox widget
25823     * @example
25824     *  $("#textbox").kendoNumericTextBox({
25825     *      format: "c2" //Define currency type and 2 digits precision
25826     *  });
25827     * @section
25828     * @exampleTitle Create Percentage NumericTextBox widget
25829     * @example
25830     *  $("#textbox").kendoNumericTextBox({
25831     *      format: "p",
25832     *      value: 0.15 // 15 %
25833     *  });
25834     *
25835     * @section
25836     * <h3>Accessing an Existing NumericTextBox</h3>
25837     * <p>
25838     *  You can reference an existing <b>NumericTextBox</b> instance via
25839     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
25840     *  Once a reference has been established, you can use the API to control
25841     *  its behavior.
25842     * </p>
25843     *
25844     * @exampleTitle Accessing an existing NumericTextBox instance
25845     * @example
25846     * var numericTextBox = $("#numericTextBox").data("kendoNumericTextBox");
25847     *
25848     */
25849    var kendo = window.kendo,
25850        keys = kendo.keys,
25851        ui = kendo.ui,
25852        Widget = ui.Widget,
25853        parse = kendo.parseFloat,
25854        touch = kendo.support.touch,
25855        CHANGE = "change",
25856        DISABLED = "disabled",
25857        INPUT = "k-input",
25858        SPIN = "spin",
25859        TOUCHEND = "touchend",
25860        MOUSEDOWN = touch ? "touchstart" : "mousedown",
25861        MOUSEUP = touch ? "touchmove " + TOUCHEND : "mouseup mouseleave",
25862        DEFAULT = "k-state-default",
25863        FOCUSED = "k-state-focused",
25864        HOVER = "k-state-hover",
25865        HOVEREVENTS = "mouseenter mouseleave",
25866        POINT = ".",
25867        SELECTED = "k-state-selected",
25868        STATEDISABLED = "k-state-disabled",
25869        TYPE = touch ? "number" : "text",
25870        NULL = null,
25871        proxy = $.proxy,
25872        decimals = {
25873            190 : ".",
25874            188 : ","
25875        };
25876
25877    var NumericTextBox = Widget.extend(/** @lends kendo.ui.NumericTextBox.prototype */{
25878        /**
25879         * @constructs
25880         * @extends kendo.ui.Widget
25881         * @param {DomElement} element DOM element
25882         * @param {Object} options Configuration options
25883         * @option {Number} [value] <null> Specifies the value of the NumericTextBox widget.
25884         * _example
25885         *  // specify in the HTML
25886         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
25887         * <br />
25888         * // specify on widget initialization
25889         * $("#numeric").kendoNumericTextBox({
25890         *     min: 0,
25891         *     max: 100,
25892         *     value: 50
25893         * });
25894         * @option {Number} [min] <null> Specifies the smallest value the user can enter.
25895         * _example
25896         *  // specify in the HTML
25897         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
25898         * <br />
25899         * // specify on widget initialization
25900         * $("#numeric").kendoNumericTextBox({
25901         *     min: 0,
25902         *     max: 100,
25903         *     value: 50
25904         * });
25905         * @option {Number} [max] <null> Specifies the largest value the user can enter.
25906         * _example
25907         *  // specify in the HTML
25908         * &lt;input id="numeric" value="10" type="number" min="-100" max="100" step="10"/&gt;
25909         * <br />
25910         * // specify on widget initialization
25911         * $("#numeric").kendoNumericTextBox({
25912         *     min: 0,
25913         *     max: 100,
25914         *     value: 50
25915         * });
25916         * @option {Number} [decimals] <null> Specifies the number precision. If not set precision defined by current culture is used.
25917         * _example
25918         * // specify on widget initialization
25919         * $("#numeric").kendoNumericTextBox({
25920         *     min: 0,
25921         *     max: 1,
25922         *     step: 0.1,
25923         *     decimals: 1
25924         * });
25925         * @option {Number} [step] <1> Specifies the increment/decrement step.
25926         * _example
25927         *  // specify in the HTML
25928         * &lt;input id="numeric" value="10" type="number" /&gt;
25929         * <br />
25930         * // specify on widget initialization
25931         * $("#numeric").kendoNumericTextBox({
25932         *     min: 0,
25933         *     max: 1,
25934         *     step: 0.1
25935         * });
25936         * @option {String} [format] <n> Specifies the format of the number. Any valid number format is allowed.
25937         * _example
25938         * $("#numeric").kendoNumericTextBox({
25939         *    format: "p0", // format as percentage with % sign
25940         *    min: 0,
25941         *    max: 1,
25942         *    step: 0.01
25943         * });
25944         * @option {String} [placeholder] <""> Specifies the text displayed when the input is empty.
25945         * _example
25946         * // specify on widget initialization
25947         * $("#numeric").kendoNumericTextBox({
25948         *     min: 0,
25949         *     max: 100,
25950         *     value: 50,
25951         *     placeholder: "Select A Value"
25952         * });
25953         * @option {String} [upArrowText] <Increase value> Specifies the text of the tooltip on the up arrow.
25954         * _example
25955         * // specify on widget initialization
25956         * $("#numeric").kendoNumericTextBox({
25957         *     min: 0,
25958         *     max: 100,
25959         *     value: 50,
25960         *     upArrowText: "More",
25961         *     downArrowText: "Less"
25962         * });
25963         * @option {String} [downArrowText] <Decrease value> Specifies the text of the tooltip on the down arrow.
25964         * _example
25965         * // specify on widget initialization
25966         * $("#numeric").kendoNumericTextBox({
25967         *     min: 0,
25968         *     max: 100,
25969         *     value: 50,
25970         *     upArrowText: "More",
25971         *     downArrowText: "Less"
25972         * });
25973         */
25974         init: function(element, options) {
25975             var that = this,
25976             isStep = options && options.step !== undefined,
25977             min, max, step, value, format;
25978
25979             Widget.fn.init.call(that, element, options);
25980
25981             options = that.options;
25982             element = that.element.addClass(INPUT)
25983                           .bind({
25984                               keydown: proxy(that._keydown, that),
25985                               paste: proxy(that._paste, that),
25986                               blur: proxy(that._focusout, that)
25987                           });
25988
25989             element.closest("form")
25990                    .bind("reset", function() {
25991                        setTimeout(function() {
25992                            that.value(element[0].value);
25993                        });
25994                    });
25995
25996             that._wrapper();
25997             that._arrows();
25998             that._input();
25999
26000             that._text.focus(proxy(that._click, that));
26001
26002             min = parse(element.attr("min"));
26003             max = parse(element.attr("max"));
26004             step = parse(element.attr("step"));
26005
26006             if (options.min === NULL && min !== NULL) {
26007                 options.min = min;
26008             }
26009
26010             if (options.max === NULL && max !== NULL) {
26011                 options.max = max;
26012             }
26013
26014             if (!isStep && step !== NULL) {
26015                 options.step = step;
26016             }
26017
26018             format = options.format;
26019             if (format.slice(0,3) === "{0:") {
26020                 options.format = format.slice(3, format.length - 1);
26021             }
26022
26023             value = options.value;
26024             that.value(value !== NULL ? value : element.val());
26025
26026             that.enable(!element.is('[disabled]'));
26027
26028             kendo.notify(that);
26029         },
26030
26031        options: {
26032            name: "NumericTextBox",
26033            decimals: NULL,
26034            min: NULL,
26035            max: NULL,
26036            value: NULL,
26037            step: 1,
26038            format: "n",
26039            placeholder: "",
26040            upArrowText: "Increase value",
26041            downArrowText: "Decrease value"
26042        },
26043        events: [
26044             /**
26045             * Fires when the value is changed
26046             * @name kendo.ui.NumericTextBox#change
26047             * @event
26048             * @param {Event} e
26049             * @example
26050             * $("#numeric").kendoNumericTextBox({
26051             *     change: function(e) {
26052             *         // handle event
26053             *     }
26054             * });
26055             * @exampleTitle To set after initialization
26056             * @example
26057             * // get a reference to the numeric textbox widget
26058             * var numeric = $("#numeric").data("kendoNumericTextBox");
26059             * // bind to the change event
26060             * numeric.bind("change", function(e) {
26061             *     // handle event
26062             * });
26063             */
26064            CHANGE,
26065             /**
26066             * Fires when the value is changed from the spin buttons
26067             * @name kendo.ui.NumericTextBox#spin
26068             * @event
26069             * @param {Event} e
26070             * @example
26071             * $("#numeric").kendoNumericTextBox({
26072             *     spin: function(e) {
26073             *         // handle event
26074             *     }
26075             * });
26076             * @exampleTitle To set after initialization
26077             * @example
26078             * // get a reference to the numeric textbox widget
26079             * var numeric = $("#numeric").data("kendoNumericTextBox");
26080             * // bind to the spin event
26081             * numeric.bind("spin", function(e) {
26082             *     // handle event
26083             * });
26084             */
26085            SPIN
26086        ],
26087        /**
26088        * Enable/Disable the numerictextbox widget.
26089        * @param {Boolean} enable The argument, which defines whether to enable/disable tha numerictextbox.
26090        * @example
26091        * // get a reference to the numeric textbox
26092        * var textbox = $("#textbox").data("kendoNumericTextBox");
26093        *
26094        * // disables the numerictextbox
26095        * numerictextbox.enable(false);
26096        *
26097        * // enables the numerictextbox
26098        * numerictextbox.enable(true);
26099        */
26100        enable: function(enable) {
26101            var that = this,
26102                text = that._text.add(that.element),
26103                wrapper = that._inputWrapper.unbind(HOVEREVENTS),
26104              
26104  upArrow = that._upArrow.unbind(MOUSEDOWN),
26105                downArrow = that._downArrow.unbind(MOUSEDOWN);
26106
26107            that._toggleText(true);
26108
26109            if (enable === false) {
26110                wrapper
26111                    .removeClass(DEFAULT)
26112                    .addClass(STATEDISABLED);
26113
26114                text.attr(DISABLED, DISABLED);
26115            } else {
26116                wrapper
26117                    .addClass(DEFAULT)
26118                    .removeClass(STATEDISABLED)
26119                    .bind(HOVEREVENTS, that._toggleHover);
26120
26121                text.removeAttr(DISABLED);
26122
26123                upArrow.bind(MOUSEDOWN, function(e) {
26124                    e.preventDefault();
26125                    that._spin(1);
26126                    that._upArrow.addClass(SELECTED);
26127                });
26128
26129                downArrow.bind(MOUSEDOWN, function(e) {
26130                    e.preventDefault();
26131                    that._spin(-1);
26132                    that._downArrow.addClass(SELECTED);
26133                });
26134            }
26135        },
26136
26137        /**
26138        * Gets/Sets the value of the numerictextbox.
26139        * @param {Number | String} value The value to set.
26140        * @returns {Number} The value of the numerictextbox.
26141        * @example
26142        * // get a referene to the numeric textbox
26143        * var numerictextbox = $("#textbox").data("kendoNumericTextBox");
26144        *
26145        * // get the value of the numerictextbox.
26146        * var value = numerictextbox.value();
26147        *
26148        * // set the value of the numerictextbox.
26149        * numerictextbox.value("10.20");
26150        */
26151        value: function(value) {
26152            var that = this, adjusted;
26153
26154            if (value === undefined) {
26155                return that._value;
26156            }
26157
26158            value = parse(value);
26159            adjusted = that._adjust(value);
26160
26161            if (value !== adjusted) {
26162                return;
26163            }
26164
26165            that._update(value);
26166            that._old = that._value;
26167        },
26168
26169        _adjust: function(value) {
26170            var that = this,
26171            options = that.options,
26172            min = options.min,
26173            max = options.max;
26174
26175            if (min !== NULL && value < min) {
26176                value = min;
26177            } else if (max !== NULL && value > max) {
26178                value = max;
26179            }
26180
26181            return value;
26182        },
26183
26184        _arrows: function() {
26185            var that = this,
26186            arrows,
26187            options = that.options,
26188            element = that.element;
26189
26190            arrows = element.siblings(".k-icon");
26191
26192            if (!arrows[0]) {
26193                arrows = $(buttonHtml("up", options.upArrowText) + buttonHtml("down", options.downArrowText))
26194                        .insertAfter(element);
26195
26196                arrows.wrapAll('<span class="k-select"/>');
26197            }
26198
26199            arrows.bind(MOUSEUP, function(e) {
26200                if (!touch || kendo.eventTarget(e) != e.currentTarget || e.type === TOUCHEND) {
26201                    clearTimeout( that._spinning );
26202                }
26203                arrows.removeClass(SELECTED);
26204            });
26205
26206            that._upArrow = arrows.eq(0);
26207            that._downArrow = arrows.eq(1);
26208        },
26209
26210        _blur: function() {
26211            var that = this;
26212
26213            that._toggleText(true);
26214            that._change(that.element.val());
26215        },
26216
26217        _click: function(e) {
26218            var that = this;
26219
26220            clearTimeout(that._focusing);
26221            that._focusing = setTimeout(function() {
26222                var input = e.target,
26223                    idx = caret(input),
26224                    value = input.value.substring(0, idx),
26225                    format = that._format(that.options.format),
26226                    group = format[","],
26227                    groupRegExp = new RegExp("\\" + group, "g"),
26228                    extractRegExp = new RegExp("([\\d\\" + group + "]+)(\\" + format[POINT] + ")?(\\d+)?"),
26229                    result = extractRegExp.exec(value),
26230                    caretPosition = 0;
26231
26232                if (result) {
26233                    caretPosition = result[0].replace(groupRegExp, "").length;
26234
26235                    if (value.indexOf("(") != -1 && that._value < 0) {
26236                        caretPosition++;
26237                    }
26238                }
26239
26240                that._focusin();
26241
26242                caret(that.element[0], caretPosition);
26243            });
26244        },
26245
26246        _change: function(value) {
26247            var that = this;
26248
26249            that._update(value);
26250            value = that._value;
26251
26252            if (that._old != value) {
26253                that._old = value;
26254                that.trigger(CHANGE);
26255
26256                // trigger the DOM change event so any subscriber gets notified
26257                that.element.trigger(CHANGE);
26258            }
26259        },
26260
26261        _focusin: function() {
26262            var that = this;
26263            that._toggleText(false);
26264            that.element.focus();
26265            that._inputWrapper.addClass(FOCUSED);
26266        },
26267
26268        _focusout: function() {
26269            var that = this;
26270
26271            clearTimeout(that._focusing);
26272            that._inputWrapper.removeClass(FOCUSED);
26273            that._blur();
26274        },
26275
26276        _format: function(format) {
26277            var numberFormat = kendo.culture().numberFormat;
26278
26279            if (format.toLowerCase().indexOf("c") > -1) {
26280                numberFormat = numberFormat.currency;
26281            } else if (format.toLowerCase().indexOf("p") > -1) {
26282                numberFormat = numberFormat.percent;
26283            }
26284
26285            return numberFormat;
26286        },
26287
26288        _input: function() {
26289            var that = this,
26290                CLASSNAME = "k-formatted-value",
26291                element = that.element.show()[0],
26292                wrapper = that.wrapper,
26293                text;
26294
26295
26296            text = wrapper.find(POINT + CLASSNAME);
26297
26298            if (!text[0]) {
26299                text = $("<input />").insertBefore(element).addClass(CLASSNAME);
26300            }
26301
26302            element.type = TYPE;
26303            text[0].type = "text";
26304
26305            text[0].style.cssText = element.style.cssText;
26306            that._text = text.attr("readonly", true).addClass(element.className);
26307        },
26308
26309        _keydown: function(e) {
26310            var that = this,
26311                key = e.keyCode;
26312
26313            if (key == keys.DOWN) {
26314                that._step(-1);
26315            } else if (key == keys.UP) {
26316                that._step(1);
26317            } else if (key == keys.ENTER) {
26318                that._change(that.element.val());
26319            }
26320
26321            if (that._prevent(key) && !e.ctrlKey) {
26322                e.preventDefault();
26323            }
26324        },
26325
26326        _paste: function(e) {
26327            var that = this,
26328                element = e.target,
26329                value = element.value;
26330
26331            setTimeout(function() {
26332                if (parse(element.value) === NULL) {
26333                    that._update(value);
26334                }
26335            });
26336        },
26337
26338        _prevent: function(key) {
26339            var that = this,
26340                element = that.element[0],
26341                value = element.value,
26342                options = that.options,
26343                min = options.min,
26344                numberFormat = that._format(options.format),
26345                separator = numberFormat[POINT],
26346                precision = options.decimals,
26347                idx = caret(element),
26348                prevent = true,
26349                end;
26350
26351            if (precision === NULL) {
26352                precision = numberFormat.decimals;
26353            }
26354
26355            if ((key > 16 && key < 21) ||
26356                (key > 32 && key < 37) ||
26357                (key > 47 && key < 58) ||
26358                (key > 95 && key < 106) ||
26359                 key == keys.INSERT ||
26360                 key == keys.DELETE ||
26361                 key == keys.LEFT ||
26362                 key == keys.RIGHT ||
26363                 key == keys.TAB ||
26364                 key == keys.BACKSPACE ||
26365                 key == keys.ENTER) {
26366                prevent = false;
26367            } else if (decimals[key] === separator && precision > 0 && value.indexOf(separator) == -1) {
26368                prevent = false;
26369            } else if ((min === NULL || min < 0) && value.indexOf("-") == -1 && (key == 189 || key == 109) && idx === 0) { //sign
26370                prevent = false;
26371            } else if (key == 110 && precision > 0 && value.indexOf(separator) == -1) {
26372                end = value.substring(idx);
26373
26374                element.value = value.substring(0, idx) + separator + end;
26375            }
26376
26377            return prevent;
26378        },
26379
26380        _spin: function(step, timeout) {
26381            var that = this;
26382
26383            timeout = timeout || 500;
26384
26385            clearTimeout( that._spinning );
26386            that._spinning = setTimeout(function() {
26387                that._spin(step, 50);
26388            }, timeout );
26389
26390            that._step(step);
26391        },
26392
26393        _step: function(step) {
26394            var that = this,
26395                element = that.element,
26396                value = parse(element.val()) || 0;
26397
26398            if (document.activeElement != element[0]) {
26399                that._focusin();
26400            }
26401
26402            value += that.options.step * parse(step);
26403
26404            that._update(that._adjust(value));
26405
26406            that.trigger(SPIN);
26407        },
26408
26409        _toggleHover: function(e) {
26410            if (!touch) {
26411                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
26412            }
26413        },
26414
26415        _toggleText: function(toggle) {
26416            var that = this;
26417
26418            toggle = !!toggle;
26419            that._text.toggle(toggle);
26420            that.element.toggle(!toggle);
26421        },
26422
26423        _update: function(value) {
26424            var that = this,
26425                options = that.options,
26426                format = options.format,
26427                decimals = options.decimals,
26428                numberFormat = that._format(format),
26429                isNotNull;
26430
26431            if (decimals === NULL) {
26432                decimals = numberFormat.decimals;
26433            }
26434
26435            value = parse(value);
26436
26437            isNotNull = value !== NULL;
26438
26439            if (isNotNull) {
26440                value = parseFloat(value.toFixed(decimals));
26441            }
26442
26443            that._value = value = that._adjust(value);
26444            that._text.val(isNotNull ? kendo.toString(value, format) : options.placeholder);
26445            that.element.val(isNotNull ? value.toString().replace(POINT, numberFormat[POINT]) : "");
26446        },
26447
26448        _wrapper: function() {
26449            var that = this,
26450                element = that.element,
26451                wrapper;
26452
26453            wrapper = element.parents(".k-numerictextbox");
26454
26455            if (!wrapper.is("span.k-numerictextbox")) {
26456                wrapper = element.hide().wrap('<span class="k-numeric-wrap k-state-default" />').parent();
26457                wrapper = wrapper.wrap("<span/>").parent();
26458            }
26459
26460            wrapper[0].style.cssText = element[0].style.cssText;
26461            element[0].style.width = "";
26462            that.wrapper = wrapper.addClass("k-widget k-numerictextbox").show();
26463            that._inputWrapper = $(wrapper[0].firstChild);
26464        }
26465    });
26466
26467    function buttonHtml(className, text) {
26468        return '<span unselectable="on" class="k-link"><span unselectable="on" class="k-icon k-arrow-' + className + '" title="' + text 
26468+ '">' + text + '</span></span>';
26469    }
26470
26471    function caret(element, position) {
26472        var range,
26473            isPosition = position !== undefined;
26474
26475        if (document.selection) {
26476            if ($(element).is(":visible")) {
26477                element.focus();
26478            }
26479            range = document.selection.createRange();
26480            if (isPosition) {
26481                range.move("character", position);
26482                range.select();
26483            } else {
26484                var rangeElement = element.createTextRange(),
26485                    rangeDuplicated = rangeElement.duplicate();
26486                    rangeElement.moveToBookmark(range.getBookmark());
26487                    rangeDuplicated.setEndPoint('EndToStart', rangeElement);
26488
26489                position = rangeDuplicated.text.length;
26490
26491            }
26492        } else if (element.selectionStart !== undefined) {
26493            if (isPosition) {
26494                element.focus();
26495                element.setSelectionRange(position, position);
26496            } else {
26497                position = element.selectionStart;
26498            }
26499        }
26500
26501        return position;
26502    }
26503
26504    ui.plugin(NumericTextBox);
26505})(jQuery);
26506(function ($, undefined) {
26507    /**
26508     * @name kendo.ui.Menu.Description
26509     *
26510     * @section
26511     * <p>
26512     *  The <b>Menu</b> displays hierarchical data as a multi-level menu. It provides rich styling for unordered lists
26513     *  of items, and can be used for both navigation and executing JavaScript commands. Items can be defined and
26514     *  initialized from HTML, or the API can be used to add and remove items.
26515     * </p>
26516     * <h3>Getting Started</h3>
26517     *
26518     * @exampleTitle Create a HTML hierarchical list of items
26519     * @example
26520     * <ul id="menu">
26521     *     <li>Item 1
26522     *         <ul>
26523     *             <li>Item 1.1</li>
26524     *             <li>Item 1.2</li>
26525     *         </ul>
26526     *     </li>
26527     *     <li>Item 2</li>
26528     * </ul>
26529     *
26530     * @section
26531     * <p>
26532     *  Initialization of a <strong>Menu</strong> should occur after the DOM is fully loaded. It is recommended that
26533     *  initialization the <strong>Menu</strong> occur within a handler is provided to $(document).ready().
26534     * </p>
26535     *
26536     * @exampleTitle Initialize a Menu using a selector within $(document).ready()
26537     * @example
26538     * $(document).ready(function() {
26539     *     $("#menu").kendoMenu();
26540     * });
26541     *
26542     * @exampleTitle Initialize the Menu using JSON data object
26543     * @example
26544     * $(document).ready(function() {
26545     *  $("#menu").kendoMenu({
26546     *   dataSource:
26547     *     [{
26548     *         text: "Item 1",
26549     *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
26550     *     },
26551     *     {
26552     *         text: "<b>Item 2</b>",
26553     *         encoded: false,                                 // Allows use of HTML for item text
26554     *         content: "text"                                 // content within an item
26555     *     },
26556     *     {
26557     *         text: "Item 3",
26558     *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
26559     *         items: [{                                    // Sub item collection
26560     *              text: "Sub Item 1"
26561     *         },
26562     *         {
26563     *              text: "Sub Item 2"
26564     *         }]
26565     *     },
26566     *     {
26567     *         text: "Item 4",
26568     *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
26569     *     }]
26570     *  })
26571     * });
26572     *
26573     * @section
26574     * <h3>Customizing Menu Animations</h3>
26575     * <p>
26576     *  By default, the <b>Menu</b> uses a slide animation to expand and
26577     *  reveal sub-items as the mouse hovers. Animations can be easily
26578     *  customized using configuration properties, changing the animation
26579     *  style and delay. Menu items can also be configured to open on click
26580     *  instead of on hover.
26581     * </p>
26582     *
26583     * @exampleTitle Changing Menu animation and open behavior
26584     * @example
26585     * $("#menu").kendoMenu({
26586     *  animation: {
26587     *   open: { effects: "fadeIn" },
26588     *   hoverDelay: 500
26589     *  },
26590     *  openOnClick: true
26591     * });
26592     *
26593     * @section
26594     * <h3>Dynamically configuring Menu items</h3>
26595     * <p>
26596     *  The <b>Menu</b> API provides several methods for dynamically adding
26597     *  or removing Items. To add items, provide the new item as a JSON
26598     *  object along with a reference item that will be used to determine the
26599     *  placement in the hierarchy.
26600     * </p>
26601     * <p>
26602     *  A reference item is simply a target Menu Item HTML element that
26603     *  already exists in the Menu. Any valid jQuery selector can be used to
26604     *  obtain a reference to the target item. For examples, see the
26605     *  <a href="../menu/api.html" title="Menu API demos">Menu API demos</a>.
26606     *  Removing an item only requires a reference to the target element that
26607     *  should be removed.
26608     * </p>
26609     *
26610     * @exampleTitle Dynamically add a new root Menu item
26611     * @example
26612     * var menu = $("#menu").kendoMenu().data("kendoMenu");
26613     * menu.insertAfter(
26614     *  { text: "New Menu Item" },
26615     *  menu.element.children("li:last")
26616     * );
26617     *
26618     * @section
26619     * <h3>Accessing an Existing Menu</h3>
26620     * <p>
26621     *  You can reference an existing <b>Menu</b> instance via
26622     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>.
26623     *  Once a reference has been established, you can use the API to control
26624     *  its behavior.
26625     * </p>
26626     *
26627     * @exampleTitle Accessing an existing Menu instance
26628     * @example
26629     * var menu = $("#menu").data("kendoMenu");
26630     *
26631     */
26632    var kendo = window.kendo,
26633        ui = kendo.ui,
26634        touch = kendo.support.touch,
26635        extend = $.extend,
26636        proxy = $.proxy,
26637        each = $.each,
26638        template = kendo.template,
26639        Widget = ui.Widget,
26640        excludedNodesRegExp = /^(ul|a|div)$/i,
26641        IMG = "img",
26642        OPEN = "open",
26643        MENU = "k-menu",
26644        LINK = "k-link",
26645        LAST = "k-last",
26646        CLOSE = "close",
26647        CLICK = touch ? "touchend" : "click",
26648        TIMER = "timer",
26649        FIRST = "k-first",
26650        IMAGE = "k-image",
26651        SELECT = "select",
26652        ZINDEX = "zIndex",
26653        MOUSEENTER = "mouseenter",
26654        MOUSELEAVE = "mouseleave",
26655        KENDOPOPUP = "kendoPopup",
26656        DEFAULTSTATE = "k-state-default",
26657        DISABLEDSTATE = "k-state-disabled",
26658        groupSelector = ".k-group",
26659        allItemsSelector = ".k-item",
26660        disabledSelector = ".k-item.k-state-disabled",
26661        itemSelector = ".k-item:not(.k-state-disabled)",
26662        linkSelector = ".k-item:not(.k-state-disabled) > .k-link",
26663
26664        templates = {
26665            content: template(
26666                "<div class='k-content k-group'>#= content(item) #</div>"
26667            ),
26668            group: template(
26669                "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
26670                    "#= renderItems(data) #" +
26671                "</ul>"
26672            ),
26673            itemWrapper: template(
26674                "<#= tag(item) # class='#= textClass(item) #'#= textAttributes(item) #>" +
26675                    "#= image(item) ##= sprite(item) ##= text(item) #" +
26676                    "#= arrow(data) #" +
26677                "</#= tag(item) #>"
26678            ),
26679            item: template(
26680                "<li class='#= wrapperCssClass(group, item) #'>" +
26681                    "#= itemWrapper(data) #" +
26682                    "# if (item.items) { #" +
26683                    "#= subGroup({ items: item.items, menu: menu, group: { expanded: item.expanded } }) #" +
26684                    "# } #" +
26685                "</li>"
26686            ),
26687            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
26688            arrow: template("<span class='#= arrowClass(item, group) #'></span>"),
26689            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
26690            empty: template("")
26691        },
26692
26693        rendering = {
26694            /** @ignore */
26695            wrapperCssClass: function (group, item) {
26696                var result = "k-item",
26697                    index = item.index;
26698
26699                if (item.enabled === false) {
26700                    result += " k-state-disabled";
26701                } else {
26702                    result += " k-state-default";
26703                }
26704
26705                if (group.firstLevel && index === 0) {
26706                    result += " k-first";
26707                }
26708
26709                if (index == group.length-1) {
26710                    result += " k-last";
26711                }
26712
26713                return result;
26714            },
26715            /** @ignore */
26716            textClass: function(item) {
26717                return LINK;
26718            },
26719            /** @ignore */
26720            textAttributes: function(item) {
26721                return item.url ? " href='" + item.url + "'" : "";
26722            },
26723            /** @ignore */
26724            arrowClass: function(item, group) {
26725                var result = "k-icon";
26726
26727                if (group.horizontal) {
26728                    result += " k-arrow-down";
26729                } else {
26730                    result += " k-arrow-right";
26731                }
26732
26733                return result;
26734            },
26735            /** @ignore */
26736            text: function(item) {
26737                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
26738            },
26739            /** @ignore */
26740            tag: function(item) {
26741                return item.url ? "a" : "span";
26742            },
26743            /** @ignore */
26744            groupAttributes: function(group) {
26745                return group.expanded !== true ? " style='display:none'" : "";
26746            },
26747            /** @ignore */
26748            groupCssClass: function(group) {
26749                return "k-group";
26750            },
26751            /** @ignore */
26752            content: function(item) {
26753                return item.content ? item.content : "&nbsp;";
26754            }
26755        };
26756
26757    function getEffectDirection(direction, root) {
26758        direction = direction.split(" ")[!root+0] || direction;
26759        return direction.replace("top", "up").replace("bottom", "down");
26760    }
26761
26762    function parseDirection(direction, root) {
26763        direction = direction.split(" ")[!root+0] || direction;
26764        var output = { origin: [ "bottom", "left" ], position: [ "top", "left" ] },
26765            horizontal = /left|right/.test(direction);
26766
26767        if (horizontal) {
26768            output.origin = [ "top", direction ];
26769            output.position[1] = kendo.directions[direction].reverse;
26770        } else {
26771            output.origin[0] = direction;
26772            output.position[0] = kendo.directions[direction].reverse;
26773        }
26774
26775        output.origin = output.origin.join(" ");
26776        output.position = output.position.join(" ");
26777
26778        return output;
26779    }
26780
26781    function contains(parent, child) {
26782        try {
26783            return $.contains(parent, child);
26784        } catch (e) {
26785            return false;
26786        }
26787    }
26788
26789    function updateItemClasses (item) {
26790        item = $(item);
26791
26792        item.addClass("k-item")
26793            .children(IMG)
26794            .addClass(IMAGE);
26795        item
26796            .children("a")
26797            .addClass(LINK)
26798            .children(IMG)
26799            .addClass(IMAGE);
26800        item
26801            .filter(":not([disabled])")
26802            .addClass(DEFAULTSTATE);
26803        item
26804            .filter(".k-separator:empty")
26805            .append("&nbsp;");
26806        item
26807            .filter("li[disabled]")
26808            .addClass(DISABLEDSTATE)
26809            .removeAttr("disabled");
26810        item
26811            .children("a:focus")
26812            .parent()
26813            .addClass("k-state-active");
26814
26815        if (!item.children("." + LINK).length) {
26816            item
26817                .contents()      // exclude groups, real links, templates and empty text nodes
26818                .filter(function() { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue))); })
26819                .wrapAll("<span class='" + LINK + "'/>");
26820        }
26821
26822        updateArrow(item);
26823        updateFirstLast(item);
26824    }
26825
26826    function updateArrow (item) {
26827        item = $(item);
26828
26829        item.find(".k-icon").remove();
26830
26831        item.filter(":has(.k-group)")
26832            .children(".k-link:not(:has([class*=k-arrow]))")
26833            .each(function () {
26834                var item = $(this),
26835                    parent = item.parent().parent();
26836
26837                item.append("<span class='k-icon " + (parent.hasClass(MENU + "-horizontal") ? "k-arrow-down" : "k-arrow-next") + "'/>");
26838            });
26839    }
26840
26841    function updateFirstLast (item) {
26842        item = $(item);
26843
26844        item.filter(".k-first:not(:first-child)").removeClass(FIRST);
26845        item.filter(".k-last:not(:last-child)").removeClass(LAST);
26846        item.filter(":first-child").addClass(FIRST);
26847        item.filter(":last-child").addClass(LAST);
26848    }
26849
26850    var Menu = Widget.extend({/** @lends kendo.ui.Menu.prototype */
26851        /**
26852         * Creates a Menu instance.
26853         * @constructs
26854         * @extends kendo.ui.Widget
26855         * @class Menu UI widget
26856         * @param {Selector} element DOM element
26857         * @param {Object} options Configuration options.
26858         * @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.
26859         * <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
26860         * 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>
26861         * <div class="details-list">
26862         * <dl>
26863         *     <dt><b>slideIn</b></dt>
26864         *     <dd>Menu content slides in from the top</dd>
26865         *     <dt><b>fadeIn</b></dt>
26866         *     <dd>Menu content fades in</dd>
26867         *     <dt><b>expand</b></dt>
26868         *     <dd>Menu content expands from the top down. Similar to slideIn.</dd>
26869         * </dl>
26870         * </div>
26871         * _example
26872         *  $("#menu").kendoMenu({
26873         *      animation: { open: { effects: "fadeIn" } }
26874         *  });
26875         * @option {Animation} [animation.open] The animation that will be used when opening sub menus.
26876         * @option {Animation} [animation.close] The animation that will be used when closing sub menus.
26877         * @option {String} [orientation] <"horizontal"> Root menu orientation. Could be horizontal or vertical.
26878         * _example
26879         *  $("#menu").kendoMenu({
26880         *      orientation: "vertical"
26881         *  });
26882         * @option {Boolean} [closeOnClick] <true> Specifies that sub menus should close after item selection (provided they won't navigate).
26883         * _example
26884         *  $("#menu").kendoMenu({
26885         *      closeOnClick: false
26886         *  });
26887         * @option {Boolean} [openOnClick] <false> Specifies that the root sub menus will be opened on item click.
26888         * _example
26889         *  $("#menu").kendoMenu({
26890         *      openOnClick: true
26891         *  });
26892         * @option {Number} [hoverDelay] <100> Specifies the delay in ms before the menu is opened/closed - used to avoid accidental closure on leaving.
26893         * _example
26894         *  $("#menu").kendoMenu({
26895         *      hoverDelay: 200
26896         *  });
26897         * @option {String} [direction] <"default"> Specifies Menu opening direction. Can be "top", "bottom", "left", "right".
26898         * 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
26899         * its sub menus to the left.
26900         * _example
26901         * $("#menu").kendoMenu({
26902         *     direction: "top left"
26903         * });
26904         * @option {String} [popupCollision] <> Specifies how Menu should adjust to screen boundaries. By default the strategy is <b>"fit"</b> for a sub menu with a horizontal parent,
26905         * meaning it will move to fit in screen boundaries in all directions, and <b>"fit flip"</b> for a sub menu with vertical parent, meaning it will fit vertically and flip over
26906         * its parent horizontally. You can also switch off the screen boundary detection completely if you set the <b>popupCollision</b> to false.
26907         * _example
26908         * $("#menu").kendoMenu({
26909         *     popupCollision: false
26910         * });
26911         */
26912        init: function(element, options) {
26913            var that = this;
26914
26915            Widget.fn.init.call(that, element, options);
26916
26917            element = that.wrapper = that.element;
26918            options = that.options;
26919
26920            if (options.dataSource) {
26921                that.element.empty();
26922                that.append(options.dataSource, element);
26923            }
26924
26925            that._updateClasses();
26926
26927            if (options.animation === false) {
26928                options.animation = { open: { show: true, effects: {} }, close: { hide: true, effects: {} } };
26929            }
26930
26931            that.nextItemZIndex = 100;
26932
26933            element.delegate(disabledSelector, CLICK, false)
26934                   .delegate(itemSelector, CLICK, proxy(that._click , that));
26935
26936            if (!touch) {
26937                element.delegate(itemSelector, MOUSEENTER, proxy(that._mouseenter, that))
26938                       .delegate(itemSelector, MOUSELEAVE, proxy(that._mouseleave, that))
26939                       .delegate(linkSelector, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover);
26940            } else {
26941                options.openOnClick = true;
26942                element.delegate(linkSelector, "touchstart touchend", that._toggleHover);
26943            }
26944
26945            $(document).click(proxy( that._documentClick, that ));
26946            that.clicked = false;
26947
26948            kendo.notify(that);
26949        },
26950
26951        events: [
26952            /**
26953            * Fires before a sub menu gets opened.
26954            * @name kendo.ui.Menu#open
26955            * @event
26956            * @param {Event} e
26957            * @param {Element} e.item The opened item
26958            * @example
26959            *  $("#menu").kendoMenu({
26960            *      open: function(e) {
26961            *          // handle event
26962            *      }
26963            *  });
26964            * @exampleTitle To set after initialization
26965            * @example
26966            *  // get a reference to the menu widget
26967            *  var menu = $("#menu").data("kendoMenu");
26968            *  // bind to the open event
26969            *  menu.bind("open", function(e) {
26970            *      // handle event
26971            *  });
26972            */
26973            OPEN,
26974            /**
26975            * Fires after a sub menu gets closed.
26976            * @name kendo.ui.Menu#close
26977            * @event
26978            * @param {Event} e
26979            * @param {Element} e.item The closed item
26980            * @example
26981            *  $("#menu").kendoMenu({
26982            *      close: function(e) {
26983            *          // handle event
26984            *      }
26985            *  });
26986            * @exampleTitle To set after initialization
26987            * @example
26988            *  // get a reference to the menu widget
26989            *  var menu = $("#menu").data("kendoMenu");
26990            *  // bind to the close event
26991            *  menu.bind("close", function(e) {
26992            *      // handle event
26993            *  });
26994            */
26995            CLOSE,
26996            /**
26997            * Fires when a menu item gets selected.
26998            * @name kendo.ui.Menu#select
26999            * @event
27000            * @param {Event} e
27001            * @param {Element} e.item The selected item
27002            * @example
27003            *  $("#menu").kendoMenu({
27004            *      select: function(e) {
27005            *          // handle event
27006            *      }
27007            *  });
27008            * @exampleTitle To set after initialization
27009            * @example
27010            *  // get a reference to the menu widget
27011            *  var menu = $("#menu").data("kendoMenu");
27012            *  // bind to the select event
27013            *  menu.bind("select", function(e) {
27014            *      // handle event
27015            *  });
27016            */
27017            SELECT
27018        ],
27019
27020        options: {
27021            name: "Menu",
27022            animation: {
27023                open: {
27024                    duration: 200,
27025                    show: true
27026                },
27027                close: { // if close animation effects are defined, they will be used instead of open.reverse
27028                    duration: 100
27029                }
27030            },
27031            orientation: "horizontal",
27032            direction: "default",
27033            openOnClick: false,
27034            closeOnClick: true,
27035            hoverDelay: 100
27036        },
27037
27038        /**
27039         *
27040         * Enables or disables an item of a <strong>Menu</strong>. This can optionally be accomplished on
27041         * initialization by setting the <b>disabled="disabled"</b> on the desired menu item html element.
27042         *
27043         * @param {Selector} element
27044         * Target element
27045         *
27046         * @param {Boolean} enable
27047         * Desired state
27048         *
27049         * @returns {Menu}
27050         * Returns the Menu object to support chaining.
27051         *
27052         * @example
27053         * // get a reference to the menu widget
27054         * var menu = $("#menu").data("kendoMenu");
27055         * // disable the li menu item with the id "secondItem"
27056         * menu.enable("#secondItem", false);
27057         */
27058        enable: function (element, enable) {
27059            this._toggleDisabled(element, enable !== false);
27060
27061            return this;
27062        },
27063
27064        disable: function (element) {
27065            this._toggleDisabled(element, false);
27066
27067            return this;
27068        },
27069
27070        /**
27071         *
27072         * Appends an item to a <strong>Menu</strong> in the specified referenceItem's sub menu.
27073         *
27074         * @param {Selector} item
27075         * Target item, specified as a JSON object. Can also handle an array of such objects.
27076         *
27077         * @param {Item} referenceItem
27078         * A reference item to append the new item in.
27079         *
27080         * @returns {Menu}
27081         * Returns the Menu object to support chaining.
27082         *
27083         * @example
27084         * // get a reference to the menu widget
27085         * var menu = $("#menu").data("kendoMenu");
27086         * //
27087         * menu.append(
27088         *     [{
27089         *         text: "Item 1",
27090         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
27091         *     },
27092         *     {
27093         *         text: "<b>Item 2</b>",
27094         *         encoded: false,                                 // Allows use of HTML for item text
27095         *         content: "text"                                 // content within an item
27096         *     },
27097         *     {
27098         *         text: "Item 3",
27099         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
27100         *         items: [{                                    // Sub item collection
27101         *              text: "Sub Item 1"
27102         *         },
27103         *         {
27104         *              text: "Sub Item 2"
27105         *         }]
27106         *     },
27107         *     {
27108         *         text: "Item 4",
27109         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
27110         *     }],
27111         *     referenceItem
27112         * );
27113         */
27114        append: function (item, referenceItem) {
27115            referenceItem = this.element.find(referenceItem);
27116
27117            var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find("> .k-group, .k-animation-container > .k-group") : null);
27118
27119            each(inserted.items, function (idx) {
27120                inserted.group.append(this);
27121
27122                var contents = inserted.contents[idx];
27123                if (contents) {
27124                    $(this).append(contents);
27125                }
27126
27127                updateArrow(this);
27128            });
27129
27130            updateArrow(referenceItem);
27131            updateFirstLast(inserted.group.find(".k-first, .k-last").add(inserted.items));
27132
27133            return this;
27134        },
27135
27136        /**
27137         *
27138         * Inserts an item into a <strong>Menu</strong> before the specified referenceItem.
27139         *
27140         * @param {Selector} item
27141         * Target item, specified as a JSON object. Can also handle an array of such objects.
27142         *
27143         * @param {Selector} referenceItem
27144         * A reference item to insert the new item before
27145         *
27146         * @returns {Menu}
27147         * Returns the Menu object to support chaining.
27148         *
27149         * @example
27150         * // get a reference to the menu widget
27151         * var menu = $("#menu").data("kendoMenu");
27152         * //
27153         * menu.insertBefore(
27154         *     [{
27155         *         text: "Item 1",
27156         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
27157         *     },
27158         *     {
27159         *         text: "<b>Item 2</b>",
27160         *         encoded: false,                                 // Allows use of HTML for item text
27161         *         content: "text"                                 // content within an item
27162         *     },
27163         *     {
27164         *         text: "Item 3",
27165         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
27166         *         items: [{                                    // Sub item collection
27167         *              text: "Sub Item 1"
27168         *         },
27169         *         {
27170         *              text: "Sub Item 2"
27171         *         }]
27172         *     },
27173         *     {
27174         *         text: "Item 4",
27175         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
27176         *     }],
27177         *     referenceItem
27178         * );
27179         */
27180        insertBefore: function (item, referenceItem) {
27181            referenceItem = this.element.find(referenceItem);
27182
27183            var inserted = this._insert(item, referenceItem, referenceItem.parent());
27184
27185            each(inserted.items, function (idx) {
27186                referenceItem.before(this);
27187
27188                var contents = inserted.contents[idx];
27189                if (contents) {
27190                    $(this).append(contents);
27191                }
27192
27193                updateArrow(this);
27194                updateFirstLast(this);
27195            });
27196
27197            updateFirstLast(referenceItem);
27198
27199            return this;
27200        },
27201
27202        /**
27203         *
27204         * Inserts an item into a <strong>Menu</strong> after the specified referenceItem.
27205         *
27206         * @param {Selector} item
27207         * Target item, specified as a JSON object. Can also handle an array of such objects.
27208         *
27209         * @param {Selector} referenceItem
27210         * A reference item to insert the new item after.
27211         *
27212         * @returns {Menu}
27213         * Returns the Menu object to support chaining.
27214         *
27215         * @example
27216         * // get a reference to the menu widget
27217         * var menu = $("#menu").data("kendoMenu");
27218         * //
27219         * menu.insertAfter(
27220         *     [{
27221         *         text: "Item 1",
27222         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
27223         *     },
27224         *     {
27225         *         text: "<b>Item 2</b>",
27226         *         encoded: false,                                 // Allows use of HTML for item text
27227         *         content: "text"                                 // content within an item
27228         *     },
27229         *     {
27230         *         text: "Item 3",
27231         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
27232         *         items: [{                                    // Sub item collection
27233         *              text: "Sub Item 1"
27234         *         },
27235         *         {
27236         *              text: "Sub Item 2"
27237         *         }]
27238         *     },
27239         *     {
27240         *         text: "Item 4",
27241         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
27242         *     }],
27243         *     referenceItem
27244         * );
27245         *
27246         */
27247        insertAfter: function (item, referenceItem) {
27248            referenceItem = this.element.find(referenceItem);
27249
27250            var inserted = this._insert(item, referenceItem, referenceItem.parent());
27251
27252            each(inserted.items, function (idx) {
27253                referenceItem.after(this);
27254
27255                var contents = inserted.contents[idx];
27256                if (contents) {
27257                    $(this).append(contents);
27258                }
27259
27260                updateArrow(this);
27261                updateFirstLast(this);
27262            });
27263
27264            updateFirstLast(referenceItem);
27265
27266            return this;
27267        },
27268
27269        _insert: function (item, referenceItem, parent) {
27270            var that = this,
27271                items, groups, contents = [];
27272
27273            if (!referenceItem || !referenceItem.length) {
27274                parent = that.element;
27275            }
27276
27277            var plain = $.isPlainObject(item),
27278                groupData = {
27279                    firstLevel: parent.hasClass(MENU),
27280                    horizontal: parent.hasClass(MENU + "-horizontal"),
27281                    expanded: true,
27282                    length: parent.children().length
27283                };
27284
27285            if (referenceItem && !parent.length) {
27286                parent = $(Menu.renderGroup({ group: groupData })).appendTo(referenceItem);
27287            }
27288
27289            if (plain || $.isArray(item)) { // is JSON
27290                items = $.map(plain ? [ item ] : item, function (value, idx) {
27291                            if (typeof value === "string") {
27292                                return $(value);
27293                            } else {
27294                                return $(Menu.renderItem({
27295                                    group: groupData,
27296                                    item: extend(value, { index: idx })
27297                                }));
27298                            }
27299                        });
27300                contents = $.map(plain ? [ item ] : item, function (value, idx) {
27301                            if (value.content || value.contentUrl) {
27302                                return $(Menu.renderContent({
27303                                    item: extend(value, { index: idx })
27304                                }));
27305                            } else {
27306                                return false;
27307                            }
27308                        });
27309            } else {
27310                items = $(item);
27311                groups = items.find("> ul")
27312                                .addClass("k-group");
27313                items = items.filter("li");
27314
27315                items.add(groups.find("> li")).each(function () {
27316                    updateItemClasses(this);
27317                });
27318            }
27319
27320            return { items: items, group: parent, contents: contents };
27321        },
27322
27323        /**
27324         *
27325         * Removes a specified item(s) from a <strong>Menu</strong>.
27326         *
27327         * @param {Selector} element
27328         * Target item selector.
27329         *
27330         * @returns {Menu}
27331         * Returns the Menu object to support chaining.
27332         *
27333         * @example
27334         * // get a reference to the menu widget
27335         * var menu = $("#menu").data("kendoMenu");
27336         * // remove the item with the id "Item1"
27337         * menu.remove("#Item1");
27338         *
27339         */
27340        remove: function (element) {
27341            element = this.element.find(element);
27342
27343            var that = this,
27344                parent = element.parentsUntil(that.element, allItemsSelector),
27345                group = element.parent("ul");
27346
27347            element.remove();
27348
27349            if (group && !group.children(allItemsSelector).length) {
27350                var container = group.parent(".k-animation-container");
27351                if (container.length) {
27352                    container.remove();
27353                } else {
27354                    group.remove();
27355                }
27356            }
27357
27358            if (parent.length) {
27359                parent = parent.eq(0);
27360
27361                updateArrow(parent);
27362                updateFirstLast(parent);
27363            }
27364
27365            return that;
27366        },
27367
27368        /**
27369         *
27370         * Opens a sub-menu of a specified item(s) in a <strong>Menu</strong>.
27371         *
27372         * @param {Selector} element
27373         * Target item selector.
27374         *
27375         * @returns {Menu}
27376         * Returns the Menu object to support chaining.
27377         *
27378         * @example
27379         * // get a reference to the menu widget
27380         * var menu = $("#menu").data("kendoMenu");
27381         * // open the sub menu of "Item1"
27382         * menu.open("#Item1");
27383         *
27384         */
27385        open: function (element) {
27386            var that = this,
27387                options = that.options,
27388                horizontal = options.orientation == "horizontal",
27389                direction = options.direction;
27390            element = that.element.find(element);
27391
27392            if (/^(top|bottom|default)$/.test(direction)) {
27393                direction = horizontal ? (direction + " right").replace("default", "bottom") : "right";
27394            }
27395
27396            element.each(function () {
27397                var li = $(this);
27398
27399                clearTimeout(li.data(TIMER));
27400
27401                li.data(TIMER, setTimeout(function () {
27402                    var ul = li.find(".k-group:first:hidden"), popup;
27403
27404                    if (ul[0] && that.trigger(OPEN, { item: li[0] }) === false) {
27405                        li.data(ZINDEX, li.css(ZINDEX));
27406                        li.css(ZINDEX, that.nextItemZIndex ++);
27407
27408                        popup = ul.data(KENDOPOPUP);
27409                        var root = li.parent().hasClass(MENU),
27410                            parentHorizontal = root && horizontal,
27411                            directions = parseDirection(direction, root),
27412                            effects = options.animation.open.effects,
27413                            openEffects = effects !== undefined ? effects : "slideIn:" + getEffectDirection(direction, root);
27414
27415                        if (!popup) {
27416                            popup = ul.kendoPopup({
27417                                origin: directions.origin,
27418                                position: directions.position,
27419                                collision: options.popupCollision !== undefined ? options.popupCollision : (parentHorizontal ? "fit" : "fit flip"),
27420                                anchor: li,
27421                                appendTo: li,
27422                                animation: {
27423                                    open: extend(true, { effects: openEffects }, options.animation.open),
27424                                    close: options.animation.close
27425                                }
27426                            }).data(KENDOPOPUP);
27427                        } else {
27428                            popup = ul.data(KENDOPOPUP);
27429                            popup.options.origin = directions.origin;
27430                            popup.options.position = directions.position;
27431                            popup.options.animation.open.effects = openEffects;
27432                        }
27433
27434                        popup.open();
27435                    }
27436
27437                }, that.options.hoverDelay));
27438            });
27439
27440            return that;
27441        },
27442
27443        /**
27444         *
27445         * Closes a sub-menu of a specified item(s) in a <strong>Menu</strong>.
27446         *
27447         * @param {Selector} element Target item selector.
27448         *
27449         * @returns {Menu}
27450         * Returns the Menu object to support chaining.
27451         *
27452         * @example
27453         * // get a reference to the menu widget
27454         * var menu = $("#menu").data("kendoMenu");
27455         * // close the sub menu of "Item1"
27456         * menu.close("#Item1");
27457         *
27458         */
27459        close: function (element) {
27460            var that = this;
27461            element = that.element.find(element);
27462
27463            element.each(function () {
27464                var li = $(this);
27465
27466                clearTimeout(li.data(TIMER));
27467
27468                li.data(TIMER, setTimeout(function () {
27469                    var ul = li.find(".k-group:first:visible"), popup;
27470                    if (ul[0] && that.trigger(CLOSE, { item: li[0] }) === false) {
27471                        li.css(ZINDEX, li.data(ZINDEX));
27472                        li.removeData(ZINDEX);
27473
27474                        popup = ul.data(KENDOPOPUP);
27475                        popup.close();
27476                    }
27477                }, that.options.hoverDelay));
27478            });
27479
27480            return that;
27481        },
27482
27483        _toggleDisabled: function (element, enable) {
27484            element = this.element.find(element);
27485            element.each(function () {
27486                $(this)
27487                    .toggleClass(DEFAULTSTATE, enable)
27488                    .toggleClass(DISABLEDSTATE, !enable);
27489            });
27490        },
27491
27492        _toggleHover: function(e) {
27493            var target = $(kendo.eventTarget(e)).closest(allItemsSelector);
27494
27495            if (!target.parents("li." + DISABLEDSTATE).length) {
27496                target.toggleClass("k-state-hover", e.type == MOUSEENTER || e.type == "touchstart");
27497            }
27498        },
27499
27500        _updateClasses: function() {
27501            var that = this;
27502
27503            that.element.addClass("k-widget k-reset k-header " + MENU).addClass(MENU + "-" + that.options.orientation);
27504
27505            var items = that.element
27506                            .find("li > ul")
27507                            .addClass("k-group")
27508                            .end()
27509                            .find("> li,.k-group > li");
27510
27511            items.each(function () {
27512                updateItemClasses(this);
27513            });
27514        },
27515
27516        _mouseenter: function (e) {
27517            var that = this,
27518                element = $(e.currentTarget),
27519                hasChildren = (element.children(".k-animation-container").length || element.children(groupSelector).length);
27520
27521            if (!that.options.openOnClick || that.clicked) {
27522                if (!contains(e.currentTarget, e.relatedTarget) && hasChildren) {
27523                    that.open(element);
27524                }
27525            }
27526
27527            if (that.options.openOnClick && that.clicked) {
27528                element.siblings().each(proxy(function (_, sibling) {
27529                    that.close(sibling);
27530                }, that));
27531            }
27532        },
27533
27534        _mouseleave: function (e) {
27535            var that = this,
27536                element = $(e.currentTarget),
27537                hasChildren = (element.children(".k-animation-container").length || element.children(groupSelector).length);
27538
27539            if (!that.options.openOnClick && !contains(e.currentTarget, e.relatedTarget) && hasChildren) {
27540                that.close(element);
27541            }
27542        },
27543
27544        _click: function (e) {
27545            var that = this, openHandle,
27546                target = $(kendo.eventTarget(e)),
27547                link = target.closest("." + LINK),
27548                href = link.attr("href"),
27549                element = target.closest(allItemsSelector),
27550                isLink = (!!href && href.charAt(href.length - 1) != "#");
27551
27552            if (element.hasClass(DISABLEDSTATE)) {
27553                e.preventDefault();
27554                return;
27555            }
27556
27557            if (touch) {
27558                element.siblings().each(proxy(function (_, sibling) {
27559                    that.close(sibling);
27560                }, that));
27561            }
27562
27563            if (!e.handled) { // We shouldn't stop propagation.
27564                that.trigger(SELECT, { item: element[0] });
27565            }
27566
27567            e.handled = true;
27568
27569            if (that.options.closeOnClick && !(href && href.length > 0) && !element.children(groupSelector + ",.k-animation-container").length) {
27570                that.close(link.parentsUntil(that.element, allItemsSelector));
27571            }
27572
27573            if ((!element.parent().hasClass(MENU) || !that.options.openOnClick) && !touch) {
27574                return;
27575            }
27576
27577            if (!isLink) {
27578                e.preventDefault();
27579            }
27580
27581            that.clicked = true;
27582            openHandle = element.children(".k-animation-container, .k-group").is(":visible") ? CLOSE : OPEN;
27583            that[openHandle](element);
27584        },
27585
27586        _documentClick: function (e) {
27587            var that = this;
27588
27589            if (contains(that.element[0], e.target)) {
27590                return;
27591            }
27592
27593            if (that.clicked) {
27594                that.clicked = false;
27595                that.close(that.element.find(".k-item>.k-animation-container:visible").parent());
27596            }
27597        }
27598    });
27599
27600    // client-side rendering
27601    extend(Menu, {
27602        renderItem: function (options) {
27603            options = extend({ menu: {}, group: {} }, options);
27604
27605            var empty = templates.empty,
27606                item = options.item;
27607
27608            return templates.item(extend(options, {
27609                image: item.imageUrl ? templates.image : empty,
27610                sprite: item.spriteCssClass ? templates.sprite : empty,
27611                itemWrapper: templates.itemWrapper,
27612                arrow: item.items || item.content ? templates.arrow : empty,
27613                subGroup: Menu.renderGroup
27614            }, rendering));
27615        },
27616
27617        renderGroup: function (options) {
27618            return templates.group(extend({
27619                renderItems: function(options) {
27620                    var html = "",
27621                        i = 0,
27622                        items = options.items,
27623                        len = items ? items.length : 0,
27624                        group = extend({ length: len }, options.group);
27625
27626                    for (; i < len; i++) {
27627                        html += Menu.renderItem(extend(options, {
27628                            group: group,
27629                            item: extend({ index: i }, items[i])
27630                        }));
27631                    }
27632
27633                    return html;
27634                }
27635            }, options, rendering));
27636        },
27637
27638        renderContent: function (options) {
27639            return templates.content(extend(options, rendering));
27640        }
27641    });
27642
27643    kendo.ui.plugin(Menu);
27644
27645})(jQuery);
27646(function($, undefined) {
27647    var kendo = window.kendo,
27648        ui = kendo.ui,
27649        Widget = ui.Widget,
27650        extend = $.extend,
27651        isFunction = $.isFunction,
27652        isPlainObject = $.isPlainObject,
27653        inArray = $.inArray,
27654        ERRORTEMPLATE = '<div class="k-widget k-tooltip k-tooltip-validation" style="margin:0.5em"><span class="k-icon k-warning"> </span>' +
27655                    '${message}<div class="k-callout k-callout-n"></div></div>',
27656        CHANGE = "change";
27657
27658    var specialRules = ["url", "email", "number", "date", "boolean"];
27659
27660    function fieldType(field) {
27661        return field.type || $.type(field) || "string";
27662    }
27663
27664    function createAttributes(options) {
27665        var field = (options.model.fields || options.model)[options.field],
27666            type = fieldType(field),
27667            validation = field.validation,
27668            ruleName,
27669            DATATYPE = kendo.attr("type"),
27670            BINDING = kendo.attr("bind"),
27671            rule,
27672            attr = {
27673                name: options.field
27674            };
27675
27676        for (ruleName in validation) {
27677            rule = validation[ruleName];
27678
27679            if (inArray(ruleName, specialRules) >= 0) {
27680                attr[DATATYPE] = ruleName;
27681            } else if (!isFunction(rule)) {
27682                attr[ruleName] = isPlainObject(rule) ? rule.value || ruleName : rule;
27683            }
27684
27685            attr[kendo.attr(ruleName + "-msg")] = rule.message;
27686        }
27687
27688        if (inArray(type, specialRules) >= 0) {
27689            attr[DATATYPE] = type;
27690        }
27691
27692        attr[BINDING] = (type === "boolean" ? "checked:" : "value:") + options.field;
27693
27694        return attr;
27695    }
27696
27697    var editors = {
27698        "number": function(container, options) {
27699            var attr = createAttributes(options);
27700            $('<input type="text"/>').attr(attr).appendTo(container).kendoNumericTextBox({ format: options.format });
27701            $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
27702        },
27703        "date": function(container, options) {
27704            var attr = createAttributes(options);
27705            attr[kendo.attr("format")] = options.format;
27706
27707            $('<input type="text"/>').attr(attr).appendTo(container).kendoDatePicker({ format: options.format });
27708            $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
27709        },
27710        "string": function(container, options) {
27711            var attr = createAttributes(options);
27712
27713            $('<input type="text" class="k-input k-textbox"/>').attr(attr).appendTo(container);
27714        },
27715        "boolean": function(container, options) {
27716            var attr = createAttributes(options);
27717            $('<input type="checkbox" />').attr(attr).appendTo(container);
27718        }
27719    };
27720
27721    var Editable = Widget.extend({
27722        init: function(element, options) {
27723            var that = this;
27724
27725            Widget.fn.init.call(that, element, options);
27726            that._validateProxy = $.proxy(that._validate, that);
27727            that.refresh();
27728        },
27729
27730        events: [CHANGE],
27731
27732        options: {
27733            name: "Editable",
27734            editors: editors,
27735            clearContainer: true,
27736            errorTemplate: ERRORTEMPLATE
27737        },
27738
27739        editor: function(field, modelField) {
27740            var that = this,
27741                editors = that.options.editors,
27742                isObject = isPlainObject(field),
27743                fieldName = isObject ? field.field : field,
27744                model = that.options.model || {},
27745                type = fieldType(modelField),
27746                isCustomEditor = isObject && field.editor,
27747                editor = isCustomEditor ? field.editor : editors[type],
27748                container = that.element.find("[data-container-for=" + fieldName + "]");
27749
27750            editor = editor ? editor : editors["string"];
27751
27752            if (modelField) {
27753                container = container.length ? container : that.element;
27754                editor(container, extend(true, {}, isObject ? field : { field: fieldName }, { model: model }));
27755
27756                if (isCustomEditor) {
27757                    container.find(":input:not(:button), select").each(function() {
27758                        var bindAttr = kendo.attr("bind"),
27759                            binding = this.getAttribute(bindAttr) || "";
27760
27761                        if (binding.indexOf("value:") === -1) {
27762                            binding += ",value:" + fieldName;
27763
27764                            $(this).attr(bindAttr, binding);
27765                        }
27766                    });
27767                }
27768           }
27769        },
27770
27771        _validate: function(e) {
27772            var that = this,
27773                isBoolean = typeof e.value === "boolean",
27774                input,
27775                preventChangeTrigger = that._validationEventInProgress,
27776                values = {};
27777
27778            values[e.field] = e.value;
27779
27780            input = $(':input[' + kendo.attr("bind") + '="' + (isBoolean ? 'checked:' : 'value:') + e.field + '"]', that.element);
27781
27782            try {
27783                that._validationEventInProgress = true;
27784
27785                if (!that.validatable.validateInput(input) || (!preventChangeTrigger && that.trigger(CHANGE, { values: values }))) {
27786                    e.preventDefault();
27787                }
27788
27789            } finally {
27790                that._validationEventInProgress = false;
27791            }
27792        },
27793
27794        end: function() {
27795            return this.validatable.validate();
27796        },
27797
27798        destroy: function() {
27799            this.options.model.unbind("set", this._validateProxy);
27800            kendo.unbind(this.element);
27801
27802            this.element.removeData("kendoValidator")
27803                .removeData("kendoEditable");
27804        },
27805
27806        refresh: function() {
27807            var that = this,
27808                idx,
27809                length,
27810                fields = that.options.fields || [],
27811                container = that.options.clearContainer ? that.element.empty() : that.element,
27812                model = that.options.model || {},
27813                rules = {};
27814
27815            if (!$.isArray(fields)) {
27816                fields = [fields];
27817            }
27818
27819            for (idx = 0, length = fields.length; idx < length; idx++) {
27820                var field = fields[idx],
27821                    isObject = isPlainObject(field),
27822                    fieldName = isObject ? field.field : field,
27823                    modelField = (model.fields || model)[fieldName],
27824                    validation = modelField ? (modelField.validation || {}) : {};
27825
27826                for (var rule in validation) {
27827                    if (isFunction(validation[rule])) {
27828                        rules[rule] = validation[rule];
27829                    }
27830                }
27831
27832                that.editor(field, modelField);
27833            }
27834
27835            kendo.bind(container, that.options.model);
27836
27837            that.options.model.bind("set", that._validateProxy);
27838
27839            that.validatable = container.kendoValidator({
27840                validateOnBlur: false,
27841                errorTemplate: that.options.errorTemplate || undefined,
27842               rules: rules }).data("kendoValidator");
27843
27844            container.find(":input:visible:first").focus();
27845        }
27846   });
27847
27848   ui.plugin(Editable);
27849})(jQuery);
27850(function($, undefined) {
27851    var kendo = window.kendo,
27852        ui = kendo.ui,
27853        DROPDOWNLIST = "kendoDropDownList",
27854        NUMERICTEXTBOX = "kendoNumericTextBox",
27855        DATEPICKER = "kendoDatePicker",
27856        proxy = $.proxy,
27857        POPUP = "kendoPopup",
27858        EQ = "Is equal to",
27859        NEQ = "Is not equal to",
27860        Widget = ui.Widget;
27861
27862    var booleanTemplate =
27863            '<div>' +
27864                '<input type="hidden" name="filters[0].field" value="#=field#"/>' +
27865                '<input type="hidden" name="filters[0].operator" value="eq"/>' +
27866                '<div class="k-filter-help-text">#=messages.info#</div>'+
27867                '<label>#=messages.isTrue#'+
27868                    '<input type="radio" name="filters[0].value" value="true"/>' +
27869                '</label>' +
27870                '<label>#=messages.isFalse#'+
27871                    '<input type="radio" name="filters[0].value" value="false"/>' +
27872                '</label>' +
27873                '<button type="submit" class="k-button">#=messages.filter#</button>'+
27874                '<button type="reset" class="k-button">#=messages.clear#</button>'+
27875            '</div>';
27876
27877    var defaultTemplate =
27878            '<div>' +
27879                '<input type="hidden" name="filters[0].field" value="#=field#"/>' +
27880                '#if(extra){#' + '<input type="hidden" name="filters[1].field" value="#=field#"/>' + '#}#'+
27881                '<div class="k-filter-help-text">#=messages.info#</div>'+
27882                '<select name="filters[0].operator">'+
27883                    '#for(var op in operators){#'+
27884                        '<option value="#=op#">#=operators[op]#</option>'+
27885                    '#}#'+
27886                '</select>'+
27887                '<input name="filters[0].value" class="k-textbox" type="text" data-#=ns#type="#=type#"/>'+
27888                '#if(extra){#'+
27889                    '<select name="logic" class="k-filter-and">'+
27890                        '<option value="and">And</option>'+
27891                        '<option value="or">Or</option>'+
27892                    '</select>'+
27893                    '<select name="filters[1].operator">'+
27894                        '#for(var op in operators){#'+
27895                            '<option value="#=op#">#=operators[op]#</option>'+
27896                        '#}#'+
27897                    '</select>'+
27898                    '<input name="filters[1].value" class="k-textbox" type="text" data-#=ns#type="#=type#"/>'+
27899                '#}#'+
27900                '<button type="submit" class="k-button">#=messages.filter#</button>'+
27901                '<button type="reset" class="k-button">#=messages.clear#</button>'+
27902            '</div>';
27903
27904    function removeFiltersForField(expression, field) {
27905        if (expression.filters) {
27906            expression.filters = $.grep(expression.filters, function(filter) {
27907                removeFiltersForField(filter, field);
27908                if (filter.filters) {
27909                    return filter.filters.length;
27910                } else {
27911                    return filter.field != field;
27912                }
27913            });
27914        }
27915    }
27916
27917    function value(dom, val) {
27918        var widget = dom.data(DROPDOWNLIST) || dom.data(NUMERICTEXTBOX) || dom.data(DATEPICKER);
27919
27920        if (widget) {
27921            widget.value(val);
27922        } else if (dom.is(":radio")) {
27923            dom.filter("[value=" + val + "]").attr("checked", "checked");
27924        } else {
27925            dom.val(val);
27926        }
27927    }
27928
27929    function identity(value) {
27930        return value;
27931    }
27932
27933    function toObject(array) {
27934        var result = {},
27935            idx,
27936            length,
27937            name,
27938            members,
27939            member,
27940            value,
27941            interimResult,
27942            previousMember,
27943            parentResult;
27944
27945        for (idx = 0, length = array.length; idx < length; idx++) {
27946            members = array[idx].name.split(/[\.\[\]]+/);
27947
27948            members = $.grep(members, identity);
27949
27950            value = array[idx].value;
27951
27952            interimResult = result;
27953
27954            parentResult = result;
27955
27956            for (member = 0; member < members.length - 1; member++) {
27957                name = members[member];
27958
27959                if (!isNaN(name)) {
27960                    previousMember = members[member-1];
27961
27962                    if (!$.isArray(parentResult[previousMember])) {
27963                        interimResult = parentResult[previousMember] = [];
27964                    }
27965                }
27966
27967                parentResult = interimResult;
27968
27969                interimResult = interimResult[name] = interimResult[name] || {};
27970            }
27971
27972            interimResult[members[member]] = value;
27973        }
27974
27975        return result;
27976    }
27977
27978    var FilterMenu = Widget.extend({
27979        init: function(element, options) {
27980            var that = this,
27981                type = "string",
27982                link,
27983                field,
27984                operators;
27985
27986            Widget.fn.init.call(that, element, options);
27987
27988            operators = options.operators || {};
27989            element = that.element;
27990            options = that.options;
27991
27992            link = element.addClass("k-filterable").find("k-grid-filter");
27993
27994            if (!link[0]) {
27995                link = element.prepend('<a class="k-grid-filter" href="#"><span class="k-icon k-filter"/></a>').find(".k-grid-filter");
27996            }
27997
27998            link.click(proxy(that._click, that));
27999
28000            that.dataSource = options.dataSource.bind("change", proxy(that.refresh, that));
28001
28002            that.field = element.attr(kendo.attr("field"));
28003
28004            that.model = that.dataSource.reader.model;
28005
28006            that._parse = function(value) {
28007                 return value + "";
28008            };
28009
28010            if (that.model && that.model.fields) {
28011                field = that.model.fields[that.field];
28012
28013                if (field) {
28014                    type = field.type;
28015                    that._parse = proxy(field.parse, field);
28016                }
28017            }
28018
28019            operators = operators[type] || options.operators[type];
28020
28021            that.form = $('<form class="k-filter-menu k-group"/>');
28022            that.form.html(kendo.template(type === "boolean" ? booleanTemplate : defaultTemplate)({
28023                field: that.field,
28024                ns: kendo.ns,
28025                messages: options.messages,
28026                extra: options.extra,
28027                operators: operators,
28028                type: type
28029            }));
28030
28031            that.popup = that.form[POPUP]({
28032                anchor: link,
28033                open: proxy(that._open, that)
28034            }).data(POPUP);
28035
28036            that.link = link;
28037
28038            that.form
28039                .bind({
28040                    submit: proxy(that._submit, that),
28041                    reset: proxy(that._reset, that)
28042                })
28043                .find("select")
28044                [DROPDOWNLIST]()
28045                .end()
28046                .find("[" + kendo.attr("type") + "=number]")
28047                [NUMERICTEXTBOX]().parent().children().removeClass("k-textbox")
28048                .end().end().end()
28049                .find("[" + kendo.attr("type") + "=date]")
28050                [DATEPICKER]().removeClass("k-textbox");
28051
28052            that.refresh();
28053        },
28054
28055        refresh: function() {
28056            var that = this,
28057                expression = that.dataSource.filter() || { filters: [], logic: "and" };
28058
28059            if (that._populateForm(expression)) {
28060                that.link.addClass("k-state-active");
28061            } else {
28062                that.link.removeClass("k-state-active");
28063            }
28064        },
28065
28066        _populateForm: function(expression) {
28067            var that = this,
28068                filters = expression.filters,
28069                idx,
28070                length,
28071                form = that.form,
28072                found = false,
28073                current = 0,
28074                filter;
28075
28076            for (idx = 0, length = filters.length; idx < length; idx++) {
28077                filter = filters[idx];
28078                if (filter.field == that.field) {
28079                    value(form.find("[name='filters[" + current + "].value']"), that._parse(filter.value));
28080                    value(form.find("[name='filters[" + current + "].operator']"), filter.operator);
28081                    value(form.find("[name=logic]"), expression.logic);
28082
28083                    current++;
28084                    found = true;
28085                } else if (filter.filters) {
28086                    found = found || that._populateForm(filter);
28087                }
28088            }
28089
28090            return found;
28091        },
28092
28093        _merge: function(expression) {
28094            var that = this,
28095                logic = expression.logic || "and",
28096                filters = expression.filters,
28097                filter,
28098                result = that.dataSource.filter() || { filters:[], logic: "and" },
28099                idx,
28100                length;
28101
28102            removeFiltersForField(result, that.field);
28103
28104            filters = $.grep(filters, function(filter) {
28105                return filter.value !== "";
28106            });
28107
28108            for (idx = 0, length = filters.length; idx < length; idx++) {
28109                filter = filters[idx];
28110                filter.value = that._parse(filter.value);
28111            }
28112
28113            if (filters.length) {
28114                if (result.filters.length) {
28115                    expression.filters = filters;
28116
28117                    if (result.logic !== "and") {
28118                        result.filters = [ { logic: result.logic, filters: result.filters }];
28119                        result.logic = "and";
28120                    }
28121
28122                    if (filters.length > 1) {
28123                        result.filters.push(expression);
28124                    } else {
28125                        result.filters.push(filters[0]);
28126                    }
28127                } else {
28128                    result.filters = filters;
28129                    result.logic = logic;
28130                }
28131            }
28132
28133            return result;
28134        },
28135
28136        filter: function(expression) {
28137            expression = this._merge(expression);
28138
28139            if (expression.filters.length) {
28140                this.dataSource.filter(expression);
28141            }
28142        },
28143
28144        clear: function() {
28145            var that = this,
28146                expression = that.dataSource.filter() || { filters:[] };
28147
28148            expression.filters = $.grep(expression.filters, function(filter) {
28149                if (filter.filters) {
28150                    filter.filters = $.grep(filter.filters, function(expr) {
28151                        return expr.field != that.field;
28152                    });
28153
28154                    return filter.filters.length;
28155                }
28156
28157                return filter.field != that.field;
28158            });
28159
28160            if (!expression.filters.length) {
28161                expression = null;
28162            }
28163
28164            that.dataSource.filter(expression);
28165        },
28166
28167        _submit: function(e) {
28168            var that = this;
28169
28170            e.preventDefault();
28171
28172            that.filter(toObject(that.form.serializeArray()));
28173
28174            that.popup.close();
28175        },
28176
28177        _reset: function(e) {
28178            this.clear();
28179            this.popup.close();
28180        },
28181
28182        _click: function(e) {
28183            e.preventDefault();
28184            e.stopPropagation();
28185            this.popup.toggle();
28186        },
28187
28188        _open: function() {
28189            $(".k-filter-menu").not(this.form).each(function() {
28190                $(this).data(POPUP).close();
28191            });
28192        },
28193
28194        options: {
28195            name: "FilterMenu",
28196            extra: true,
28197            type: "string",
28198            operators: {
28199                string: {
28200                    eq: EQ,
28201                    neq: NEQ,
28202                    startswith: "Starts with",
28203                    contains: "Contains",
28204                    endswith: "Ends with"
28205                },
28206                number: {
28207                    eq: EQ,
28208                    neq: NEQ,
28209                    gte: "Is greater than or equal to",
28210                    gt: "Is greater than",
28211                    lte: "Is less than or equal to",
28212                    lt: "Is less than"
28213                },
28214                date: {
28215                    eq: EQ,
28216                    neq: NEQ,
28217                    gte: "Is after or equal to",
28218                    gt: "Is after",
28219                    lte: "Is before or equal to",
28220                    lt: "Is before"
28221                }
28222            },
28223            messages: {
28224                info: "Show rows with value that:",
28225                isTrue: "is true",
28226                isFalse: "is false",
28227                filter: "Filter",
28228                clear: "Clear"
28229            }
28230        }
28231    });
28232
28233    ui.plugin(FilterMenu);
28234})(jQuery);
28235/**
28236 * @fileOverview Provides a PanelBar implementation which can be used to
28237 * display a hierarchical data as a multi-level, expandable panel bar.
28238 */
28239
28240(function($, undefined) {
28241    /**
28242     * @name kendo.ui.PanelBar.Description
28243     *
28244     * @section
28245     * <p>
28246     *  The <strong>PanelBar</strong> displays hierarchical data as a multi-level, expandable widget that is useful for
28247     *  constained areas of a page. Its structure may be defined in HTML or c
28247onfigured dynamically through its API. The
28248     *  content for items can also be loaded via AJAX by specifying a content URL.
28249     * </p>
28250     * <h3>Getting Started</h3>
28251     * <p>
28252     *  A <strong>PanelBar</strong> can be created by targeting the root element of a HTML list. A
28253     *  <strong>PanelBar</strong> will utilize this list to define its structure and content.
28254     * </p>
28255     *
28256     * @exampleTitle Create a list of items
28257     * @example
28258     * <ul id="panelBar">
28259     *     <li>
28260     *         Item 1
28261     *             <ul>
28262     *                 <li>Sub Item 1</li>
28263     *                 <li>Sub Item 2</li>
28264     *             </ul>
28265     *     <li>
28266     *     <li>Item 2</li>
28267     * </ul>
28268     *
28269     * @section
28270     * <p></p>
28271     * <p>
28272     *  Initialization of a <strong>PanelBar</strong> should occur after the DOM is fully loaded. It is recommended
28273     *  that initialization the <strong>PanelBar</strong> occur within a handler is provided to $(document).ready().
28274     * </p>
28275     *
28276     * @exampleTitle Initialize the PanelBar via an ID selector
28277     * @example
28278     * $(document).ready(function() {
28279     *     $("#panelBar").kendoPanelBar();
28280     * });
28281     *
28282     * @section
28283     * <p>
28284     *  <strong>PanelBar</strong> items may contain nested content (including markup) within a <strong>div</strong>
28285     *  element. Text content located outside nested content will be used as the title of the item.
28286     * </p>
28287     *
28288     * @exampleTitle Create a list of items in HTML with nested content
28289     * @example
28290     * <ul id="panelBar">
28291     *     <li>Item with no content</li>
28292     *     <li>Item with content
28293     *         <div>This is nested content of a PanelBar item.</div>
28294     *     </li>
28295     * </ul>
28296     *
28297     * @section
28298     * <p>A <strong>PanelBar</strong> will preserve the content defined within an item.</p>
28299     *
28300     * @exampleTitle Initialize the PanelBar via an ID selector
28301     * @example
28302     * var panelBar = $("#panelbar").kendoPanelBar();
28303     *
28304     * @exampleTitle Initialize a PanelBar using JSON data object
28305     * @example
28306     * $("#panelbar").kendoPanelBar({
28307     *     dataSource: [
28308     *         {
28309     *             text: "Item 1",
28310     *             url: "http://www.kendoui.com/"                  // link URL if navigation is needed (optional)
28311     *         },
28312     *         {
28313     *             text: "<b>Item 2</b>",
28314     *             encoded: false,                                 // Allows use of HTML for item text
28315     *             content: "text"                                 // content within an item
28316     *         },
28317     *         {
28318     *             text: "Item 3",
28319     *             contentUrl: "partialContent.html"               // content URL to load within an item
28320     *         },
28321     *         {
28322     *             text: "Item 4",
28323     *             imageUrl: "http://www.kendoui.com/test.jpg",    // item image URL, optional
28324     *             expanded: true,                                 // item is rendered expanded
28325     *             items: [{                                       // Sub item collection.
28326     *                 text: "Sub Item 1"
28327     *             },
28328     *             {
28329     *                 text: "Sub Item 2"
28330     *             }]
28331     *         },
28332     *         {
28333     *             text: "Item 5",
28334     *             // item image sprite CSS class, optional
28335     *             spriteCssClass: "imageClass3"
28336     *         }
28337     *     ]
28338     * });
28339     *
28340     * @section
28341     * <h3>Loading Content with AJAX</h3>
28342     * <p>
28343     *  While any valid technique for loading AJAX content can be used, the <strong>PanelBar</strong> provides built-in
28344     *  support for asynchronously loading content from URLs. These URLs should return HTML fragments that can be
28345     *  loaded in the <strong>PanelBar</strong> item content area.
28346     * </p>
28347     *
28348     * @exampleTitle Create a list of items with a target for dynamic content
28349     * @example
28350     * <ul id="panelBar">
28351     *     <li>Item 1
28352     *         <ul>
28353     *             <li>Sub Item 1</li>
28354     *         </ul>
28355     *     </li>
28356     *     <li>Item 2</li>
28357     *     <li>
28358     *         Item with Dynamic Content
28359     *         <div></div>
28360     *     </li>
28361     * </ul>
28362     *
28363     * @exampleTitle Load a PanelBar item content asynchronously via AJAX
28364     * @example
28365     * $("#panelBar").kendoPanelBar({
28366     *     contentUrls:[
28367     *         null,
28368     *         null,
28369     *         "html-content-snippet.html"
28370     *     ]
28371     * });
28372     *
28373     * @section
28374     * <p>
28375     *  When the <strong>PanelBar</strong> loads remote content via AJAX, the server response is cached in-memory so
28376     *  that subsequent expand/collapse actions do not trigger subsequent AJAX requests.
28377     * </p>
28378     * <h3>Customizing PanelBar Animations</h3>
28379     * <p>
28380     *  By default, a <strong>PanelBar</strong> uses animations to expand and reveal sub-items when an item header is
28381     *  clicked. These animations can be modified in configuration via the open and close animation properties. A
28382     *  <strong>PanelBar</strong>
28382 can also be configured to only allow one panel to remain open at a time.
28383     * </p>
28384     *
28385     * @exampleTitle Changing PanelBar animation and expandMode behavior
28386     * @example
28387     * $("#panelBar").kendoPanelBar({
28388     *     animation: {
28389     *         open : { effects: "fadeIn" }
28390     *     },
28391     *     expandMode: "single"
28392     * });
28393     *
28394     * @section
28395     * <h3>Dynamically Configuring PanelBar Items</h3>
28396     * <p>
28397     *  The <strong>PanelBar</strong> API provides several methods for dynamically adding or removing Items. To add
28398     *  items, provide the new item as a JSON object along with a reference item that will be used to determine its
28399     *  placement in the items hierarchy. Note: The reference item is optional when appending.
28400     * </p>
28401     * <p>
28402     *  A reference item is a target <strong>PanelBar</strong> item HTML element that already exists in the PanelBar.
28403     *  Any valid selector can be used to obtain a reference to the target item.
28404     * </p>
28405     * <p>Removing an item only requires a reference to the target element that should be removed.</p>
28406     *
28407     * @exampleTitle Dynamically adding a new root PanelBar item
28408     * @example
28409     * var panelBar = $("#panelBar").kendoPanelBar().data("kendoPanelBar");
28410     *
28411     * panelBar.insertAfter(
28412     *      { text: "New PanelBar Item" },
28413     *      panelBar.element.children("li:last")
28414     * );
28415     *
28416     * @section
28417     * <h3>Accessing an Existing PanelBar</h3>
28418     * <p>
28419     *  You can reference an existing <strong>PanelBar</strong> instance via
28420     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
28421     *  use the API to control its behavior.
28422     * </p>
28423     *
28424     * @exampleTitle Accessing an existing PanelBar instance
28425     * @example
28426     * var panelBar = $("#panelBar").data("kendoPanelBar");
28427     *
28428     */
28429    var kendo = window.kendo,
28430        ui = kendo.ui,
28431        extend = $.extend,
28432        each = $.each,
28433        template = kendo.template,
28434        Widget = ui.Widget,
28435        excludedNodesRegExp = /^(ul|a|div)$/i,
28436        IMG = "img",
28437        HREF = "href",
28438        LAST = "k-last",
28439        LINK = "k-link",
28440        ERROR = "error",
28441        CLICK = "click",
28442        ITEM = ".k-item",
28443        IMAGE = "k-image",
28444        FIRST = "k-first",
28445        EXPAND = "expand",
28446        SELECT = "select",
28447        CONTENT = "k-content",
28448        ACTIVATE = "activate",
28449        COLLAPSE = "collapse",
28450        CONTENTURL = "contentUrl",
28451        MOUSEENTER = "mouseenter",
28452        MOUSELEAVE = "mouseleave",
28453        CONTENTLOAD = "contentLoad",
28454        ACTIVECLASS = ".k-state-active",
28455        GROUPS = "> .k-panel",
28456        CONTENTS = "> .k-content",
28457        SELECTEDCLASS = ".k-state-selected",
28458        DISABLEDCLASS = ".k-state-disabled",
28459        HIGHLIGHTEDCLASS = ".k-state-highlighted",
28460        clickableItems = ITEM + ":not(.k-state-disabled) .k-link",
28461        disabledItems = ITEM + ".k-state-disabled .k-link",
28462        defaultState = "k-state-default",
28463        VISIBLE = ":visible",
28464        EMPTY = ":empty",
28465        SINGLE = "single",
28466
28467        templates = {
28468            content: template(
28469                "<div class='k-content'#= contentAttributes(data) #>#= content(item) #</div>"
28470            ),
28471            group: template(
28472                "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
28473                    "#= renderItems(data) #" +
28474                "</ul>"
28475            ),
28476            itemWrapper: template(
28477                "<#= tag(item) # class='#= textClass(item, group) #'#= contentUrl(item) ##= textAttributes(item) #>" +
28478                    "#= image(item) ##= sprite(item) ##= text(item) #" +
28479                    "#= arrow(data) #" +
28480                "</#= tag(item) #>"
28481            ),
28482            item: template(
28483                "<li class='#= wrapperCssClass(group, item) #'>" +
28484                    "#= itemWrapper(data) #" +
28485                    "# if (item.items) { #" +
28486                    "#= subGroup({ items: item.items, panelBar: panelBar, group: { expanded: item.expanded } }) #" +
28487                    "# } #" +
28488                "</li>"
28489            ),
28490            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
28491            arrow: template("<span class='#= arrowClass(item) #'></span>"),
28492            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
28493            empty: template("")
28494        },
28495
28496        rendering = {
28497            wrapperCssClass: function (group, item) {
28498                var result = "k-item",
28499                    index = item.index;
28500
28501                if (item.enabled === false) {
28502                    result += " k-state-disabled";
28503                } else if (item.expanded === true) {
28504                    result += " k-state-active";
28505                } else {
28506                    result += " k-state-default";
28507                }
28508
28509                if (index === 0) {
28510                    result += " k-first";
28511                }
28512
28513                if (index == group.length-1) {
28514                    result += " k-last";
28515                }
28516
28517                return result;
28518            },
28519            textClass: function(item, group) {
28520                var result = LINK;
28521
28522                if (group.firstLevel) {
28523                    result += " k-header";
28524                }
28525
28526                return result;
28527            },
28528            textAttributes: function(item) {
28529                return item.url ? " href='" + item.url + "'" : "";
28530            },
28531            arrowClass: function(item) {
28532                var result = "k-icon";
28533
28534                result += item.expanded ? " k-arrow-up k-panelbar-collapse" : " k-arrow-down k-panelbar-expand";
28535
28536                return result;
28537            },
28538            text: function(item) {
28539                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
28540            },
28541            tag: function(item) {
28542                return item.url ? "a" : "span";
28543            },
28544            groupAttributes: function(group) {
28545                return group.expanded !== true ? " style='display:none'" : "";
28546            },
28547            groupCssClass: function(group) {
28548                return "k-group k-panel";
28549            },
28550            contentAttributes: function(content) {
28551                return content.item.expanded !== true ? " style='display:none'" : "";
28552            },
28553            content: function(item) {
28554                return item.content ? item.content : item.contentUrl ? "" : "&nbsp;";
28555            },
28556            contentUrl: function(item) {
28557                return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
28558            }
28559        };
28560
28561    function updateItemClasses (item, panelElement) {
28562        item = $(item).addClass("k-item");
28563
28564        item
28565            .children(IMG)
28566            .addClass(IMAGE);
28567        item
28568            .children("a")
28569            .addClass(LINK)
28570            .children(IMG)
28571            .addClass(IMAGE);
28572        item
28573            .filter(":not([disabled]):not([class*=k-state])")
28574            .addClass("k-state-default");
28575        item
28576            .filter("li[disabled]")
28577            .addClass("k-state-disabled")
28578            .removeAttr("disabled");
28579        item
28580            .filter(":not([class*=k-state])")
28581            .children("a:focus")
28582            .parent()
28583            .addClass(ACTIVECLASS.substr(1));
28584        item
28585            .find(">div")
28586            .addClass(CONTENT)
28587            .css({ display: "none" });
28588
28589        item.each(function() {
28590            var item = $(this);
28591
28592            if (!item.children("." + LINK).length) {
28593                item
28594                    .contents()      // exclude groups, real links, templates and empty text nodes
28595                    .filter(function() { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue))); })
28596                    .wrapAll("<span class='" + LINK + "'/>");
28597            }
28598        });
28599
28600        panelElement
28601            .find(" > li > ." + LINK)
28602            .addClass("k-header");
28603    }
28604
28605    function updateArrow (items) {
28606        items = $(items);
28607
28608        items.children(".k-link").children(".k-icon").remove();
28609
28610        items
28611            .filter(":has(.k-panel),:has(.k-content)")
28612            .children(".k-link:not(:has([class*=k-arrow]))")
28613            .each(function () {
28614                var item = $(this),
28615                    parent = item.parent();
28616
28617                item.append("<span class='k-icon " + (parent.hasClass(ACTIVECLASS.substr(1)) ? "k-arrow-up k-panelbar-collapse" : "k-arrow-down k-panelbar-expand") + "'/>");
28618            });
28619    }
28620
28621    function updateFirstLast (items) {
28622        items = $(items);
28623
28624        items.filter(".k-first:not(:first-child)").removeClass(FIRST);
28625        items.filter(".k-last:not(:last-child)").removeClass(LAST);
28626        items.filter(":first-child").addClass(FIRST);
28627        items.filter(":last-child").addClass(LAST);
28628    }
28629
28630    var PanelBar = Widget.extend({/** @lends kendo.ui.PanelBar.prototype */
28631        /**
28632         *
28633         * Creates a PanelBar instance.
28634         *
28635         * @constructs
28636         * @extends kendo.ui.Widget
28637         *
28638         * @param {Selector} element DOM element
28639         * @param {Object} options Configuration options.
28640         *
28641         * @option {Object} [animation]
28642         * A collection of visual animations used when <strong>PanelBar</strong> items are opened or closed through
28643         * user interactions. Setting this option to <strong>false</strong> will disable all animations.
28644         *
28645         * _exampleTitle Defining custom animations when opening and closing items
28646         * _example
28647         * $("#panelBar").kendoPanelBar({
28648         *     animation: {
28649         *         // fade-out closing items over 1000 milliseconds
28650         *         close: {
28651         *             duration: 1000,
28652         *             effects: "fadeOut"
28653         *         },
28654         *        // fade-in and expand opening items over 500 milliseconds
28655         *        open: {
28656         *            duration: 500,
28657         *            effects: "expandVertical fadeIn"
28658         *        }
28659         *    }
28660         * });
28661         *
28662         * @option {Object} [animation.open]
28663         * The visual animation(s) that will be used when opening items.
28664         *
28665         * _exampleTitle Defining a custom animation when opening items that executes over 200 milliseconds
28666         * _example
28667         * $("#panelBar").kendoPanelBar({
28668         *     animation: {
28669         *         open: {
28670         *             duration: 200,
28671         *             effects: "expandVertical"
28672         *         }
28673         *     }
28674         * });
28675         *
28676         * @option {Number} [animation.open.duration] <200>
28677         * The number of milliseconds used for the visual animation when an item is opened.
28678         *
28679         * _exampleTitle Defining a custom animation for opening items that executes over 1000 milliseconds
28680         * _example
28681         * $("#panelBar").kendoPanelBar({
28682         *  animation: {
28683         *       open: {
28684         *           duration: 1000
28685         *       }
28686         *    }
28687         * });
28688         *
28689         * @option {String} [animation.open.effects] <"expandVertical">
28690         * A whitespace-delimited string of animation effects that are used when an item is expanded. Options include
28691         * <strong>"expandVertical"</strong> and <strong>"fadeIn"</strong>.
28692         *
28693         * @option {Boolean} [animation.open.show] <true>
28694         *
28695         * @option {Object} [animation.close]
28696         * The visual animation(s) that will be used when <strong>PanelBar</strong> items are closed.
28697         *
28698         * _exampleTitle Defining a custom animation for closing items that
28699         * executes over 200 milliseconds
28700         * _example
28701         * $("#panelBar").kendoPanelBar({
28702         *     animation: {
28703         *         close: {
28704         *             duration: 200,
28705         *             effects: "fadeOut"
28706         *         }
28707         *     }
28708         * });
28709         *
28710         * @option {Number} [animation.close.duration] <200>
28711         * The number of milliseconds used for the visual animation when a <strong>PanelBar</strong> item is closed.
28712         *
28713         * _exampleTitle Animating all closing items for 1000 milliseconds
28714         * _example
28715         * $("#panelBar").kendoPanelBar({
28716         *     animation: {
28717         *         close: {
28718                       duration: 1000
28719                   }
28720         *   }
28721         * });
28722         *
28723         * @option {String} [animation.close.effects]
28724         * A whitespace-delimited string of animation effects that are utilized when a <strong>PanelBar</strong> item
28725         * is closed. Options include <strong>"fadeOut"</strong>.
28726         *
28727         * _exampleTitle Fading-out all closing items for 1000 milliseconds
28728         * _example
28729         * $("#panelBar").kendoPanelBar({
28730         *     animation: {
28731         *         close: {
28732         *             duration: 1000,
28733         *             effects: "fadeOut"
28734         *         }
28735         *     }
28736         * });
28737         *
28738         * @option {String} [expandMode] <"multiple">
28739         * Specifies how the <strong>PanelBar</strong> items are displayed when opened and closed. The following values
28740         * are available:
28741         * <div class="details-list">
28742         *  <dl>
28743         *   <dt>"single"</dt>
28744         *   <dd>Display one item at a time when an item is opened; opening an item will close the previously opened item.</dd>
28745         *   <dt>"multiple"</dt>
28746         *   <dd>Display multiple values at one time; opening an item has no visual impact on any other items in the <strong>PanelBar</strong>.</dd>
28747         *  </dl>
28748         * </div>
28749         *
28750         * _example
28751         * $("#panelBar").kendoPanelBar({
28752         *     expandMode: "single"
28753         * });
28754         *
28755         */
28756        init: function(element, options) {
28757            var that = this,
28758                content;
28759
28760            Widget.fn.init.call(that, element, options);
28761
28762            element = that.wrapper = that.element;
28763            options = that.options;
28764
28765            if (options.dataSource) {
28766                that.element.empty();
28767                that.append(options.dataSource, element);
28768            }
28769
28770            that._updateClasses();
28771
28772            if (options.animation === false) {
28773                options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
28774            }
28775
28776            element
28777                .delegate(clickableItems, CLICK, $.proxy(that._click, that))
28778                .delegate(clickableItems, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover)
28779                .delegate(disabledItems, CLICK, false);
28780
28781            if (options.contentUrls) {
28782                element.find("> .k-item")
28783                    .each(function(index, item) {
28784                        $(item).find("." + LINK).data(CONTENTURL, options.contentUrls[index]);
28785                    });
28786            }
28787
28788            content = element.find("li" + ACTIVECLASS + " > ." + CONTENT);
28789
28790            if (content.length > 0) {
28791                that.expand(content.parent(), false);
28792            }
28793
28794            kendo.notify(that);
28795        },
28796
28797        events: [
28798                /**
28799                 *
28800                 * Triggered when an item of a PanelBar is expanded.
28801                 *
28802                 * @name kendo.ui.PanelBar#expand
28803                 * @event
28804                 *
28805                 * @param {Event} e
28806                 *
28807                 * @param {Element} e.item
28808                 * The expanding item of the PanelBar.
28809                 *
28810                 * @exampleTitle Attach expand event handler during initialization; detach via unbind()
28811                 * @example
28812                 * // event handler for expand
28813                 * var onExpand = function(e) {
28814                 *     // access the expanded item via e.item (HTMLElement)
28815                 * };
28816                 *
28817                 * // attach expand event handler during initialization
28818                 * var panelBar = $("#panelBar").kendoPanelBar({
28819                 *     expand: onExpand
28820                 * });
28821                 *
28822                 * // detach expand event handler via unbind()
28823                 * panelBar.data("kendoPanelBar").unbind("expand", onExpand);
28824                 *
28825                 * @exampleTitle Attach expand event handler via bind(); detach via unbind()
28826                 * @example
28827                 * // event handler for expand
28828                 * var onExpand = function(e) {
28829                 *     // access the expanded item via e.item (HTMLElement)
28830                 * };
28831                 *
28832                 * // attach expand event handler via bind()
28833                 * $("#panelBar").data("kendoPanelBar").bind("expand", onExpand);
28834                 *
28835                 * // detach expand event handler via unbind()
28836                 * $("#panelBar").data("kendoPanelBar").unbind("expand", onExpand);
28837                 *
28838                 */
28839                EXPAND,
28840
28841                /**
28842                 *
28843                 * Triggered when an item of a PanelBar is collapsed.
28844                 *
28845                 * @name kendo.ui.PanelBar#collapse
28846                 * @event
28847                 *
28848                 * @param {Event} e
28849                 *
28850                 * @param {Element} e.item
28851                 * The collapsing item of the PanelBar.
28852                 *
28853                 * @exampleTitle Attach collapse event handler during initialization; detach via unbind()
28854                 * @example
28855                 * // event handler for collapse
28856                 * var onCollapse = function(e) {
28857                 *     // access the collapsed item via e.item (HTMLElement)
28858                 * };
28859                 *
28860                 * // attach collapse event handler during initialization
28861                 * var panelBar = $("#panelBar").kendoPanelBar({
28862                 *     collapse: onCollapse
28863                 * });
28864                 *
28865                 * // detach collapse event handler via unbind()
28866                 * panelBar.data("kendoPanelBar").unbind("collapse", onCollapse);
28867                 *
28868                 * @exampleTitle Attach collapse event handler via bind(); detach via unbind()
28869                 * @example
28870                 * // event handler for collapse
28871                 * var onCollapse = function(e) {
28872                 *     // access the collapsed item via e.item (HTMLElement)
28873                 * };
28874                 *
28875                 * // attach collapse event handler via bind()
28876                 * $("#panelBar").data("kendoPanelBar").bind("collapse", onCollapse);
28877                 *
28878                 * // detach collapse event handler via unbind()
28879                 * $("#panelBar").data("kendoPanelBar").unbind("collapse", onCollapse);
28880                 *
28881                 */
28882                COLLAPSE,
28883
28884                /**
28885                 *
28886                 * Triggered when an item of a PanelBar is selected.
28887                 *
28888                 * @name kendo.ui.PanelBar#select
28889                 * @event
28890                 *
28891                 * @param {Event} e
28892                 *
28893                 * @param {Element} e.item
28894                 * The selected item of the PanelBar.
28895                 *
28896                 * @exampleTitle Attach select event handler during initialization; detach via unbind()
28897                 * @example
28898                 * // event handler for select
28899                 * var onSelect = function(e) {
28900                 *     // access the selected item via e.item (HTMLElement)
28901                 * };
28902                 *
28903                 * // attach select event handler during initialization
28904                 * var panelBar = $("#panelBar").kendoPanelBar({
28905                 *     select: onSelect
28906                 * });
28907                 *
28908                 * // detach select event handler via unbind()
28909                 * panelBar.data("kendoPanelBar").unbind("select", onSelect);
28910                 *
28911                 * @exampleTitle Attach select event handler via bind(); detach via unbind()
28912                 * @example
28913                 * // event handler for select
28914                 * var onSelect = function(e) {
28915                 *     // access the selected item via e.item (HTMLElement)
28916                 * };
28917                 *
28918                 * // attach select event handler via bind()
28919                 * $("#panelBar").data("kendoPanelBar").bind("select", onSelect);
28920                 *
28921                 * // detach select event handler via unbind()
28922                 * $("#panelBar").data("kendoPanelBar").unbind("select", onSelect);
28923                 *
28924                 */
28925                SELECT,
28926
28927                /**
28928                 *
28929                 * Triggered when an item of a PanelBar is activated.
28930                 *
28931                 * @name kendo.ui.PanelBar#activate
28932                 * @event
28933                 *
28934                 * @param {Event} e
28935                 *
28936                 * @param {Element} e.item
28937                 * The activated item of the PanelBar.
28938                 *
28939                 * @exampleTitle Attach activate event handler during initialization; detach via unbind()
28940                 * @example
28941                 * // event handler for activate
28942                 * var onActivate = function(e) {
28943                 *     // access the activated item via e.item (HTMLElement)
28944                 * };
28945                 *
28946                 * // attach activate event handler during initialization
28947                 * var panelBar = $("#panelBar").kendoPanelBar({
28948                 *     activate: onActivate
28949                 * });
28950                 *
28951                 * // detach activate event handler via unbind()
28952                 * panelBar.data("kendoPanelBar").unbind("activate", onActivate);
28953                 *
28954                 * @exampleTitle Attach activate event handler via bind(); detach via unbind()
28955                 * @example
28956                 * // event handler for activate
28957                 * var onActivate = function(e) {
28958                 *     // access the activated item via e.item (HTMLElement)
28959                 * };
28960                 *
28961                 * // attach activate event handler via bind()
28962                 * $("#panelBar").data("kendoPanelBar").bind("activate", onActivate);
28963                 *
28964                 * // detach activate event handler via unbind()
28965                 * $("#panelBar").data("kendoPanelBar").unbind("activate", onActivate);
28966                 *
28967                 */
28968                ACTIVATE,
28969
28970                /**
28971                 * Fires when AJAX request results in an error.
28972                 * @name kendo.ui.PanelBar#error
28973                 * @event
28974                 * @param {Event} e
28975                 * @param {jqXHR} e.xhr The jqXHR object used to load the content
28976                 * @param {String} e.status The returned status.
28977                 * @example
28978                 * $("#panelBar").kendoPanelBar({
28979                 *     error: function(e) {
28980                 *         // handle event
28981                 *     }
28982                 * });
28983                 *
28984                 * @exampleTitle To set after intialization
28985                 * @example
28986                 * // get a reference to the panel bar
28987                 * var panelBar = $("#panelBar").data("kendoPanelBar");
28988                 * // bind the error ajax event
28989                 * panelBar.bind("error", function(e) {
28990                 *     // handle event
28991                 * });
28992                 */
28993                ERROR,
28994                /**
28995                 * Fires when content is fetched from an AJAX request.
28996                 * @name kendo.ui.PanelBar#contentLoad
28997                 * @event
28998                 * @param {Event} e
28999                 * @param {Element} e.item The selected item
29000                 * @param {Element} e.contentElement The loaded content element
29001                 * @example
29002                 * $("#panelBar").kendoPanelBar({
29003                 *     contentLoad: function(e) {
29004                 *         // handle event
29005                 *     }
29006                 * });
29007                 * @exampleTitle To set after intialization
29008                 * @example
29009                 * // get a reference to the panel bar
29010                 * var panelBar = $("#panelBar").data("kendoPanelBar");
29011                 * // bind the contentLoad event
29012                 * panelBar.bind("contentLoad", function(e) {
29013                 *     // handle event
29014                 * });
29015                 */
29016                CONTENTLOAD
29017            ],
29018        options: {
29019            name: "PanelBar",
29020            animation: {
29021                expand: {
29022                    effects: "expand:vertical",
29023                    duration: 200,
29024                    show: true
29025                },
29026                collapse: { // if collapse animation effects are defined, they will be used instead of expand.reverse
29027                    duration: 200
29028                }
29029            },
29030            expandMode: "multiple"
29031        },
29032
29033        /**
29034         *
29035         * Expands the specified item(s) of a <strong>PanelBar</strong>.
29036         *
29037         * @example
29038         * // access an existing PanelBar instance
29039         * var panelBar = $("#panelBar").data("kendoPanelBar");
29040         * // expand the element with ID, "item1"
29041         * panelBar.expand($("#item1"));
29042         * // expand the element with ID, "item2" without visual animations
29043         * panelBar.expand($("#item2"), false);
29044         * // expand all list items that start with ID, "item"
29045         * panelBar.expand($('[id^="item"]'));
29046         *
29047         * @param {Selector} element
29048         * The <strong>PanelBar</strong> item(s) to be expanded, expressed as a selector.
29049         *
29050         * @param {Boolean} [useAnimation]
29051         * Temporariliy enables (<b>true</b>) or disables (<b>false</b>) any visual animation(s) when expanding items.
29052         *
29053         * @returns {PanelBar}
29054         * Returns the PanelBar object to support chaining.
29055         *
29056         */
29057        expand: function (element, useAnimation) {
29058            var that = this,
29059                animBackup = {};
29060            useAnimation = useAnimation !== false;
29061            element = this.element.find(element);
29062
29063            element.each(function (index, item) {
29064                item = $(item);
29065                var groups = item.find(GROUPS).add(item.find(CONTENTS));
29066
29067                if (!item.hasClass(DISABLEDCLASS) && groups.length > 0) {
29068
29069                    if (that.options.expandMode == SINGLE && that._collapseAllExpanded(item)) {
29070                        return that;
29071                    }
29072
29073                    element.find(HIGHLIGHTEDCLASS).removeClass(HIGHLIGHTEDCLASS.substr(1));
29074                    item.addClass(HIGHLIGHTEDCLASS.substr(1));
29075
29076                    if (!useAnimation) {
29077                        animBackup = that.options.animation;
29078                        that.options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
29079                    }
29080
29081                    if (!that._triggerEvent(EXPAND, item)) {
29082                        that._toggleItem(item, false, null);
29083                    }
29084
29085                    if (!useAnimation) {
29086                        that.options.animation = animBackup;
29087                    }
29088                }
29089            });
29090
29091            return that;
29092        },
29093
29094        /**
29095         *
29096         * Collapses the specified item(s) of a <strong>PanelBar</strong>.
29097         *
29098         * @example
29099         * // access an existing PanelBar instance
29100         * var panelBar = $("#panelBar").data("kendoPanelBar");
29101         * // collapse the element with ID, "item1"
29102         * panelBar.collapse($("#item1"));
29103         * // collapse the element with ID, "item2" without visual animations
29104         * panelBar.collapse($("#item2"), false);
29105         * // collapse all list items that start with ID, "item"
29106         * panelBar.collapse($('[id^="item"]'));
29107         *
29108         * @param {Selector} element
29109         * The <strong>PanelBar</strong> item(s) to be collapsed, expressed as a string containing a selector
29110         * expression or represented by a <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
29111         *
29112         * @param {Boolean} [useAnimation]
29113         * Temporarily enables (<strong>true</strong>) or disables (<strong>false</strong>) any visual animation(s)
29114         * when collapsing items.
29115         *
29116         * @returns {PanelBar}
29117         * Returns the PanelBar object to support chaining.
29118         *
29119         */
29120        collapse: function (element, useAnimation) {
29121            var that = this,
29122                animBackup = {};
29123            useAnimation = useAnimation !== false;
29124            element = that.element.find(element);
29125
29126            element.each(function (index, item) {
29127                item = $(item);
29128                var groups = item.find(GROUPS).add(item.find(CONTENTS));
29129
29130                if (!item.hasClass(DISABLEDCLASS) && groups.is(VISIBLE)) {
29131                    item.removeClass(HIGHLIGHTEDCLASS.substr(1));
29132
29133                    if (!useAnimation) {
29134                        animBackup = that.options.animation;
29135                        that.options.animation = { expand: { show: true, effects: {} }, collapse: { hide:true, effects: {} } };
29136                    }
29137
29138                    if (!that._triggerEvent(COLLAPSE, item)) {
29139                        that._toggleItem(item, true, null);
29140                    }
29141
29142                    if (!useAnimation) {
29143                        that.options.animation = animBackup;
29144                    }
29145                }
29146
29147            });
29148
29149            return that;
29150        },
29151
29152        _toggleDisabled: function (element, enable) {
29153            element = this.element.find(element);
29154            element
29155                .toggleClass(defaultState, enable)
29156                .toggleClass(DISABLEDCLASS.substr(1), !enable);
29157        },
29158
29159        /**
29160         *
29161         * Selects the specified item of the <strong>PanelBar</strong>. If this method is invoked without arguments, it
29162         * returns the currently selected item.
29163         *
29164         * @param {String | Selector} element
29165         * The <strong>PanelBar</strong> item to be selected, expressed as a string containing a selector expression or
29166         * represented by a <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
29167         *
29168         * @example
29169         * // access an existing PanelBar instance
29170         * var panelBar = $("#panelBar").data("kendoPanelBar");
29171         * // select the item with ID, "item1"
29172         * panelBar.select("#item1");
29173         *
29174         */
29175        select: function (element) {
29176            var that = this;
29177            element = that.element.find(element);
29178
29179            if (arguments.length === 0) {
29180                return that.element.find(".k-item > " + SELECTEDCLASS).parent();
29181            }
29182
29183            element.each(function (index, item) {
29184                item = $(item);
29185                var link = item.children("." + LINK);
29186
29187                if (item.is(DISABLEDCLASS)) {
29188                    return that;
29189                }
29190
29191                $(SELECTEDCLASS, that.element).removeClass(SELECTEDCLASS.substr(1));
29192                $(HIGHLIGHTEDCLASS, that.element).removeClass(HIGHLIGHTEDCLASS.substr(1));
29193
29194                link.addClass(SELECTEDCLASS.substr(1));
29195                link.parentsUntil(that.element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTEDCLASS.substr(1));
29196            });
29197
29198            return that;
29199        },
29200
29201        /**
29202         *
29203         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the specified item(s) of the
29204         * <strong>PanelBar</strong>.
29205         *
29206         * @example
29207         * // access an existing PanelBar instance
29208         * var panelBar = $("#panelBar").data("kendoPanelBar");
29209         * // enable the item of the PanelBar with ID, "item1"
29210         * panelBar.enable($("#item1"), true);
29211         * // disable the currently selected item of the PanelBar
29212         * var item = panelBar.select();
29213         * panelBar.enable(item, false);
29214         * // disable all list items that start with ID, "item"
29215         * panelBar.enable($('[id^="item"]'), false);
29216         *
29217         * @param {String | Selector} element
29218         * The <strong>PanelBar</strong> item(s) to be enabled (<b>true</b>) or disabled (<b>false</b>), expressed as a
29219         * string containing a selector expression or represented by a
29220         * <a href="http://api.jquery.com/category/selectors/">jQuery selector</a>.
29221         *
29222         * @param {Boolean} enable
29223         * The desired state - enabled (<strong>true</strong>) or disabled (<strong>false</strong>) - of the target
29224         * element(s).
29225         *
29226         */
29227        enable: function (element, state) {
29228            this._toggleDisabled(element, state !== false);
29229
29230            return this;
29231        },
29232
29233        disable: function (element) {
29234            this._toggleDisabled(element, false);
29235
29236            return this;
29237        },
29238
29239        /**
29240         *
29241         * Appends an item to the PanelBar.
29242         *
29243         * @param {Selector} item
29244         * Target item, specified as the JSON representation of an object. You can pass item text, content or
29245         * contentUrl here. Can handle an HTML string or array of such strings or JSON.
29246         *
29247         * @param {Item} referenceItem
29248         * A reference item to append the new item in
29249         *
29250         * @returns {PanelBar}
29251         * Returns the PanelBar object to support chaining.
29252         *
29253         * @example
29254         * var panelBar = $("#panelBar").data("kendoPanelBar");
29255         * panelBar.append(
29256         *     [
29257         *         {
29258         *             text: "Item 1",
29259         *             url: "http://www.kendoui.com/"                  // link URL if navigation is needed (optional)
29260         *         },
29261         *         {
29262         *             text: "<b>Item 2</b>",
29263         *             encoded: false,                                 // Allows use of HTML for item text
29264         *             content: "text"                                 // content within an item
29265         *         },
29266         *         {
29267         *             text: "Item 3",
29268         *             contentUrl: "partialContent.html"               // content URL to load within an item
29269         *         },
29270         *         {
29271         *             text: "Item 4",
29272         *             imageUrl: "http://www.kendoui.com/test.jpg",    // item image URL, optional
29273         *             expanded: true,                                 // item is rendered expanded
29274         *             items: [{                                       // Sub item collection.
29275         *                 text: "Sub Item 1"
29276         *             },
29277         *             {
29278         *                 text: "Sub Item 2"
29279         *             }]
29280         *         },
29281         *         {
29282         *             text: "Item 5",
29283         *             // item image sprite CSS class, optional
29284         *             spriteCssClass: "imageClass3"
29285         *         }
29286         *     ],
29287         *     referenceItem
29288         * );
29289         *
29290         */
29291        append: function (item, referenceItem) {
29292            referenceItem = this.element.find(referenceItem);
29293
29294            var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find(GROUPS) : null);
29295
29296            each(inserted.items, function (idx) {
29297                inserted.group.append(this);
29298
29299                var contents = inserted.contents[idx];
29300                if (contents) {
29301                    $(this).append(contents);
29302                }
29303
29304                updateFirstLast(this);
29305            });
29306
29307            updateArrow(referenceItem);
29308            updateFirstLast(inserted.group.find(".k-first, .k-last"));
29309            inserted.group.height("auto");
29310
29311            return this;
29312        },
29313
29314        /**
29315         *
29316         * Inserts a PanelBar item before the specified referenceItem
29317         *
29318         * @param {Selector} item
29319         * Target item, specified as a JSON object. You can pass item text, content or contentUrl here. Can handle an
29320         * TML string or array of such strings or JSON.
29321         *
29322         * @param {Item} referenceItem
29323         * A reference item to insert the new item before.
29324         *
29325         * @returns {PanelBar}
29326         * Returns the PanelBar object to support chaining.
29327         *
29328         * @example
29329         * panelBar.insertBefore(
29330         *     [{
29331         *         text: "Item 1",
29332         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
29333         *     },
29334         *     {
29335         *         text: "<b>Item 2</b>",
29336         *         encoded: false,                              // Allows use of HTML for item text
29337         *         content: "text"                              // Content for the content element
29338         *     },
29339         *     {
29340         *         text: "Item 3",
29341         *         contentUrl: "partialContent.html"            // From where to load the item content
29342         *     },
29343         *     {
29344         *         text: "Item 4",
29345         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
29346         *         expanded: true,                              // item is rendered expanded
29347         *         items: [{                                    // Sub item collection.
29348         *              text: "Sub Item 1"
29349         *         },
29350         *         {
29351         *              text: "Sub Item 2"
29352         *         }]
29353         *     },
29354         *     {
29355         *         text: "Item 5",
29356         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
29357         *     }],
29358         *     referenceItem
29359         * );
29360         *
29361         */
29362        insertBefore: function (item, referenceItem) {
29363            referenceItem = this.element.find(referenceItem);
29364
29365            var inserted = this._insert(item, referenceItem, referenceItem.parent());
29366
29367            each(inserted.items, function (idx) {
29368                referenceItem.before(this);
29369
29370                var contents = inserted.contents[idx];
29371                if (contents) {
29372                    $(this).append(contents);
29373                }
29374
29375                updateFirstLast(this);
29376            });
29377
29378            updateFirstLast(referenceItem);
29379            inserted.group.height("auto");
29380
29381            return this;
29382        },
29383
29384        /**
29385         * Inserts a PanelBar item after the specified referenceItem
29386         * @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.
29387         * @param {Item} referenceItem A reference item to insert the new item after
29388         * @example
29389         * panelBar.insertAfter(
29390         *     [{
29391         *         text: "Item 1",
29392         *         url: "http://www.kendoui.com"                // Link URL if navigation is needed, optional.
29393         *     },
29394         *     {
29395         *         text: "<b>Item 2</b>",
29396         *         encoded: false,                              // Allows use of HTML for item text
29397         *         content: "text"                              // Content for the content element
29398         *     },
29399         *     {
29400         *         text: "Item 3",
29401         *         contentUrl: "partialContent.html"            // From where to load the item content
29402         *     },
29403         *     {
29404         *         text: "Item 4",
29405         *         imageUrl: "http://www.kendoui.com/test.jpg", // Item image URL, optional.
29406         *         expanded: true,                              // item is rendered expanded
29407         *         items: [{                                    // Sub item collection.
29408         *              text: "Sub Item 1"
29409         *         },
29410         *         {
29411         *              text: "Sub Item 2"
29412         *         }]
29413         *     },
29414         *     {
29415         *         text: "Item 5",
29416         *         spriteCssClass: "imageClass3"                // Item image sprite CSS class, optional.
29417         *     }],
29418         *     referenceItem
29419         * );
29420         */
29421        insertAfter: function (item, referenceItem) {
29422            referenceItem = this.element.find(referenceItem);
29423
29424            var inserted = this._insert(item, referenceItem, referenceItem.parent());
29425
29426            each(inserted.items, function (idx) {
29427                referenceItem.after(this);
29428
29429                var contents = inserted.contents[idx];
29430                if (contents) {
29431                    $(this).append(contents);
29432                }
29433
29434                updateFirstLast(this);
29435            });
29436
29437            updateFirstLast(referenceItem);
29438            inserted.group.height("auto");
29439
29440            return this;
29441        },
29442
29443        /**
29444         *
29445         * Removes the specified PanelBar item(s).
29446         *
29447         * @param {Selector} element Target item selector.
29448         *
29449         * @example
29450         * // get a reference to the panel bar
29451         * var panelBar = $("#panelBar").data("kendoPanelBar");
29452         * // remove Item 1
29453         * panelBar.remove("#Item1");
29454         *
29455         */
29456        remove: function (element) {
29457            element = this.element.find(element);
29458
29459            var that = this,
29460                parent = element.parentsUntil(that.element, ITEM),
29461                group = element.parent("ul");
29462
29463            element.remove();
29464
29465            if (group && !group.hasClass("k-panelbar") && !group.children(ITEM).length) {
29466                group.remove();
29467            }
29468
29469            if (parent.length) {
29470                parent = parent.eq(0);
29471
29472                updateArrow(parent);
29473                updateFirstLast(parent);
29474            }
29475
29476            return that;
29477        },
29478
29479        /**
29480         * Reloads the content of a <strong>PanelBar</strong> from an AJAX request.
29481         * @param {Selector} element Target element
29482         * @example
29483         * // get a reference to the panel bar
29484         * var panelBar = $("#panelBar").data("kendoPanelBar");
29485         * // reload the panel basr
29486         * panelBar.reload();
29487         */
29488        reload: function (element) {
29489            var that = this;
29490            element = that.element.find(element);
29491
29492            element.each(function () {
29493                var item = $(this);
29494
29495                that._ajaxRequest(item, item.children("." + CONTENT), !item.is(VISIBLE));
29496            });
29497        },
29498
29499        _insert: function (item, referenceItem, parent) {
29500            var that = this,
29501                items, contents = [];
29502
29503            if (!referenceItem || !referenceItem.length) {
29504                parent = that.element;
29505            }
29506
29507            var plain = $.isPlainObject(item),
29508                groupData = {
29509                    firstLevel: parent.hasClass("k-panelbar"),
29510                    expanded: parent.parent().hasClass("k-state-active"),
29511                    length: parent.children().length
29512                };
29513
29514            if (referenceItem && !parent.length) {
29515                parent = $(PanelBar.renderGroup({ group: groupData })).appendTo(referenceItem);
29516            }
29517
29518            if (plain || $.isArray(item)) { // is JSON
29519                items = $.map(plain ? [ item ] : item, function (value, idx) {
29520                            if (typeof value === "string") {
29521                                return $(value);
29522                            } else {
29523                                return $(PanelBar.renderItem({
29524                                    group: groupData,
29525                                    item: extend(value, { index: i
29525dx })
29526                                }));
29527                            }
29528                        });
29529                contents = $.map(plain ? [ item ] : item, function (value, idx) {
29530                            if (value.content || value.contentUrl) {
29531                                return $(PanelBar.renderContent({
29532                                    item: extend(value, { index: idx })
29533                                }));
29534                            } else {
29535                                return false;
29536                            }
29537                        });
29538            } else {
29539                items = $(item);
29540
29541                updateItemClasses(items, that.element);
29542            }
29543
29544            return { items: items, group: parent, contents: contents };
29545        },
29546
29547        _toggleHover: function(e) {
29548            var target = $(e.currentTarget);
29549
29550            if (!target.parents("li" + DISABLEDCLASS).length) {
29551                target.toggleClass("k-state-hover", e.type == MOUSEENTER);
29552            }
29553        },
29554
29555        _updateClasses: function() {
29556            var that = this;
29557
29558            that.element.addClass("k-widget k-reset k-header k-panelbar");
29559
29560            var panels = that.element
29561                                .find("li > ul")
29562                                .not(function () {
29563                                        return $(this).parentsUntil(".k-panelbar", "div").length;
29564                                    })
29565                                .addClass("k-group k-panel")
29566                                .add(that.element);
29567
29568            var items = panels
29569                            .find("> li:not(" + ACTIVECLASS + ") > ul")
29570                            .css({ display: "none" })
29571                            .end()
29572                            .find("> li");
29573
29574            items.each(function () {
29575                updateItemClasses(this, that.element);
29576            });
29577
29578            updateArrow(items);
29579            updateFirstLast(items);
29580        },
29581
29582        _click: function (e) {
29583            var that = this,
29584                target = $(e.currentTarget),
29585                element = that.element;
29586
29587            if (target.parents("li" + DISABLEDCLASS).length) {
29588                return;
29589            }
29590
29591            if (target.closest(".k-widget")[0] != element[0]) {
29592                return;
29593            }
29594
29595            var link = target.closest("." + LINK),
29596                item = link.closest(ITEM);
29597
29598            $(SELECTEDCLASS, element).removeClass(SELECTEDCLASS.substr(1));
29599            $(HIGHLIGHTEDCLASS, element).removeClass(HIGHLIGHTEDCLASS.substr(1));
29600
29601            link.addClass(SELECTEDCLASS.substr(1));
29602            link.parentsUntil(that.element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTEDCLASS.substr(1));
29603
29604            var contents = item.find(GROUPS).add(item.find(CONTENTS)),
29605                href = link.attr(HREF),
29606                isAnchor = link.data(CONTENTURL) || (href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1));
29607
29608            if (contents.data("animating")) {
29609                return;
29610            }
29611
29612            if (that._triggerEvent(SELECT, item)) {
29613                e.preventDefault();
29614            }
29615
29616            if (isAnchor || contents.length) {
29617                e.preventDefault();
29618            } else {
29619                return;
29620            }
29621
29622            if (that.options.expandMode == SINGLE) {
29623                if (that._collapseAllExpanded(item)) {
29624                    return;
29625                }
29626            }
29627
29628            if (contents.length) {
29629                var visibility = contents.is(VISIBLE);
29630
29631                if (!that._triggerEvent(!visibility ? EXPAND : COLLAPSE, item)) {
29632                    that._toggleItem(item, visibility, e);
29633                }
29634            }
29635        },
29636
29637        _toggleItem: function (element, isVisible, e) {
29638            var that = this,
29639                childGroup = element.find(GROUPS);
29640
29641            if (childGroup.length) {
29642
29643                this._toggleGroup(childGroup, isVisible);
29644
29645                if (e) {
29646                    e.preventDefault();
29647                }
29648            } else {
29649
29650                var content = element.find("> ."  + CONTENT);
29651
29652                if (content.length) {
29653                    if (e) {
29654                        e.preventDefault();
29655                    }
29656
29657                    if (!content.is(EMPTY)) {
29658                        that._toggleGroup(content, isVisible);
29659                    } else {
29660                        that._ajaxRequest(element, content, isVisible);
29661                    }
29662                }
29663            }
29664        },
29665
29666        _toggleGroup: function (element, visibility) {
29667            var that = this,
29668                animationSettings = that.options.animation,
29669                animation = animationSettings.expand,
29670                collapse = extend({}, animationSettings.collapse),
29671                hasCollapseAnimation = collapse && "effects" in collapse;
29672
29673            if (element.is(VISIBLE) != visibility) {
29674                return;
29675            }
29676
29677            if (visibility) { // Set initial height on visible items (due to a Chrome bug/feature).
29678                element.css("height", element.height());
29679            }
29680            element.css("height");
29681
29682            element
29683                .parent()
29684                .toggleClass(defaultState, visibility)
29685                .toggleClass(ACTIVECLASS.substr(1), !visibility)
29686                .find("> .k-link > .k-icon")
29687                    .toggleClass("k-arrow-up", !visibility)
29688                    .toggleClass("k-panelbar-collapse", !visibility)
29689                    .toggleClass("k-arrow-down", visibility)
29690                    .toggleClass("k-panelbar-expand", visibility);
29691
29692            if (visibility) {
29693                animation = extend( hasCollapseAnimation ? collapse
29694                                    : extend({ reverse: true }, animation), { show: false, hide: true });
29695            } else {
29696                animation = extend( { complete: function (element) {
29697                    that._triggerEvent(ACTIVATE, element.closest(ITEM));
29698                } }, animation );
29699            }
29700
29701            element
29702                .kendoStop(true, true)
29703                .kendoAnimate( animation );
29704        },
29705
29706        _collapseAllExpanded: function (item) {
29707            var that = this, children, stopExpand = false;
29708
29709            if (item.find("> ." + LINK).hasClass("k-header")) {
29710                var groups = item.find(GROUPS).add(item.find(CONTENTS));
29711
29712                if (groups.is(VISIBLE)) {
29713                    stopExpand = true;
29714                }
29715
29716                if (!(groups.is(VISIBLE) || groups.length === 0)) {
29717                    children = $(that.element).children();
29718                    children.find(GROUPS).add(children.find(CONTENTS))
29719                            .filter(function () { return $(this).is(VISIBLE); })
29720                            .each(function (index, content) {
29721                                content = $(content);
29722
29723                                stopExpand = that._triggerEvent(COLLAPSE, content.closest(ITEM));
29724                                if (!stopExpand) {
29725                                    that._toggleGroup(content, true);
29726                                }
29727                            });
29728                }
29729
29730                return stopExpand;
29731            }
29732        },
29733
29734        _ajaxRequest: function (element, contentElement, isVisible) {
29735
29736            var that = this,
29737                statusIcon = element.find(".k-panelbar-collapse, .k-panelbar-expand"),
29738                link = element.find("." + LINK),
29739                loadingIconTimeout = setTimeout(function () {
29740                    statusIcon.addClass("k-loading");
29741                }, 100),
29742                data = {};
29743
29744            $.ajax({
29745                type: "GET",
29746                cache: false,
29747                url: link.data(CONTENTURL) || link.attr(HREF),
29748                dataType: "html",
29749                data: data,
29750
29751                error: function (xhr, status) {
29752                    statusIcon.removeClass("k-loading");
29753                    if (that.trigger(ERROR, { xhr: xhr, status: status })) {
29754                        this.complete();
29755                    }
29756                },
29757
29758                complete: function () {
29759                    clearTimeout(loadingIconTimeout);
29760                    statusIcon.removeClass("k-loading");
29761                },
29762
29763                success: function (data, textStatus) {
29764                    contentElement.html(data);
29765                    that._toggleGroup(contentElement, isVisible);
29766
29767                    that.trigger(CONTENTLOAD, { item: element[0], contentElement: contentElement[0] });
29768                }
29769            });
29770        },
29771
29772        _triggerEvent: function (eventName, element) {
29773            var that = this;
29774
29775            return that.trigger(eventName, { item: element[0] });
29776        }
29777    });
29778
29779    // client-side rendering
29780    extend(PanelBar, {
29781        renderItem: function (options) {
29782            options = extend({ panelBar: {}, group: {} }, options);
29783
29784            var empty = templates.empty,
29785                item = options.item;
29786
29787            return templates.item(extend(options, {
29788                image: item.imageUrl ? templates.image : empty,
29789                sprite: item.spriteCssClass ? templates.sprite : empty,
29790                itemWrapper: templates.itemWrapper,
29791                arrow: item.items || item.content || item.contentUrl ? templates.arrow : empty,
29792                subGroup: PanelBar.renderGroup
29793            }, rendering));
29794        },
29795
29796        renderGroup: function (options) {
29797            return templates.group(extend({
29798                renderItems: function(options) {
29799                    var html = "",
29800                        i = 0,
29801                        items = options.items,
29802                        len = items ? items.length : 0,
29803                        group = extend({ length: len }, options.group);
29804
29805                    for (; i < len; i++) {
29806                        html += PanelBar.renderItem(extend(options, {
29807                            group: group,
29808                            item: extend({ index: i }, items[i])
29809                        }));
29810                    }
29811
29812                    return html;
29813                }
29814            }, options, rendering));
29815        },
29816
29817        renderContent: function (options) {
29818            return templates.content(extend(options, rendering));
29819        }
29820    });
29821
29822    kendo.ui.plugin(PanelBar);
29823
29824})(jQuery);
29825/**
29826 * @fileOverview Provides a TabStrip implementation which can be used to display a collection of tabs with associated
29827 * content
29828 */
29829
29830(function ($, undefined) {
29831    /**
29832     *
29833     * @name kendo.ui.TabStrip.Description
29834     *
29835     * @section
29836     * <p>
29837     *  A <strong>TabStrip</strong> displays a collection of tabs with associated content. It is composed of an
29838     *  unordered list of items - representing tabs - and a collection of div elements, which contain the content for
29839     *  each tab.
29840     * </p>
29841     * <h3>Getting Started</h3>
29842     *
29843     * @exampleTitle Create an unordered list for tabs with associated div elements for content
29844     * @example
29845     * <div id="tabStrip">
29846     *     <ul>
29847     *         <li>First tab</li>
29848     *         <li>Second tab</li>
29849     *     </ul>
29850     *     <div>First tab content</div>
29851     *     <div>Second tab content</div>
29852     * </div>
29853     *
29854     * @section
29855     * <p>
29856     *  Initialization of a <strong>TabStrip</strong> should occur after the DOM is fully loaded. It is recommended
29857     *  that initialization the <strong>TabStrip</strong> occur within a handler is provided to $(document).ready().
29858     * </p>
29859     *
29860     * @exampleTitle Initialize a TabStrip using a selector within $(document).ready()
29861     * @example
29862     * $(document).ready(function() {
29863     *     $("#tabStrip").kendoTabStrip();
29864     * });
29865     *
29866     * @exampleTitle Initialize the TabStrip using JSON data object
29867     * @example
29868     * $(document).ready(function() {
29869     *     $("#tabstrip").kendoTabStrip({
29870     *         dataTextField: "text",
29871     *         dataContentField: "content",
29872     *         dataUrlField: "url",
29873     *         dataImageUrlField: "imageUrl",
29874     *         dataSpriteCssClass: "spriteCssClass",
29875     *         dataContentUrlField: "contentUrl",
29876     *         dataSource:
29877     *         [{
29878     *             text: "Item 1",
29879     *             url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
29880     *         },
29881     *         {
29882     *             text: "Item 2",
29883     *             content: "text"                             // Content for the content element
29884     *         },
29885     *         {
29886     *             text: "Item 3",
29887     *             contentUrl: "partialContent.html"           // From where to load the item content
29888     *         },
29889     *         {
29890     *             text: "Item 4",
29891     *             imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
29892     *         },
29893     *         {
29894     *             text: "Item 5",
29895     *             spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
29896     *         }]
29897     *     });
29898     * });
29899     *
29900     * @section
29901     * <p>
29902     *  The tabs of a <strong>TabStrip</strong> are not required to have content. Should a tab have no content, it is
29903     *  safe to omit its associated div.
29904     * </p>
29905     * <h3>Loading TabStrip content with AJAX</h3>
29906     * <p>
29907     *  While any valid technique for loading AJAX content can be used, a <strong>TabStrip</strong> supports loading
29908     *  content from URLs in an asynchronous manner. These URLs should return HTML fragments that can be loaded in a
29909     *  TabStrip content area.
29910     * </p>
29911     *
29912     * @exampleTitle Loading Tab content asynchronously
29913     * @example
29914     * <div id="tabstrip">
29915     *     <ul>
29916     *         <li>First Tab</li>
29917     *         <li>Second Tab</li>
29918     *     </ul>
29919     *     <div></div>
29920     *     <div></div>
29921     *  </div>
29922     *
29923     * @exampleTitle Initialize TabStrip and configure one tab with async content loading
29924     * @example
29925     * $(document).ready(function(){
29926     *     $("#tabstrip").kendoTabStrip({
29927     *         contentUrls: [null, "html-content-snippet.html"]
29928     *     });
29929     *  });
29930     *
29931     * @section
29932     * <h3>Dynamically Configure TabStrip Tabs</h3>
29933     * <p>
29934     *  The <strong>TabStrip</strong> API provides several methods for dynamically adding or removing tabs. To add
29935     *  tabs, provide the new item as a JSON object along with a reference item that will be used to determine the
29936     *  placement in the <strong>TabStrip</strong>. Note: append() does not require a reference item.
29937     * <p>
29938     * <p>
29939     *  A reference item is simply a target DOM element of a tab that already exists in the TabStrip. Any valid
29940     *  selector may be used to obtain a reference to the target item.
29941     * </p>
29942     * <p>Removing an item requires a reference to the target element.</p>
29943     *
29944     * @exampleTitle Dynamically add a new tab
29945     * @example
29946     * var tabStrip = $("#tabStrip").data("kendoTabStrip");
29947     * tabStrip.insertAfter(
29948     *     { text: "New Tab" },
29949     *     tabstrip.tabGroup.children("li:last")
29950     * );
29951     *
29952     * @section
29953     * <h3>Selecting a Tab on Initial Load</h3>
29954     * <p>
29955     *  It is possible to select a tab and display its associated content upon its initial load. There are two (2) ways
29956     *  to accomplish this task:
29957     * </p>
29958     * <ol>
29959     *  <li>Add a "k-state-active" class to the DOM element of the tab</li>
29960     *  <li>Use select() to target and select a tab either by selector or index</li>
29961     * </ol>
29962     * <p>Both approaches produce the same result.</p>
29963     *
29964     * @exampleTitle Selecting a default tab manually using HTML
29965     * @example
29966     * <div id="tabstrip">
29967     *     <ul>
29968     *         <li class="k-state-active">First Tab</li>
29969     *         <li>Second Tab</li>
29970     *     </ul>
29971     *     <div></div>
29972     *     <div></div>
29973     * </div>
29974     *
29975     * @exampleTitle Initialize a TabStrip and select first tab via select(element)
29976     * @example
29977     * $(document).ready(function(){
29978     *     var tabstrip = $("#tabstrip").kendoTabStrip().data("kendoTabStrip");
29979     *     tabstrip.select(tabstrip.tabGroup.children("li:first"));
29980     * });
29981     *
29982     * @exampleTitle Initialize a TabStrip and select first tab via select(index)
29983     * @example
29984     * $(document).ready(function(){
29985     *     var tabstrip = $("#tabstrip").kendoTabStrip().data("kendoTabStrip");
29986     *     tabstrip.select(1);
29987     * });
29988     *
29989     * @section
29990     * <h3>Accessing an Existing TabStrip</h3>
29991     * <p>
29992     *  You can reference an existing <b>TabStrip</b> instance via
29993     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
29994     *  use the API to control its behavior.
29995     * </p>
29996     *
29997     * @exampleTitle Accessing an existing TabStrip instance
29998     * @example
29999     * var tabStrip = $("#tabStrip").data("kendoTabStrip");
30000     *
30001     */
30002    var kendo = window.kendo,
30003        ui = kendo.ui,
30004        map = $.map,
30005        each = $.each,
30006        trim = $.trim,
30007        extend = $.extend,
30008        template = kendo.template,
30009        Widget = ui.Widget,
30010        excludedNodesRegExp = /^(a|div)$/i,
30011        IMG = "img",
30012        HREF = "href",
30013        LINK = "k-link",
30014        LAST = "k-last",
30015        CLICK = "click",
30016        ERROR = "error",
30017        EMPTY = ":empty",
30018        IMAGE = "k-image",
30019        FIRST = "k-first",
30020        SELECT = "select",
30021        ACTIVATE = "activate",
30022        CONTENT = "k-content",
30023        CONTENTURL = "contentUrl",
30024        MOUSEENTER = "mouseenter",
30025        MOUSELEAVE = "mouseleave",
30026        CONTENTLOAD = "contentLoad",
30027        CLICKABLEITEMS = ".k-tabstrip-items > .k-item:not(.k-state-disabled)",
30028        HOVERABLEITEMS = ".k-tabstrip-items > .k-item:not(.k-state-disabled):not(.k-state-active)",
30029        DISABLEDLINKS = ".k-tabstrip-items > .k-state-disabled .k-link",
30030        DISABLEDSTATE = "k-state-disabled",
30031        DEFAULTSTATE = "k-state-default",
30032        ACTIVESTATE = "k-state-active",
30033        HOVERSTATE = "k-state-hover",
30034        TABONTOP = "k-tab-on-top",
30035
30036        templates = {
30037            content: template(
30038                "<div class='k-content'#= contentAttributes(data) #>#= content(item) #</div>"
30039            ),
30040            itemWrapper: template(
30041                "<#= tag(item) # class='k-link'#= contentUrl(item) ##= textAttributes(item) #>" +
30042                    "#= image(item) ##= sprite(item) ##= text(item) #" +
30043                "</#= tag(item) #>"
30044            ),
30045            item: template(
30046                "<li class='#= wrapperCssClass(group, item) #'>" +
30047                    "#= itemWrapper(data) #" +
30048                "</li>"
30049            ),
30050            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
30051            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
30052            empty: template("")
30053        },
30054
30055        rendering = {
30056            wrapperCssClass: function (group, item) {
30057                var result = "k-item",
30058                    index = item.index;
30059
30060                if (item.enabled === false) {
30061                    result += " k-state-disabled";
30062                } else {
30063                    result += " k-state-default";
30064                }
30065
30066                if (index === 0) {
30067                    result += " k-first";
30068                }
30069
30070                if (index == group.length-1) {
30071                    result += " k-last";
30072                }
30073
30074                return result;
30075            },
30076            textAttributes: function(item) {
30077                return item.url ? " href='" + item.url + "'" : "";
30078            },
30079            text: function(item) {
30080                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
30081            },
30082            tag: function(item) {
30083                return item.url ? "a" : "span";
30084            },
30085            contentAttributes: function(content) {
30086                return content.active !== true ? " style='display:none'" : "";
30087            },
30088            content: function(item) {
30089                return item.content ? item.content : item.contentUrl ? "" : "&nbsp;";
30090            },
30091            contentUrl: function(item) {
30092                return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
30093            }
30094        };
30095
30096    function updateTabClasses (tabs) {
30097        tabs.children(IMG)
30098            .addClass(IMAGE);
30099
30100        tabs.children("a")
30101            .addClass(LINK)
30102            .children(IMG)
30103            .addClass(IMAGE);
30104
30105        tabs.filter(":not([disabled]):not([class*=k-state-disabled])")
30106            .addClass(DEFAULTSTATE);
30107
30108        tabs.filter("li[disabled]")
30109            .addClass(DISABLEDSTATE)
30110            .removeAttr("disabled");
30111
30112        tabs.filter(":not([class*=k-state])")
30113            .children("a:focus")
30114            .parent()
30115            .addClass(ACTIVESTATE + " " + TABONTOP);
30116
30117        tabs.each(function() {
30118            var item = $(this);
30119
30120            if (!item.children("." + LINK).length) {
30121                item
30122                    .contents()      // exclude groups, real links, templates and empty text nodes
30123                    .filter(function() { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !trim(this.nodeValue))); })
30124                    .wrapAll("<a class='" + LINK + "'/>");
30125            }
30126        });
30127
30128    }
30129
30130    function updateFirstLast (tabGroup) {
30131        var tabs = tabGroup.children(".k-item");
30132
30133        tabs.filter(".k-first:not(:first-child)").removeClass(FIRST);
30134        tabs.filter(".k-last:not(:last-child)").removeClass(LAST);
30135        tabs.filter(":first-child").addClass(FIRST);
30136        tabs.filter(":last-child").addClass(LAST);
30137    }
30138
30139    var TabStrip = Widget.extend({/** @lends kendo.ui.TabStrip.prototype */
30140        /**
30141         *
30142         * Creates a TabStrip instance.
30143         *
30144         * @constructs
30145         * @extends kendo.ui.Widget
30146         * @class TabStrip UI widget
30147         *
30148         * @param {Selector} element DOM element
30149         * @param {Object} options Configuration options.
30150         *
30151         * @option {Object} [animation]
30152         * A collection of visual animations used when <strong>TabStrip</strong> tab are selected through
30153         * user interactions. Setting this option to <strong>false</strong> will disable all animations.
30154         *
30155         * _exampleTitle Defining custom animations when selecting tabs
30156         * _example
30157         * $("#tabstrip").kendoTabStrip({
30158         *     animation: {
30159         *         // fade-out current tab over 1000 milliseconds
30160         *         close: {
30161         *             duration: 1000,
30162         *             effects: "fadeOut"
30163         *         },
30164         *        // fade-in new tab over 500 milliseconds
30165         *        open: {
30166         *            duration: 500,
30167         *            effects: "fadeIn"
30168         *        }
30169         *    }
30170         * });
30171         *
30172         * @option {Object} [animation.open]
30173         * The visual animation(s) that will be used when the new tab is shown.
30174         *
30175         * _exampleTitle Defining a custom animation when new tab is shown that executes over 200 milliseconds
30176         * _example
30177         * $("#tabstrip").kendoTabStrip({
30178         *     animation: {
30179         *         open: {
30180         *             duration: 200,
30181         *             effects: "expand:vertical"
30182         *         }
30183         *     }
30184         * });
30185         *
30186         * @option {Number} [animation.open.duration] <200>
30187         * The number of milliseconds used for the visual animation when a new tab is shown.
30188         *
30189         * _exampleTitle Defining animation when a new tab is shown that executes over 1000 milliseconds
30190         * _example
30191         * $("#tabstrip").kendoTabStrip({
30192         *  animation: {
30193         *       open: {
30194         *           duration: 1000
30195         *       }
30196         *    }
30197         * });
30198         *
30199         * @option {String} [animation.open.effects] <"expand:vertical fadeIn">
30200         * A whitespace-separated string of animation effects that are used when a new tab is shown. Options include
30201         * <strong>"expand:vertical"</strong> and <strong>"fadeIn"</strong>.
30202         *
30203         * @option {Boolean} [animation.open.show] <true>
30204         *
30205         * @option {Object} [animation.close]
30206         * The visual animation(s) that will be used when the current tab is closed.
30207         *
30208         * _exampleTitle Defining a custom animation for the current tab that
30209         * executes over 200 milliseconds
30210         * _example
30211         * $("#tabstrip").kendoTabStrip({
30212         *     animation: {
30213         *         close: {
30214         *             duration: 200,
30215         *             effects: "fadeOut"
30216         *         }
30217         *     }
30218         * });
30219         *
30220         * @option {Number} [animation.close.duration] <200>
30221         * The number of milliseconds used for the visual animation when the current tab is closed.
30222         *
30223         * _exampleTitle Animating the current tab for 1000 milliseconds
30224         * _example
30225         * $("#tabstrip").kendoTabStrip({
30226         *     animation: {
30227         *         close: {
30228                       duration: 1000
30229                   }
30230         *   }
30231         * });
30232         *
30233         * @option {String} [animation.close.effects]
30234         * A whitespace-delimited string of animation effects that are utilized when the current tab
30235         * is closed. By default not specified - uses the opening animation with reverse.
30236         *
30237         * _exampleTitle Fading-out the current tab for 1000 milliseconds
30238         * _example
30239         * $("#tabstrip").kendoTabStrip({
30240         *     animation: {
30241         *         close: {
30242         *             duration: 1000,
30243         *             effects: "fadeOut"
30244         *         }
30245         *     }
30246         * });
30247         *
30248         * @option {String} [dataTextField] <""> Sets the field of the data item that provides the text name of the tab.
30249         * _example
30250         * $("#tabstrip").kendoTabStrip({
30251         *     dataTextField: "Name",
30252         *     dataSource: data
30253         * });
30254         *
30255         * @option {String} [dataContentField] <""> Sets the field of the data item that provides the text content of
30256         * the tab content element.
30257         * _example
30258         * $("#tabstrip").kendoTabStrip({
30259         *     dataTextField: "Name",
30260         *     dataContentField: "Content",
30261         *     dataSource: data
30262         * });
30263         *
30264         * @option {String} [dataImageUrlField] <""> Sets the field of the data item that provides the image URL of
30265         * the tab.
30266         * _example
30267         * $("#tabstrip").kendoTabStrip({
30268         *     dataTextField: "Name",
30269         *     dataImageUrlField: "ImageUrl",
30270         *     dataSource: data
30271         * });
30272         *
30273         * @option {String} [dataUrlField] <""> Sets the field of the data item that provides the link URL for the
30274         * tab.
30275         * _example
30276         * $("#tabstrip").kendoTabStrip({
30277         *     dataTextField: "Name",
30278         *     dataUrlField: "Url",
30279         *     dataSource: data
30280         * });
30281         *
30282         * @option {String} [dataSpriteCssClass] <""> Sets the field of the data item that provides the CSS class of
30283         * the tab.
30284         * _example
30285         * $("#tabstrip").kendoTabStrip({
30286         *     dataTextField: "Name",
30287         *     dataSpriteCssClass: "CssClass",
30288         *     dataSource: data
30289         * });
30290         *
30291         * @option {String} [dataContentUrlField] <""> Sets the field of the data item that provides the URL for
30292         * the ajax loaded tab content.
30293         * _example
30294         * $("#tabstrip").kendoTabStrip({
30295         *     dataTextField: "Name",
30296         *     dataContentUrlField: "ContentUrl",
30297         *     dataSource: data
30298         * });
30299         *
30300         * @option {Boolean} [collapsible] <false>
30301         * Specifies whether the TabStrip should be able to collapse completely when clicking an expanded tab.
30302         *
30303         * _exampleTitle Turning on tab collapsing
30304         * _example
30305         * $("#tabstrip").kendoTabStrip({
30306         *     collapsible: true
30307         * });
30308         *
30309         */
30310        init: function(element, options) {
30311            var that = this;
30312
30313            that._animations(options);
30314
30315            Widget.fn.init.call(that, element, options);
30316
30317            if (that.element.is("ul")) {
30318                that.wrapper = that.element.wrapAll("<div />").parent();
30319            } else {
30320                that.wrapper = that.element;
30321            }
30322
30323            options = that.options;
30324
30325            that.wrapper
30326                .delegate(CLICKABLEITEMS, CLICK, $.proxy(that._click, that))
30327                .delegate(HOVERABLEITEMS, MOUSEENTER + " " + MOUSELEAVE, that._toggleHover)
30328                .delegate(DISABLEDLINKS, CLICK, false);
30329
30330            that._updateClasses();
30331
30332            that._dataSource();
30333
30334            if (options.dataSource) {
30335                that.dataSource.fetch();
30336            }
30337
30338            if (that.options.contentUrls) {
30339                that.wrapper.find(".k-tabstrip-items > .k-item")
30340                    .each(function(index, item) {
30341                        $(item).find(">." + LINK).data(CONTENTURL, that.options.contentUrls[index]);
30342                    });
30343            }
30344
30345            var selectedItems = that.wrapper.find("li." + ACTIVESTATE),
30346                content = $(that.contentElement(selectedItems.parent().children().index(selectedItems)));
30347
30348            if (content.length > 0 && content[0].childNodes.length === 0) {
30349                that.activateTab(selectedItems.eq(0));
30350            }
30351
30352            kendo.notify(that);
30353        },
30354
30355        _dataSource: function() {
30356            var that = this;
30357
30358            if (that.dataSource && that._refreshHandler) {
30359                that.dataSource.unbind("change", that._refreshHandler);
30360            } else {
30361                that._refreshHandler = $.proxy(that.refresh, that);
30362            }
30363
30364            that.dataSource = kendo.data.DataSource.create(that.options.dataSource)
30365                                .bind("change", that._refreshHandler);
30366        },
30367
30368        setDataSource: function(dataSource) {
30369            this.options.dataSource = dataSource;
30370            this._dataSource();
30371            dataSource.fetch();
30372        },
30373
30374        _animations: function(options) {
30375            if (options && ("animation" in options) && !options.animation) {
30376                options.animation = { open: { effects: {}, show: true }, close: { effects: {} } }; // No animation
30377            }
30378        },
30379
30380        refresh: function(e) {
30381            var that = this,
30382                options = that.options,
30383                text = kendo.getter(options.dataTextField),
30384                content = kendo.getter(options.dataContentField),
30385                contentUrl = kendo.getter(options.dataContentUrlField),
30386                image = kendo.getter(options.dataImageUrlField),
30387                url = kendo.getter(options.dataUrlField),
30388                sprite = kendo.getter(options.dataSpriteCssClass),
30389                idx,
30390                tabs = [],
30391                tab,
30392                action,
30393                view = that.dataSource.view(),
30394                length;
30395
30396
30397            e = e || {};
30398            action = e.action;
30399
30400            if (action) {
30401               view = e.items;
30402            }
30403
30404            for (idx = 0, length = view.length; idx < length; idx ++) {
30405                tab = {
30406                    text: text(view[idx])
30407                };
30408
30409                if (options.dataContentField) {
30410                    tab.content = content(view[idx]);
30411                }
30412
30413                if (options.dataContentUrlField) {
30414                    tab.contentUrl = contentUrl(view[idx]);
30415                }
30416
30417                if (options.dataUrlField) {
30418                    tab.url = url(view[idx]);
30419                }
30420
30421                if (options.dataImageUrlField) {
30422                    tab.imageUrl = image(view[idx]);
30423                }
30424
30425                if (options.dataSpriteCssClass) {
30426                    tab.spriteCssClass = sprite(view[idx]);
30427                }
30428
30429                tabs[idx] = tab;
30430            }
30431
30432            if (e.action == "add") {
30433                if (e.index < that.tabGroup.children().length) {
30434                    that.insertBefore(tabs, that.tabGroup.children().eq(e.index));
30435                } else {
30436                    that.append(tabs);
30437                }
30438            } else if (e.action == "remove") {
30439                for (idx = 0; idx < view.length; idx++) {
30440                   that.remove(e.index);
30441                }
30442            } else if (e.action == "itemchange") {
30443                idx = that.dataSource.view().indexOf(view[0]);
30444                if (e.field === options.dataTextField) {
30445                    that.tabGroup.children().eq(idx).find(".k-link").text(view[0].get(e.field));
30446                }
30447            } else {
30448                that.trigger("dataBinding");
30449                that.append(tabs);
30450                that.trigger("dataBound");
30451            }
30452        },
30453
30454        value: function(value) {
30455            var that = this;
30456
30457            if (value !== undefined) {
30458                if (value != that.value()) {
30459                   that.tabGroup.children().each(function() {
30460                        if ($.trim($(this).text()) == value) {
30461                            that.select(this);
30462                        }
30463                   });
30464                }
30465            } else {
30466                return that.select().text();
30467            }
30468        },
30469
30470        items: function() {
30471            return this.tabGroup[0].children;
30472        },
30473
30474        setOptions: function(options) {
30475            var animation = this.options.animation;
30476
30477            this._animations(options);
30478
30479            options.animation = extend(true, animation, options.animation);
30480
30481            Widget.fn.setOptions.call(this, options);
30482        },
30483
30484        events: [
30485            /**
30486             *
30487             * Triggered before a tab is selected.
30488             *
30489             * @name kendo.ui.TabStrip#select
30490             * @event
30491             *
30492             * @param {Event} e
30493             *
30494             * @param {HTMLElement} e.item
30495             * The selected item chosen by a user.
30496             *
30497             * @param {Element} e.contentElement
30498             * The content element of the tab going to be selected.
30499             *
30500             * @exampleTitle Attach select event handler during initialization; detach via unbind()
30501             * @example
30502             * // event handler for select
30503             * var onSelect = function(e) {
30504             *     // access the selected item via e.item (HTMLElement)
30505             * };
30506             *
30507             * // attach select event handler during initialization
30508             * var tabStrip = $("#tabStrip").kendoTabStrip({
30509             *     select: onSelect
30510             * });
30511             *
30512             * // detach select event handler via unbind()
30513             * tabStrip.data("kendoTabStrip").unbind("select", onSelect);
30514             *
30515             * @exampleTitle Attach select event handler via bind(); detach via unbind()
30516             * @example
30517             * // event handler for select
30518             * var onSelect = function(e) {
30519             *     // access the selected item via e.item (HTMLElement)
30520             * };
30521             *
30522             * // attach select event handler via bind()
30523             * $("#tabStrip").data("kendoTabStrip").bind("select", onSelect);
30524             *
30525             * // detach select event handler via unbind()
30526             * $("#tabStrip").data("kendoTabStrip").unbind("select", onSelect);
30527             *
30528             */
30529            SELECT,
30530            /**
30531             * Triggered just after a tab is being made visible, but before the end of the animation
30532             *
30533             * @name kendo.ui.TabStrip#activate
30534             * @event
30535             *
30536             * @param {Event} e
30537             *
30538             * @param {HTMLElement} e.item
30539             * The activated tab.
30540             *
30541             * @param {Element} e.contentElement
30542             * The content element of the activated tab.
30543             *
30544             * @exampleTitle Attach activate event handler during initialization; detach via unbind()
30545             * @example
30546             * // event handler for activate
30547             * var onActivate = function(e) {
30548             *     // access the activated item via e.item (HTMLElement)
30549             * };
30550             *
30551             * // attach activate event handler during initialization
30552             * var tabStrip = $("#tabStrip").kendoTabStrip({
30553             *     activate: onActivate
30554             * });
30555             *
30556             * // detach activate event handler via unbind()
30557             * tabStrip.data("kendoTabStrip").unbind("activate", onActivate);
30558             *
30559             * @exampleTitle Attach activate event handler via bind(); detach via unbind()
30560             * @example
30561             * // event handler for activate
30562             * var onActivate = function(e) {
30563             *     // access the activated item via e.item (HTMLElement)
30564             * };
30565             *
30566             * // attach activate event handler via bind()
30567             * $("#tabStrip").data("kendoTabStrip").bind("activate", onActivate);
30568             *
30569             * // detach activate event handler via unbind()
30570             * $("#tabStrip").data("kendoTabStrip").unbind("activate", onActivate);
30571             *
30572             */
30573            ACTIVATE,
30574            /**
30575             *
30576             * Triggered when an AJAX request results in an error.
30577             *
30578             * @name kendo.ui.TabStrip#error
30579             * @event
30580             *
30581             * @param {Event} e
30582             *
30583             * @param {jqXHR} e.xhr
30584             * The jqXHR object used to load the content
30585             *
30586             * @param {String} e.status
30587             * The returned status.
30588             *
30589             */
30590            ERROR,
30591            /**
30592             *
30593             * Triggered when content is fetched from an AJAX request.
30594             *
30595             * @name kendo.ui.TabStrip#contentLoad
30596             * @event
30597             *
30598             * @param {Event} e
30599             *
30600             * @param {Element} e.item
30601             * The selected item
30602             *
30603             * @param {Element} e.contentElement
30604             * The loaded content element that is retrieved via AJAX.
30605             *
30606             */
30607            CONTENTLOAD,
30608            "change",
30609            "dataBinding",
30610            "dataBound"
30611        ],
30612
30613        options: {
30614            name: "TabStrip",
30615            dataTextField: "",
30616            dataContentField: "",
30617            dataImageUrlField: "",
30618            dataUrlField: "",
30619            dataSpriteCssClass: "",
30620            dataContentUrlField: "",
30621            animation: {
30622                open: {
30623                    effects: "expand:vertical fadeIn",
30624                    duration: 200,
30625                    show: true
30626                },
30627                close: { // if close animation effects are defined, they will be used instead of open.reverse
30628                    duration: 200
30629                }
30630            },
30631            collapsible: false
30632        },
30633
30634        /**
30635         *
30636         * Selects the specified tab(s) within a <strong>TabStrip</strong>. If called without arguments, it returns the
30637         * currently selected tab.
30638         *
30639         * @param {Selector/Index} element or index
30640         * The target tab(s), specified as a selector or index in the tab group.
30641         *
30642         * @example
30643         * tabStrip.select("#tab1");
30644         *
30645         * @example
30646         * tabStrip.select(1);
30647         *
30648         * @returns {TabStrip}
30649         * Returns the TabStrip object to support chaining.
30650         *
30651         */
30652        select: function (element) {
30653            var that = this;
30654
30655            if (arguments.length === 0) {
30656                return that.wrapper.find("li." + ACTIVESTATE);
30657            }
30658
30659            if (!isNaN(element)) {
30660                element = that.tabGroup.children().get(element);
30661            }
30662
30663            element = that.element.find(element);
30664            $(element).each(function (index, item) {
30665                item = $(item);
30666                if (!item.hasClass(ACTIVESTATE) && !that.trigger(SELECT, { item: item[0], contentElement: that.contentElement(item.index()) })) {
30667                    that.activateTab(item);
30668                }
30669            });
30670
30671            return that;
30672        },
30673
30674        /**
30675         *
30676         * Disables (<strong>false</strong>) or enables (<strong>true</strong>) a tab(s) of a <strong>TabStrip</strong>.
30677         *
30678         * @param {Selector} element
30679         * The target tab(s), specified as a selector, to be enabled (<strong>true</strong>) or disabled
30680         * (<strong>false</strong>).
30681         *
30682         * @param {Boolean} enable
30683         * Desired state of the tab(s) specified by the selector; enabled (<strong>true</strong>) or disabled
30684         * (<strong>false</strong>).
30685         *
30686         * @returns {TabStrip}
30687         * Returns the TabStrip object to support chaining.
30688         *
30689         */
30690        enable: function (element, state) {
30691            this._toggleDisabled(element, state !== false);
30692
30693            return this;
30694        },
30695
30696        /**
30697         *
30698         * Disables a tab(s) of a <strong>TabStrip</strong>.
30699         *
30700         * @param {Selector} element
30701         * The target tab(s), specified as a selector, to be disabled.
30702         *
30703         * @returns {TabStrip}
30704         * Returns the TabStrip object to support chaining.
30705         *
30706         */
30707        disable: function (element) {
30708            this._toggleDisabled(element, false);
30709
30710            return this;
30711        },
30712
30713        /**
30714         *
30715         * Reloads TabStrip tab(s) via AJAX.
30716         *
30717         * @param {Selector} element
30718         * The target tab(s), specified as a selector, to be reloaded via AJAX.
30719         *
30720         * @returns {TabStrip}
30721         * Returns the TabStrip object to support chaining.
30722         *
30723         */
30724        reload: function (element) {
30725            element = this.tabGroup.find(element);
30726            var that = this;
30727
30728            element.each(function () {
30729                var item = $(this),
30730                    contentUrl = item.find("." + LINK).data(CONTENTURL);
30731
30732                if (contentUrl) {
30733                    that.ajaxRequest(item, $(that.contentElement(item.index())), null, contentUrl);
30734                }
30735            });
30736
30737            return that;
30738        },
30739
30740        /**
30741         *
30742         * Appends a tab to the collection of tabs in a <strong>TabStrip</strong>.
30743         *
30744         * @param {Selector} tab
30745         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
30746         * HTML string or array of such strings or JSON.
30747         *
30748         * @returns {TabStrip}
30749         * Returns the TabStrip object to support chaining.
30750         *
30751         * @example
30752         * tabStrip.append(
30753         *     [{
30754         *         text: "Item 1",
30755         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
30756         *     },
30757         *     {
30758         *         text: "<b>Item 2</b>",
30759         *         encoded: false,                             // Allows use of HTML for item text
30760         *         content: "text"                             // Content for the content element
30761         *     },
30762         *     {
30763         *         text: "Item 3",
30764         *         contentUrl: "partialContent.html"           // From where to load the item content
30765         *     },
30766         *     {
30767         *         text: "Item 4",
30768         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
30769         *     },
30770         *     {
30771         *         text: "Item 5",
30772         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
30773         *     }]
30774         * );
30775         *
30776         */
30777        append: function (tab) {
30778            var that = this,
30779                inserted = that._create(tab);
30780
30781            each(inserted.tabs, function (idx) {
30782                that.tabGroup.append(this);
30783                that.wrapper.append(inserted.contents[idx]);
30784            });
30785
30786            updateFirstLast(that.tabGroup);
30787            that._updateContentElements();
30788
30789            return that;
30790        },
30791
30792        /**
30793         *
30794         * Inserts a newly-created tab before a specified tab.
30795         *
30796         * @param {Selector} item
30797         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
30798         * HTML string or array of such strings or JSON.
30799         *
30800         * @param {Item} referenceTab
30801         * A reference tab to insert the new item before
30802         *
30803         * @returns {TabStrip}
30804         * Returns the TabStrip object to support chaining.
30805         *
30806         * @example
30807         * tabStrip.insertBefore(
30808         *     [{
30809         *         text: "Item 1",
30810         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
30811         *     },
30812         *     {
30813         *         text: "<b>Item 2</b>",
30814         *         encoded: false,                             // Allows use of HTML for item text
30815         *         content: "text"                             // Content for the content element
30816         *     },
30817         *     {
30818         *         text: "Item 3",
30819         *         contentUrl: "partialContent.html"           // From where to load the item content
30820         *     },
30821         *     {
30822         *         text: "Item 4",
30823         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
30824         *     },
30825         *     {
30826         *         text: "Item 5",
30827         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
30828         *     }],
30829         *     referenceItem
30830         * );
30831         */
30832        insertBefore: function (tab, referenceTab) {
30833            var that = this,
30834                inserted = that._create(tab),
30835                referenceContent = $(that.contentElement(referenceTab.index()));
30836
30837            each(inserted.tabs, function (idx) {
30838                referenceTab.before(this);
30839                referenceContent.before(inserted.contents[idx]);
30840            });
30841
30842            updateFirstLast(that.tabGroup);
30843            that._updateContentElements();
30844
30845            return that;
30846        },
30847
30848        /**
30849         *
30850         * Inserts a newly-created tab after a specified tab.
30851         *
30852         * @param {Selector} item
30853         * Target tab, specified as a JSON object. You can pass tab text, content or contentUrl here. Can handle an
30854         * HTML string or array of such strings or JSON.
30855         *
30856         * @param {Item} referenceTab
30857         * A reference tab to insert the new item after.
30858         *
30859         * @returns {TabStrip}
30860         * Returns the TabStrip object to support chaining.
30861         *
30862         * @example
30863         * tabStrip.insertAfter(
30864         *     [{
30865         *         text: "Item 1",
30866         *         url: "http://www.kendoui.com"               // Link URL if navigation is needed, optional.
30867         *     },
30868         *     {
30869         *         text: "<b>Item 2</b>",
30870         *         encoded: false,                             // Allows use of HTML for item text
30871         *         content: "text"                             // Content for the content element
30872         *     },
30873         *     {
30874         *         text: "Item 3",
30875         *         contentUrl: "partialContent.html"           // From where to load the item content
30876         *     },
30877         *     {
30878         *         text: "Item 4",
30879         *         imageUrl: "http://www.kendoui.com/test.jpg" // Item image URL, optional.
30880         *     },
30881         *     {
30882         *         text: "Item 5",
30883         *         spriteCssClass: "imageClass3"               // Item image sprite CSS class, optional.
30884         *     }],
30885         *     referenceItem
30886         * );
30887         *
30888         */
30889        insertAfter: function (tab, referenceTab) {
30890            var that = this,
30891                inserted = that._create(tab),
30892                referenceContent = $(that.contentElement(referenceTab.index()));
30893
30894            each(inserted.tabs, function (idx) {
30895                referenceTab.after(this);
30896                referenceContent.after(inserted.contents[idx]);
30897            });
30898
30899            updateFirstLast(that.tabGroup);
30900            that._updateContentElements();
30901
30902            return that;
30903        },
30904
30905        /**
30906         *
30907         * Removes a specified tab from a TabStrip.
30908         *
30909         * @param {Selector} element
30910         * The target tab(s), specified as a selector, to be removed.
30911         *
30912         * @returns {TabStrip}
30913         * Returns the TabStrip object to support chaining.
30914         *
30915         * @exampleTitle Remove a tab with ID, tab1 from a TabStrip
30916         * @example
30917         * tabStrip.remove("#tab1");
30918         *
30919         */
30920        remove: function (element) {
30921            var that = this,
30922                type = typeof element,
30923                content;
30924
30925            if (type === "string") {
30926                element = that.tabGroup.find(element);
30927            } else if (type === "number") {
30928                element = that.tabGroup.children().eq(element);
30929            }
30930
30931            content = $(that.contentElement(element.index()));
30932
30933            content.remove();
30934            element.remove();
30935
30936            that._updateContentElements();
30937
30938            return that;
30939        },
30940
30941        _create: function (tab) {
30942            var plain = $.isPlainObject(tab),
30943                that = this, tabs, contents;
30944
30945            if (plain || $.isArray(tab)) {
30946                tab = $.isArray(tab) ? tab : [tab];
30947
30948                tabs = map(tab, function (value, idx) {
30949                            return $(TabStrip.renderItem({
30950                                group: that.tabGroup,
30951                                item: extend(value, { index: idx })
30952                            }));
30953                        });
30954
30955                contents = map( tab, function (value, idx) {
30956                            if (value.content || value.contentUrl) {
30957                                return $(TabStrip.renderContent({
30958                                    item: extend(value, { index: idx })
30959                                }));
30960                            }
30961                        });
30962            } else {
30963                tabs = $(tab);
30964                contents = $("<div class='" + CONTENT + "'/>");
30965
30966                updateTabClasses(tabs);
30967            }
30968
30969            return { tabs: tabs, contents: contents };
30970        },
30971
30972        _toggleDisabled: function(element, enable) {
30973            element = this.tabGroup.find(element);
30974            element.each(function () {
30975                $(this)
30976                    .toggleClass(DEFAULTSTATE, enable)
30977                    .toggleClass(DISABLEDSTATE, !enable);
30978            });
30979        },
30980
30981        _updateClasses: function() {
30982            var that = this,
30983                tabs, activeItem, activeTab;
30984
30985            that.wrapper.addClass("k-widget k-header k-tabstrip");
30986
30987            that.tabGroup = that.wrapper.children("ul").addClass("k-tabstrip-items k-reset");
30988
30989            if (!that.tabGroup[0]) {
30990                that.tabGroup = $("<ul class='k-tabstrip-items k-reset'/>").appendTo(that.wrapper);
30991            }
30992
30993            tabs = that.tabGroup.find("li").addClass("k-item");
30994
30995            if (tabs.length) {
30996                activeItem = tabs.filter("." + ACTIVESTATE).index();
30997                activeTab = activeItem >= 0 ? activeItem : undefined;
30998
30999                that.tabGroup // Remove empty text nodes
31000                    .contents()
31001                    .filter(function () { return (this.nodeType == 3 && !trim(this.nodeValue)); })
31002                    .remove();
31003            }
31004
31005            tabs.eq(activeItem).addClass(TABONTOP);
31006
31007            that.contentElements = that.wrapper.children("div");
31008
31009            that.contentElements
31010                .addClass(CONTENT)
31011                .eq(activeTab)
31012                .addClass(ACTIVESTATE)
31013                .css({ display: "block" });
31014
31015            if (tabs.length) {
31016                updateTabClasses(tabs);
31017
31018                updateFirstLast(that.tabGroup);
31019                that._updateContentElements();
31020            }
31021        },
31022
31023        _updateContentElements: function() {
31024            var that = this,
31025                contentUrls = that.options.contentUrls || [],
31026                tabStripID = that.element.attr("id"),
31027                contentElements = that.wrapper.children("div");
31028
31029            that.tabGroup.find(".k-item").each(function(idx) {
31030                var currentContent = contentElements.eq(idx),
31031                    id = tabStripID + "-" + (idx+1);
31032
31033                if (!currentContent.length && contentUrls[idx]) {
31034                    $("<div id='"+ id +"' class='" + CONTENT + "'/>").appendTo(that.wrapper);
31035                } else {
31036                    currentContent.attr("id", id);
31037                }
31038            });
31039
31040            that.contentElements = that.contentAnimators = that.wrapper.children("div"); // refresh the contents
31041
31042            if (kendo.support.touch && kendo.mobile.ui.Scroller) {
31043                kendo.touchScroller(that.contentElements);
31044                that.contentElements = that.contentElements.children(".km-scroll-container");
31045            }
31046        },
31047
31048        _toggleHover: function(e) {
31049            $(e.currentTarget).toggleClass(HOVERSTATE, e.type == MOUSEENTER);
31050        },
31051
31052        _click: function (e) {
31053            var that = this,
31054                item = $(e.currentTarget),
31055                link = item.find("." + LINK),
31056                href = link.attr(HREF),
31057                collapse = that.options.collapsible,
31058                content = $(that.contentElement(item.index()));
31059
31060            if (item.closest(".k-widget")[0] != that.wrapper[0]) {
31061                return;
31062            }
31063
31064            if (item.is("." + DISABLEDSTATE + (!collapse ? ",." + ACTIVESTATE : ""))) {
31065                e.preventDefault();
31066                return;
31067            }
31068
31069            if (that.tabGroup.children("[data-animating], [data-in-request]").length) {
31070                return;
31071            }
31072
31073            if (that.trigger(SELECT, { item: item[0], contentElement: content[0] })) {
31074                e.preventDefault();
31075            } else {
31076                var isAnchor = link.data(CONTENTURL) || (href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1));
31077
31078                if (!href || isAnchor) {
31079                    e.preventDefault();
31080                } else {
31081                    return;
31082                }
31083
31084                if (collapse && item.is("." + ACTIVESTATE)) {
31085                    that.deactivateTab(item);
31086                    e.preventDefault();
31087
31088                    return;
31089                }
31090
31091                if (that.activateTab(item)) {
31092                    e.preventDefault();
31093                }
31094            }
31095        },
31096
31097        /**
31098         *
31099         * Deactivates a tab specified as a selector. Note: Invoking this method will not trigger any events.
31100         *
31101         * @param {Selector} item
31102         * The target tab, specified as a selector, to be deactivated.
31103         *
31104         * @example
31105         * var tabToDeactivate = $("#tab1");
31106         * $("#tabStrip").data("kendoTabStrip").deactivateTab(tabToActivate);
31107         *
31108         */
31109        deactivateTab: function (item) {
31110            var that = this,
31111                animationSettings = that.options.animation,
31112                animation = animationSettings.open,
31113                close = extend({}, animationSettings.close),
31114                hasCloseAnimation = close && "effects" in close;
31115            item = that.tabGroup.find(item);
31116
31117            close = extend( hasCloseAnimation ? close : extend({ reverse: true }, animation), { show: false, hide: true });
31118
31119            if (kendo.size(animation.effects)) {
31120                item.kendoAddClass(DEFAULTSTATE, { duration: animation.duration });
31121                item.kendoRemoveClass(ACTIVESTATE, { duration: animation.duration });
31122            } else {
31123                item.addClass(DEFAULTSTATE);
31124                item.removeClass(ACTIVESTATE);
31125            }
31126
31127            that.contentAnimators
31128                    .filter("." + ACTIVESTATE)
31129                    .kendoStop(true, true)
31130                    .kendoAnimate( close )
31131                    .removeClass(ACTIVESTATE);
31132        },
31133
31134        /**
31135         *
31136         * Activates a tab specified as a selector. Note: Invoking this method will not trigger any events.
31137         *
31138         * @param {Selector} item
31139         * The target tab, specified as a selector, to be activated.
31140         *
31141         * @returns {Boolean}
31142         * Returns <strong>true</strong> if successful; otherwise, <strong>false</strong>.
31143         *
31144         * @exampleTitle Activate a tab with ID, tab1 in a TabStrip
31145         * @example
31146         * var tabToActivate = $("#tab1");
31147         * $("#tabStrip").data("kendoTabStrip").activateTab(tabToActivate);
31148         *
31149         */
31150        activateTab: function (item) {
31151            item = this.tabGroup.find(item);
31152
31153            var that = this,
31154                animationSettings = that.options.animation,
31155                animation = animationSettings.open,
31156                close = extend({}, animationSettings.close),
31157                hasCloseAnimation = close && "effects" in close,
31158                neighbours = item.parent().children(),
31159                oldTab = neighbours.filter("." + ACTIVESTATE),
31160                itemIndex = neighbours.index(item);
31161
31162            close = extend( hasCloseAnimation ? close : extend({ reverse: true }, animation), { show: false, hide: true });
31163            // deactivate previously active tab
31164            if (kendo.size(animation.effects)) {
31165                oldTab.kendoRemoveClass(ACTIVESTATE, { duration: close.duration });
31166                item.kendoRemoveClass(HOVERSTATE, { duration: close.duration });
31167            } else {
31168                oldTab.removeClass(ACTIVESTATE);
31169                item.removeClass(HOVERSTATE);
31170            }
31171
31172            // handle content elements
31173            var contentAnimators = that.contentAnimators;
31174
31175            if (contentAnimators.length === 0) {
31176                oldTab.removeClass(TABONTOP);
31177                item.addClass(TABONTOP) // change these directly to bring the tab on top.
31178                    .css("z-index");
31179
31180                item.addClass(ACTIVESTATE);
31181
31182                that.trigger("change");
31183
31184                return false;
31185            }
31186
31187            var visibleContents = contentAnimators.filter("." + ACTIVESTATE);
31188
31189            // find associated content element
31190            var content = $(that.contentElement(itemIndex));
31191
31192            if (content.length === 0) {
31193                visibleContents
31194                    .removeClass( ACTIVESTATE )
31195                    .kendoStop(true, true)
31196                    .kendoAnimate( close );
31197                return false;
31198            }
31199
31200            item.attr("data-animating", true);
31201
31202            var isAjaxContent = (item.children("." + LINK).data(CONTENTURL) || false) && content.is(EMPTY),
31203                showContentElement = function () {
31204                    oldTab.removeClass(TABONTOP);
31205                    item.addClass(TABONTOP) // change these directly to bring the tab on top.
31206                        .css("z-index");
31207
31208                    if (kendo.size(animation.effects)) {
31209                        oldTab.kendoAddClass(DEFAULTSTATE, { duration: animation.duration });
31210                        item.kendoAddClass(ACTIVESTATE, { duration: animation.duration });
31211                    } else {
31212                        oldTab.addClass(DEFAULTSTATE);
31213                        item.addClass(ACTIVESTATE);
31214                    }
31215
31216                    content
31217                        .closest(".k-content")
31218                        .addClass(ACTIVESTATE)
31219                        .kendoStop(true, true)
31220                        .kendoAnimate( extend({ init: function () {
31221                            that.trigger(ACTIVATE, { item: item[0], contentElement: content[0] });
31222                        } }, animation, { complete: function () {
31223                                                        item.removeAttr("data-animating");
31224                                                    } } ) );
31225                },
31226                showContent = function() {
31227                    if (!isAjaxContent) {
31228                        showContentElement();
31229                        that.trigger("change");
31230                    } else {
31231                        that.ajaxRequest(item, content, function () {
31232                            showContentElement();
31233                            that.trigger("change");
31234                        });
31235                    }
31236                };
31237
31238            visibleContents
31239                    .removeClass(ACTIVESTATE);
31240
31241            if (visibleContents.length) {
31242                visibleContents
31243                    .kendoStop(true, true)
31244                    .kendoAnimate(extend( {
31245                        complete: showContent
31246                   }, close ));
31247            } else {
31248                showContent();
31249            }
31250
31251            return true;
31252        },
31253
31254        /**
31255         *
31256         * Obtains the DOM element representing a tab by its index in the <strong>TabStrip</strong>.
31257         *
31258         * @param {int} itemIndex
31259         * The index of the tab in the TabStrip.
31260         *
31261         * @returns {HTMLElement}
31262         * The DOM element representing a tab by its index in the <strong>TabStrip</strong>.
31263         *
31264         * @exampleTitle Obtain the DOM element representing the first tab in a TabStrip
31265         * @example
31266         * var tabContent = $("#tabStrip").data("kendoTabStrip").contentElement(0);
31267         *
31268         */
31269        contentElement: function (itemIndex) {
31270            if (isNaN(itemIndex - 0)) {
31271                return;
31272            }
31273
31274            var contentElements = this.contentElements,
31275                idTest = new RegExp("-" + (itemIndex + 1) + "$");
31276
31277            for (var i = 0, len = contentElements.length; i < len; i++) {
31278                if (idTest.test(contentElements.closest(".k-content")[i].id)) {
31279                    return contentElements[i];
31280                }
31281            }
31282        },
31283
31284        ajaxRequest: function (element, content, complete, url) {
31285            element = this.tabGroup.find(element);
31286            if (element.find(".k-loading").length) {
31287                return;
31288            }
31289
31290            var that = this,
31291                link = element.find("." + LINK),
31292                data = {},
31293                statusIcon = null,
31294                loadingIconTimeout = setTimeout(function () {
31295                    statusIcon = $("<span class='k-icon k-loading'/>").prependTo(link);
31296                }, 100);
31297
31298            element.attr("data-in-request", true);
31299
31300            $.ajax({
31301                type: "GET",
31302                cache: false,
31303                url: url || link.data(CONTENTURL) || link.attr(HREF),
31304                dataType: "html",
31305                data: data,
31306
31307                error: function (xhr, status) {
31308                    element.removeAttr("data-animating");
31309                    if (that.trigger("error", { xhr: xhr, status: status })) {
31310                        this.complete();
31311                    }
31312                },
31313
31314                complete: function () {
31315                    element.removeAttr("data-in-request");
31316
31317                    clearTimeout(loadingIconTimeout);
31318                    if (statusIcon !== null) {
31319                        statusIcon.remove();
31320                    }
31321                },
31322
31323                success: function (data, textStatus) {
31324                    content.html(data);
31325
31326                    if (complete) {
31327                        complete.call(that, content);
31328                    }
31329
31330                    that.trigger(CONTENTLOAD, { item: element[0], contentElement: content[0] });
31331                }
31332            });
31333        }
31334    });
31335
31336    // client-side rendering
31337    extend(TabStrip, {
31338        renderItem: function (options) {
31339            options = extend({ tabStrip: {}, group: {} }, options);
31340
31341            var empty = templates.empty,
31342                item = options.item;
31343
31344            return templates.item(extend(options, {
31345                image: item.imageUrl ? templates.image : empty,
31346                sprite: item.spriteCssClass ? templates.sprite : empty,
31347                itemWrapper: templates.itemWrapper
31348            }, rendering));
31349        },
31350
31351        renderContent: function (options) {
31352            return templates.content(extend(options, rendering));
31353        }
31354    });
31355
31356    kendo.ui.plugin(TabStrip);
31357
31358})(jQuery);
31359/**
31360 * @fileOverview Provides a TimePicker implementation which allows the end user to select a time value from a list of
31361 * predefined values or to type a new value.
31362 */
31363
31364(function($, undefined) {
31365    /**
31366     * @name kendo.ui.TimePicker.Description
31367     *
31368     * @section
31369     * <p>
31370     *  The <strong>TimePicker</strong> allows the end user to select a time value from a list of predefined values or
31371     *  to type a new value. It supports configurable options for the format, minimum and maximum time, and the
31372     *  interval between predefined values in the list.
31373     * </p>
31374     * <h3>Getting Started</h3>
31375     *
31376     * @exampleTitle Creating a TimePicker from existing input element
31377     * @example
31378     * <input id="timePicker" />
31379     *
31380     * @exampleTitle Initialize the PanelBar via an ID selector
31381     * @example
31382     * $(document).ready(function(){
31383     *     $("#timePicker").kendoTimePicker();
31384     * });
31385     *
31386     * @section
31387     * <p>
31388     *  When a <strong>TimePicker</strong> is initialized, it will automatically be displayed near the location of the
31389     *  used HTML element.
31390     * </p>
31391     * <h3>Configuring TimePicker Behaviors</h3>
31392     * <p>
31393     *  A <strong>TimePicker</strong> provides configuration options that can be easily set during initialization.
31394     *  Among the properties that can be controlled:
31395     * </p>
31396     * <ul>
31397     *  <li>Selected time</li>
31398     *  <li>Minimum/Maximum time</li>
31399     *  <li>Define format</li>
31400     *  <li>Define interval between predefined values in the list</li>
31401     * </ul>
31402     *
31403     * @exampleTitle Create TimePicker with selected time and defined min and max time
31404     * @example
31405     * $("#timePicker").kendoTimePicker({
31406     *     value: new Date(2000, 10, 10, 10, 0, 0),
31407     *     min: new Date(1950, 0, 1, 8, 0, 0),
31408     *     max: new Date(2049, 11, 31, 18, 0, 0)
31409     * });
31410     *
31411     * @section
31412     * <p>
31413     *  A <strong>TimePicker</strong> will set the value only if the entered time is valid and if it is in the defined
31414     *  range.
31415     * </p>
31416     *
31417     * @exampleTitle Define time format
31418     * @example
31419     * $("#timePicker").kendoTimePicker({
31420     *     format: "hh:mm:ss tt"
31421     * });
31422     *
31423     * @exampleTitle Define the interval (in minutes) between values in the list
31424     * @example
31425     * $("#timePicker").kendoTimePicker({
31426     *     interval: 15
31427     * });
31428     *
31429     * @section
31430     * <h3>Accessing an Existing TimePicker</h3>
31431     * <p>
31432     *  You can reference an existing <strong>TimePicker</strong> instance via
31433     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
31434     *  use the API to control its behavior.
31435     * </p>
31436     *
31437     * @exampleTitle Accessing an existing TimePicker instance
31438     * @example
31439     * var timePicker = $("#timePicker").data("kendoTimePicker");
31440     *
31441     */
31442    var kendo = window.kendo,
31443        touch = kendo.support.touch,
31444        keys = kendo.keys,
31445        ui = kendo.ui,
31446        Widget = ui.Widget,
31447        OPEN = "open",
31448        CLOSE = "close",
31449        CHANGE = "change",
31450        CLICK = (touch ? "touchend" : "click"),
31451        DEFAULT = "k-state-default",
31452        DISABLED = "disabled",
31453        LI = "li",
31454        SPAN = "<span/>",
31455        FOCUSED = "k-state-focused",
31456        HOVER = "k-state-hover",
31457        HOVEREVENTS = "mouseenter mouseleave",
31458        MOUSEDOWN = "mousedown",
31459        MS_PER_MINUTE = 60000,
31460        MS_PER_DAY = 86400000,
31461        SELECTED = "k-state-selected",
31462        STATEDISABLED = "k-state-disabled",
31463        extend = $.extend,
31464        proxy = $.proxy,
31465        DATE = Date,
31466        TODAY = new DATE();
31467
31468    TODAY = new DATE(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate(), 0, 0, 0);
31469
31470    var TimeView = function(options) {
31471        var that = this;
31472
31473        that.options = options;
31474
31475        that.ul = $('<ul unselectable="on" class="k-list k-reset"/>')
31476                    .css({ overflow: kendo.support.touch ? "": "auto" })
31477                    .delegate(LI, CLICK, proxy(that._click, that))
31478                    .delegate(LI, "mouseenter", function() { $(this).addClass(HOVER); })
31479                    .delegate(LI, "mouseleave", function() { $(this).removeClass(HOVER); });
31480
31481        that.list = $("<div class='k-list-container'/>")
31482                    .append(that.ul)
31483                    .mousedown(function(e) {
31484                        e.preventDefault();
31485                    });
31486
31487        that._popup();
31488
31489        that.template = kendo.template('<li class="k-item" unselectable="on">#=data#</li>', { useWithBlock: false });
31490    };
31491
31492    TimeView.prototype = {
31493        current: function(candidate) {
31494            var that = this;
31495
31496            if (candidate !== undefined) {
31497                if (that._current) {
31498                    that._current.removeClass(SELECTED);
31499                }
31500
31501                if (candidate) {
31502                    candidate = $(candidate);
31503                    candidate.addClass(SELECTED);
31504                    that.scroll(candidate[0]);
31505                }
31506
31507                that._current = candidate;
31508            } else {
31509                return that._current;
31510            }
31511        },
31512
31513        close: function() {
31514            this.popup.close();
31515        },
31516
31517        open: function() {
31518            var that = this;
31519
31520            if (!that.ul[0].firstChild) {
31521                that.refresh();
31522            }
31523
31524            that.popup.open();
31525            if (that._current) {
31526                that.scroll(that._current[0]);
31527            }
31528        },
31529
31530        refresh: function() {
31531            var that = this,
31532                options = that.options,
31533                format = options.format,
31534                offset = dst(),
31535                ignoreDST = offset < 0,
31536                min = options.min,
31537                max = options.max,
31538                msMin = getMilliseconds(min),
31539                msMax = getMilliseconds(max),
31540                msInterval = options.interval * MS_PER_MINUTE,
31541                toString = kendo.toString,
31542                template = that.template,
31543                start = new DATE(min),
31544                idx = 0, length,
31545                html = "";
31546
31547            if (ignoreDST) {
31548                length = (MS_PER_DAY + (offset * MS_PER_MINUTE)) / msInterval;
31549            } else {
31550                length = MS_PER_DAY / msInterval;
31551            }
31552
31553
31554            if (msMin != msMax) {
31555                if (msMin > msMax) {
31556                    msMax += MS_PER_DAY;
31557                }
31558                length = (msMax - msMin) / msInterval + 1;
31559            }
31560
31561            for (; idx < length; idx++) {
31562                if (idx) {
31563                    setTime(start, msInterval, ignoreDST);
31564                }
31565
31566                if (msMax && getMilliseconds(start) > msMax) {
31567                    start = new DATE(max);
31568                }
31569
31570                html += template(toString(start, format));
31571            }
31572
31573            that.ul[0].innerHTML = html;
31574
31575            that._height(length);
31576
31577            that.select(that._value);
31578        },
31579
31580        scroll: function(item) {
31581            if (!item) {
31582                return;
31583            }
31584
31585            var ul = this.ul[0],
31586                itemOffsetTop = item.offsetTop,
31587                itemOffsetHeight = item.offsetHeight,
31588                ulScrollTop = ul.scrollTop,
31589                ulOffsetHeight = ul.clientHeight,
31590                bottomDistance = itemOffsetTop + itemOffsetHeight;
31591
31592                ul.scrollTop = ulScrollTop > itemOffsetTop ?
31593                               itemOffsetTop : bottomDistance > (ulScrollTop + ulOffsetHeight) ?
31594                               bottomDistance - ulOffsetHeight : ulScrollTop;
31595        },
31596
31597        select: function(li) {
31598            var that = this,
31599                current = that._current;
31600
31601            if (typeof li === "string") {
31602                if (!current || current.text() !== li) {
31603                    li = $.grep(that.ul[0].childNodes, function(node) {
31604                        return (node.textContent || node.innerText) == li;
31605                    });
31606
31607                    li = li[0] ? li : null;
31608                } else {
31609                    li = current;
31610                }
31611            }
31612
31613            that.current(li);
31614        },
31615
31616        toggle: function() {
31617            var that = this;
31618
31619            if (that.popup.visible()) {
31620                that.close();
31621            } else {
31622                that.open();
31623            }
31624        },
31625
31626        value: function(value) {
31627            var that = this;
31628
31629            that._value = value;
31630            if (that.ul[0].firstChild) {
31631                that.select(value);
31632            }
31633        },
31634
31635        _click: function(e) {
31636            var that = this,
31637                li = $(e.currentTarget);
31638
31639            if (!e.isDefaultPrevented()) {
31640                that.select(li);
31641                that.options.change(li.text(), true);
31642                that.close();
31643            }
31644        },
31645
31646        _height: function(length) {
31647            if (length) {
31648                var that = this,
31649                    list = that.list,
31650                    parent = list.parent(".k-animation-container"),
31651                    height = that.options.height;
31652
31653                list.add(parent)
31654                    .show()
31655                    .height(that.ul[0].scrollHeight > height ? height : "auto")
31656                    .hide();
31657            }
31658        },
31659
31660        _popup: function() {
31661            var that = this,
31662                list = that.list,
31663                options = that.options,
31664                anchor = options.anchor,
31665                width;
31666
31667            that.popup = new ui.Popup(list, extend(options.popup, {
31668                anchor: anchor,
31669                open: options.open,
31670                close: options.close,
31671                animation: options.animation
31672            }));
31673
31674            width = anchor.outerWidth() - (list.outerWidth() - list.width());
31675
31676            list.css({
31677                fontFamily: anchor.css("font-family"),
31678                width: width
31679            });
31680
31681            kendo.touchScroller(that.popup.element);
31682        },
31683
31684        move: function(e) {
31685            var that = this,
31686                key = e.keyCode,
31687                ul = that.ul[0],
31688                current = that._current,
31689                down = key === keys.DOWN;
31690
31691            if (key === keys.UP || down) {
31692                if (e.altKey) {
31693                    that.toggle(down);
31694                    return;
31695                } else if (down) {
31696                    current = current ? current[0].nextSibling : ul.firstChild;
31697                } else {
31698                    current = current ? current[0].previousSibling : ul.lastChild;
31699                }
31700
31701                if (current) {
31702                    that.select(current);
31703                }
31704
31705                that.options.change(that._current.text());
31706                e.preventDefault();
31707
31708            } else if (key === keys.ENTER || key === keys.TAB || key === keys.ESC) {
31709                e.preventDefault();
31710                that.close();
31711            }
31712        }
31713    };
31714
31715    function setTime(date, time, ignoreDST) {
31716        var offset = date.getTimezoneOffset(),
31717            offsetDiff;
31718
31719        date.setTime(date.getTime() + time);
31720
31721        if (!ignoreDST) {
31722            offsetDiff = date.getTimezoneOffset() - offset;
31723            date.setTime(date.getTime() + offsetDiff * MS_PER_MINUTE);
31724        }
31725    }
31726
31727    function dst() {
31728        var today = new DATE(),
31729            midnight = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0),
31730            noon = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 12, 0, 0);
31731
31732        return -1 * (midnight.getTimezoneOffset() - noon.getTimezoneOffset());
31733    }
31734
31735    function getMilliseconds(date) {
31736        return date.getHours() * 60 * MS_PER_MINUTE + date.getMinutes() * MS_PER_MINUTE + date.getSeconds() * 1000 + date.getMilliseconds();
31737    }
31738
31739    function isInRange(value, min, max) {
31740        var msMin = getMilliseconds(min),
31741            msMax = getMilliseconds(max),
31742            msValue;
31743
31744        if (!value || msMin == msMax) {
31745            return true;
31746        }
31747
31748        msValue = getMilliseconds(value);
31749
31750        if (msMin > msValue) {
31751            msValue += MS_PER_DAY;
31752        }
31753
31754        if (msMax < msMin) {
31755            msMax += MS_PER_DAY;
31756        }
31757
31758        return msValue >= msMin && msValue <= msMax;
31759    }
31760
31761    kendo.TimeView = TimeView;
31762
31763    var TimePicker = Widget.extend(/** @lends kendo.ui.TimePicker.prototype */{
31764        /**
31765         *
31766         * Creates a TimePicker instance.
31767         *
31768         * @constructs
31769         * @extends kendo.ui.Widget
31770         *
31771         * @param {DomElement} element DOM element
31772         * @param {Object} options Configuration options.
31773         *
31774         * @option {Date} [value] <null>
31775         * Specifies the selected time.
31776         *
31777         * @option {Date} [min] <00:00>
31778         * Specifies the start value in the popup list.
31779         *
31780         * @option {Date} [max] <00:00>
31781         * Specifies the end value in the popup list.
31782         *
31783         * @option {String} [format] <h:mm tt>
31784         * Specifies the format, which is used to parse value set with value() method.
31785         *
31786         * @option {Number} [interval] <30>
31787         * Specifies the interval, between values in the popup list, in minutes.
31788         *
31789         * @option {Object} [animation] <>
31790         * Animations to be used for opening/closing the popup. Setting to false will turn of the animation.
31791         *
31792         * @option {Object} [animation.open] <>
31793         * Animation to be used for opening of the popup.
31794         *
31795         * _exampleTitle Intialize a TimePicker that fades-in the time drop-down list over 300 milliseconds
31796         * _example
31797         * $("#timePicker").kendoTimePicker({
31798         *     animation: {
31799         *         open: {
31800         *             effects: "fadeIn",
31801         *             duration: 300,
31802         *             show: true
31803         *         }
31804         *     }
31805         * });
31806         *
31807         * @option {Object} [animation.close] <>
31808         * Animation to be used for closing of the popup.
31809         *
31810         * _exampleTitle Initialize a TimePicker that fades-out the time drop-down list over 300 milliseconds
31811         * _example
31812         * $("#timepicker").kendoTimePicker({
31813         *     animation: {
31814         *         close: {
31815         *             effects: "fadeOut",
31816         *             duration: 300,
31817         *             hide: true
31818         *             show: false
31819         *         }
31820         *     }
31821         * });
31822         *
31823         */
31824        init: function(element, options) {
31825            var that = this;
31826
31827            Widget.fn.init.call(that, element, options);
31828
31829            element = that.element;
31830            options = that.options;
31831
31832            options.format = options.format || kendo.culture().calendar.patterns.t;
31833
31834            that._wrapper();
31835
31836            that.timeView = new TimeView(extend({}, options, {
31837                anchor: that.wrapper,
31838                format: options.format,
31839                change: function(value, trigger) {
31840                    if (trigger) {
31841                        that._change(value);
31842                    } else {
31843                        element.val(value);
31844                    }
31845                },
31846                open: function(e) {
31847                    if (that.trigger(OPEN)) {
31848                        e.preventDefault();
31849                    }
31850                },
31851                close: function(e) {
31852                    if (that.trigger(CLOSE)) {
31853                        e.preventDefault();
31854                    }
31855                }
31856            }));
31857
31858            that._icon();
31859
31860            element.addClass("k-input")
31861                .bind({
31862                    keydown: proxy(that._keydown, that),
31863                    focus: function(e) {
31864                        that._inputWrapper.addClass(FOCUSED);
31865                    },
31866                    blur: proxy(that._blur, that)
31867                })
31868                .closest("form")
31869                .bind("reset", function() {
31870                    that.value(element[0].defaultValue);
31871                });
31872
31873            that.enable(!element.is('[disabled]'));
31874            that.value(options.value || element.val());
31875
31876            kendo.notify(that);
31877        },
31878
31879        options: {
31880            name: "TimePicker",
31881            min: TODAY,
31882            max: TODAY,
31883            format: "",
31884            value: null,
31885            interval: 30,
31886            height: 200,
31887            animation: {}
31888        },
31889
31890        events: [
31891        /**
31892         *
31893         * Triggered when the underlying value of a TimePicker is changed.
31894         *
31895         * @name kendo.ui.TimePicker#change
31896         * @event
31897         *
31898         * @param {Event} e
31899         *
31900         * @exampleTitle Attach change event handler during initialization; detach via unbind()
31901         * @example
31902         * // event change for expand
31903         * var onChange = function(e) {
31904         *     // ...
31905         * };
31906         *
31907         * // attach change event handler during initialization
31908         * var timePicker = $("#timePicker").kendoTimePicker({
31909         *     change: onChange
31910         * });
31911         *
31912         * // detach change event handler via unbind()
31913         * timePicker.data("kendoTimePicker").unbind("change", onChange);
31914         *
31915         * @exampleTitle Attach change event handler via bind(); detach via unbind()
31916         * @example
31917         * // event change for expand
31918         * var onChange = function(e) {
31919         *     // ...
31920         * };
31921         *
31922         * // attach change event handler via bind()
31923         * $("#timePicker").data("kendoTimePicker").bind("change", onChange);
31924         *
31925         * // detach change event handler via unbind()
31926         * $("#timePicker").data("kendoTimePicker").unbind("change", onChange);
31927         *
31928         */
31929         OPEN,
31930         CLOSE,
31931         CHANGE
31932        ],
31933
31934        setOptions: function(options) {
31935            Widget.fn.setOptions.call(this, options);
31936
31937            extend(this.timeView.options, options);
31938
31939            this.timeView.refresh();
31940        },
31941
31942        /**
31943         *
31944         * Enables or disables a TimePicker.
31945         *
31946         * @param {Boolean} enable
31947         * Enables (<strong>true</strong> or undefined) or disables (<strong>false</strong>) a TimePicker.
31948         *
31949         * @exampleTitle Enable a TimePicker
31950         * @example
31951         * $("timepicker").data("kendoTimePicker").enable();
31952         *
31953         * @exampleTitle Enable a TimePicker
31954         * @example
31955         * $("timepicker").data("kendoTimePicker").enable(true);
31956         *
31957         * @exampleTitle Disable a TimePicker
31958         * @example
31959         * $("timepicker").data("kendoTimePicker").enable(false);
31960         *
31961         */
31962        enable: function(enable) {
31963            var that = this,
31964                element = that.element,
31965                arrow = that._arrow.unbind(CLICK + " " + MOUSEDOWN),
31966                wrapper = that._inputWrapper.unbind(HOVEREVENTS);
31967
31968            if (enable === false) {
31969                wrapper
31970                    .removeClass(DEFAULT)
31971                    .addClass(STATEDISABLED);
31972
31973                element.attr(DISABLED, DISABLED);
31974            } else {
31975                wrapper
31976                    .removeClass(STATEDISABLED)
31977                    .addClass(DEFAULT)
31978                    .bind(HOVEREVENTS, that._toggleHover);
31979
31980                element
31981                    .removeAttr(DISABLED);
31982
31983                arrow.bind(CLICK, proxy(that._click, that))
31984                     .bind(MOUSEDOWN, function(e) { e.preventDefault(); });
31985            }
31986        },
31987
31988        /**
31989         *
31990         * Closes the drop-down list of a TimePicker.
31991         *
31992         * @exampleTitle Close the time drop-down list of a TimePicker.
31993         * @example
31994         * $("timepicker").data("kendoTimePicker").close();
31995         *
31996         */
31997        close: function() {
31998            this.timeView.close();
31999        },
32000
32001        /**
32002         *
32003         * Opens the drop-down list of a TimePicker.
32004         *
32005         * @exampleTitle Open the time drop-down list of a TimePicker.
32006         * @example
32007         * $("timepicker").data("kendoTimePicker").open();
32008         *
32009         */
32010        open: function() {
32011            this.timeView.open();
32012        },
32013
32014        /**
32015         *
32016         * Gets or sets the minimum value of the TimePicker.
32017         *
32018         * @param {Date|String} value
32019         * The minimum time value to set for a TimePicker, expressed as a Date object or as a string.
32020         *
32021         * @returns {Date}
32022         * The minimum time value of a TimePicker.
32023         *
32024         * @exampleTitle Get the minimum value of a TimePicker
32025         * @example
32026         * var timePicker = $("#timePicker").data("kendoTimePicker");
32027         * var minimum = timePicker.min();
32028         *
32029         * @exampleTitle Set the minimum value of a TimePicker
32030         * @example
32031         * var timePicker = $("#timePicker").data("kendoTimePicker");
32032         * timePicker.min(new Date(1900, 0, 1, 10, 0, 0));
32033         *
32034         */
32035        min: function (value) {
32036            return this._option("min", value);
32037        },
32038
32039        /**
32040         *
32041         * Gets or sets the maximum value of the TimePicker.
32042         *
32043         * @param {Date|String} value
32044         * The maximum time value to set for a TimePicker, expressed as a Date object or as a string.
32045         *
32046         * @returns {Date}
32047         * The maximum time value of a TimePicker.
32048         *
32049         * @exampleTitle Get the maximum value of a TimePicker
32050         * @example
32051         * var timePicker = $("#timePicker").data("kendoTimePicker");
32052         * var maximum = timePicker.max();
32053         *
32054         * @exampleTitle Set the maximum value of a TimePicker
32055         * @example
32056         * var timePicker = $("#timePicker").data("kendoTimePicker");
32057         * timePicker.max(new Date(1900, 0, 1, 10, 0, 0));
32058         *
32059         */
32060        max: function (value) {
32061            return this._option("max", value);
32062        },
32063
32064        /**
32065         *
32066         * Gets or sets the value of the TimePicker.
32067         *
32068         * @param {Date|String} value
32069         * The time value to set for a TimePicker, expressed as a Date object or as a string.
32070         *
32071         * @returns {Date}
32072         * The time value of a TimePicker.
32073         *
32074         * @exampleTitle Get the value of a TimePicker
32075         * @example
32076         * var timePicker = $("#timePicker").data("kendoTimePicker");
32077         * var timePickerValue = timePicker.value();
32078         *
32079         * @exampleTitle Set the value of a TimePicker
32080         * @example
32081         * var timePicker = $("#timePicker").data("kendoTimePicker");
32082         * timePicker.value("10:00 AM");
32083         *
32084         */
32085        value: function(value) {
32086            var that = this;
32087
32088            if (value === undefined) {
32089                return that._value;
32090            }
32091
32092            that._old = that._update(value);
32093        },
32094
32095        _blur: function() {
32096            var that = this;
32097
32098            that.close();
32099            that._change(that.element.val());
32100            that._inputWrapper.removeClass(FOCUSED);
32101        },
32102
32103        _click: function() {
32104            var that = this,
32105                element = that.element;
32106
32107            if (!touch && element[0] !== document.activeElement) {
32108                element.focus();
32109            }
32110
32111            that.timeView.toggle();
32112        },
32113
32114        _change: function(value) {
32115            var that = this;
32116
32117            value = that._update(value);
32118
32119            if (+that._old != +value) {
32120                that._old = value;
32121                that.trigger(CHANGE);
32122
32123                // trigger the DOM change event so any subscriber gets notified
32124                that.element.trigger(CHANGE);
32125            }
32126        },
32127
32128        _icon: function() {
32129            var that = this,
32130                element = that.element,
32131                arrow;
32132
32133            arrow = element.next("span.k-select");
32134
32135            if (!arrow[0]) {
32136                arrow = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-icon-clock">select</span></span>').insertAfter(element);
32137            }
32138
32139            that._arrow = arrow;
32140        },
32141
32142        _keydown: function(e) {
32143            var that = this,
32144                key = e.keyCode,
32145                enter = key == keys.ENTER,
32146                timeView = that.timeView;
32147
32148            if (timeView.popup.visible() || e.altKey || enter) {
32149                timeView.move(e);
32150            }
32151
32152            if (enter) {
32153                that._change(that.element.val());
32154            }
32155        },
32156
32157        _option: function(option, value) {
32158            var that = this,
32159                options = that.options;
32160
32161            if (value === undefined) {
32162                return options[option];
32163            }
32164
32165            value = that._parse(value);
32166
32167            if (!value) {
32168                return;
32169            }
32170
32171            value = new DATE(value);
32172
32173            options[option] = value;
32174            that.timeView.options[option] = value;
32175            that.timeView.refresh();
32176        },
32177
32178        _parse: function(value) {
32179            var that = this,
32180                current = that._value || TODAY;
32181
32182            if (value instanceof DATE) {
32183                return value;
32184            }
32185
32186            value = kendo.parseDate(value, that.options.format);
32187
32188            if (value) {
32189                value = new DATE(current.getFullYear(),
32190                                 current.getMonth(),
32191                                 current.getDate(),
32192                                 value.getHours(),
32193                                 value.getMinutes(),
32194                                 value.getSeconds(),
32195                                 value.getMilliseconds());
32196            }
32197
32198            return value;
32199        },
32200
32201        _toggleHover: function(e) {
32202            if (!touch) {
32203                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
32204            }
32205        },
32206
32207        _update: function(value) {
32208            var that = this,
32209                options = that.options,
32210                date = that._parse(value),
32211                text = kendo.toString(date, options.format);
32212
32213            if (!isInRange(date, options.min, options.max)) {
32214                date = null;
32215            }
32216
32217            that._value = date;
32218            that.element.val(date ? text : value);
32219            that.timeView.value(text);
32220
32221            return date;
32222        },
32223
32224        _wrapper: function() {
32225            var that = this,
32226                element = that.element,
32227                wrapper;
32228
32229            wrapper = element.parents(".k-timepicker");
32230
32231            if (!wrapper[0]) {
32232                wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
32233                wrapper = wrapper.wrap(SPAN).parent();
32234            }
32235
32236            wrapper[0].style.cssText = element[0].style.cssText;
32237            element.css({
32238                width: "100%",
32239                height: element[0].style.height
32240            });
32241
32242            that.wrapper = wrapper.addClass("k-widget k-timepicker k-header");
32243            that._inputWrapper = $(wrapper[0].firstChild);
32244        }
32245    });
32246
32247    ui.plugin(TimePicker);
32248
32249})(jQuery);
32250/**
32251 * @fileOverview Provides a TreeView implementation which can be used to display hierarchical data in a traditional
32252 * tree structure.
32253 */
32254
32255(function($, undefined){
32256    /**
32257     * @name kendo.ui.TreeView.Description
32258     *
32259     * @section
32260     * <p>
32261     *  The <strong>TreeView</strong> displays hierarchical data in a traditional tree structure. It supports user
32262     *  interaction through the mouse or touch to perform re-ordering operations via drag-and-drop.
32263     * </p>
32264     * <p>
32265     *  A <strong>TreeView</strong> can be created by leveraging HTML lists. However, it does not support binding to a
32266     *  remote data source at this point in time.
32267     * </p>
32268     * <h3>Getting Started</h3>
32269     * <p>A <strong>TreeView</strong> can be created in two ways:</p>
32270     * <ol>
32271     *  <li>Define a hierarchical list with static HTML</li>
32272     *  <li>Use dynamic data binding</li>
32273     * </ol>
32274     * <p>
32275     *  Static HTML definition is appropriate for small hierarchies and for data that does not change frequently.
32276     *  Databinding should be used for larger data sets and for data that changes frequently.
32277     * </p>
32278     * <h3>Creating a TreeView from HTML</h3>
32279     *
32280     * @exampleTitle Create a hierarchical list in HTML
32281     * @example
32282     * <ul id="treeView">
32283     *     <li>Item 1
32284     *         <ul>
32285     *             <li>Item 1.1</li>
32286     *             <li>Item 1.2</li>
32287     *         </ul>
32288     *     </li>
32289     *     <li>Item 2</li>
32290     * </ul>
32291     *
32292     * @section
32293     * <p>
32294     *  Initialization of a <strong>TreeView</strong> should occur after the DOM is fully loaded. It is recommended
32295     *  that initialization the <strong>TreeView</strong> occur within a handler is provided to $(document).ready().
32296     * </p>
32297     *
32298     * @exampleTitle Initialize a TreeView using a selector within $(document).ready()
32299     * @example
32300     * $(document).ready(function() {
32301     *     $("#treeView").kendoTreeView();
32302     * });
32303     *
32304     * @section
32305     * <h3>Creating a TreeView with Data Binding to a Local Data Source</h3>
32306     *
32307     * @exampleTitle Create a hierarchical HTML list
32308     * @example
32309     * <div id="treeView"></div>
32310     *
32311     * @exampleTitle Initialize and bind the TreeView
32312     * @example
32313     * $(document).ready(function() {
32314     *     $("#treeView").kendoTreeView({
32315     *         dataSource: [
32316     *             {
32317     *                 text: "Item 1",
32318     *                 items: [
32319     *                     { text: "Item 1.1" },
32320     *                     { text: "Item 1.2" }
32321     *                 ]
32322     *             },
32323     *             { text: "Item 2" }
32324     *         ]
32325     *     })
32326     * });
32327     *
32328     * @section
32329     * <p>Currently, the <strong>TreeView</strong> does not support binding to a remote data source.</p>
32330     * @exampleTitle TreeView item JSON structure
32331     * @example
32332     * var item = {
32333     *     text: "Item text",
32334     *
32335     *     // renders a <img class="k-image" src="/images/icon.png" />
32336     *     imageUrl: "/images/icon.png",
32337     *
32338     *     // renders a <span class="k-sprite icon save" />
32339     *     spriteCssClass: "icon save",
32340     *
32341     *     // specifies whether the node text should be encoded or not
32342     *     // useful when rendering node-specific HTML
32343     *     encoded: false,
32344     *
32345     *     items: [
32346     *         // child items
32347     *     ]
32348     * }
32349     *
32350     * @section
32351     * <h3>Configuring TreeView Behavior</h3>
32352     * <p>
32353     *  A number of <strong>TreeView</strong> behaviors can be easily controlled by simple configuration properties,
32354     *  such as animation behaviors and drag-and-drop behaviors.
32355     * </p>
32356     *
32357     * @exampleTitle Enabling drag-and-drop for TreeView nodes
32358     * @example
32359     * $("#treeView").kendoTreeView({
32360     *     dragAndDrop: true
32361     * });
32362     *
32363     * @section
32364     * <p>
32365     *  When drag-and-drop is enabled, the nodes of a <strong>TreeView</strong> can be dragged and dropped between all
32366     *  levels, with useful tooltips helping indicate where the node will be dropped.
32367     * </p>
32368     * <h3>Accessing an Existing TreeView</h3>
32369     * <p>
32370     *  You can reference an existing <strong>TreeView</strong> instance via
32371     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
32372     *  use the API to control its behavior.
32373     * </p>
32374     *
32375     * @exampleTitle Accessing an existing TreeView instance
32376     * @example
32377     * var treeView = $("#treeView").data("kendoTreeView");
32378     *
32379     */
32380    var kendo = window.kendo,
32381        ui = kendo.ui,
32382        extend = $.extend,
32383        template = kendo.template,
32384        Widget = ui.Widget,
32385        proxy = $.proxy,
32386        SELECT = "select",
32387        EXPAND = "expand",
32388        COLLAPSE = "collapse",
32389        DRAGSTART = "dragstart",
32390        DRAG = "drag",
32391        DROP = "drop",
32392        DRAGEND = "dragend",
32393        CLICK = "click",
32394        VISIBILITY = "visibility",
32395        KSTATEHOVER = "k-state-hover",
32396        KTREEVIEW = "k-treeview",
32397        VISIBLE = ":visible",
32398        NODE = ".k-item",
32399        templates, rendering, TreeView,
32400        subGroup, nodeContents;
32401
32402    function contentChild(filter) {
32403        return function(node) {
32404            var result = node.children(".k-animation-container");
32405
32406            if (!result.length) {
32407                result = node;
32408            }
32409
32410            return result.children(filter);
32411        };
32412    }
32413
32414    subGroup = contentChild(".k-group");
32415    nodeContents = contentChild(".k-group,.k-content");
32416
32417    function updateNodeHtml(node) {
32418        var wrapper = node.children("div"),
32419            group = node.children("ul"),
32420            toggleButton = wrapper.children(".k-icon"),
32421            innerWrapper = wrapper.children(".k-in"),
32422            currentNode, tmp;
32423
32424        if (node.hasClass("k-treeview")) {
32425            return;
32426        }
32427
32428        if (!wrapper.length) {
32429            wrapper = $("<div />").prependTo(node);
32430        }
32431
32432        if (!toggleButton.length && group.length) {
32433            toggleButton = $("<span class='k-icon' />").prependTo(wrapper);
32434        } else if (!group.length || !group.children().length) {
32435            toggleButton.remove();
32436            group.remove();
32437        }
32438
32439        if (!innerWrapper.length) {
32440            innerWrapper = $("<span class='k-in' />").appendTo(wrapper)[0];
32441
32442            // move all non-group content in the k-in container
32443            currentNode = wrapper[0].nextSibling;
32444            innerWrapper = wrapper.find(".k-in")[0];
32445
32446            while (currentNode && currentNode.nodeName.toLowerCase() != "ul") {
32447                tmp = currentNode;
32448                currentNode = currentNode.nextSibling;
32449
32450                if (tmp.nodeType == 3) {
32451                    tmp.nodeValue = $.trim(tmp.nodeValue);
32452                }
32453
32454                innerWrapper.appendChild(tmp);
32455            }
32456        }
32457    }
32458
32459    function updateNodeClasses(node, groupData, nodeData) {
32460        var wrapper = node.children("div"),
32461            group = node.children("ul");
32462
32463        if (node.hasClass("k-treeview")) {
32464            return;
32465        }
32466
32467        if (!nodeData) {
32468            nodeData = {
32469                expanded: group.css("display") != "none",
32470                index: node.index(),
32471                enabled: !wrapper.children(".k-in").hasClass("k-state-disabled")
32472            };
32473        }
32474
32475        if (!groupData) {
32476            groupData = {
32477                firstLevel: node.parent().parent().hasClass(KTREEVIEW),
32478                length: node.parent().children().length
32479            };
32480        }
32481
32482        // li
32483        node.removeClass("k-first k-last")
32484            .addClass(rendering.wrapperCssClass(groupData, nodeData));
32485
32486        // div
32487        wrapper.removeClass("k-top k-mid k-bot")
32488               .addClass(rendering.cssClass(groupData, nodeData));
32489
32490        // toggle button
32491        if (group.length) {
32492            wrapper.children(".k-icon").removeClass("k-plus k-minus k-plus-disabled k-minus-disabled")
32493                .addClass(rendering.toggleButtonClass(nodeData));
32494
32495            group.addClass("k-group");
32496        }
32497    }
32498
32499
32500    templates = {
32501        dragClue: template("<div class='k-header k-drag-clue'><span class='k-icon k-drag-status'></span>#= text #</div>"),
32502        group: template(
32503            "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
32504                "#= renderItems(data) #" +
32505            "</ul>"
32506        ),
32507        itemWrapper: template(
32508            "<div class='#= cssClass(group, item) #'>" +
32509                "#= toggleButton(data) #" +
32510                "#= checkbox(data) #" +
32511                "<#= tag(item) # class='#= textClass(item) #'#= textAttributes(item) #>" +
32512                    "#= image(item) ##= sprite(item) #" +
32513                    "#= treeview.template ? template(treeview, item) : text(item) #" +
32514                "</#= tag(item) #>" +
32515            "</div>"
32516        ),
32517        item: template(
32518            "<li class='#= wrapperCssClass(group, item) #'>" +
32519                "#= itemWrapper(data) #" +
32520                "# if (item.items) { #" +
32521                "#= subGroup(treeview, group, item) #" +
32522                "# } #" +
32523            "</li>"
32524        ),
32525        checkbox: template(
32526            "# if (treeview.checkboxTemplate) { #" +
32527                "<span class='k-checkbox'>" +
32528                    "#= checkboxTemplate(treeview, group, item) #" +
32529                "</span>" +
32530            "# } #"
32531        ),
32532        image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
32533        toggleButton: template("<span class='#= toggleButtonClass(item) #'></span>"),
32534        sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
32535        empty: template("")
32536    };
32537
32538    TreeView = Widget.extend(/** @lends kendo.ui.TreeView.prototype */ {
32539        /**
32540         *
32541         * Creates a TreeView instance.
32542         *
32543         * @constructs
32544         * @extends kendo.ui.Widget
32545         *
32546         * @param {DomElement} element DOM element
32547         * @param {Object} options Configuration options.
32548         *
32549         * @option {Array} [dataSource]
32550         * The data that the <strong>TreeView</strong> will be bound to.
32551         *
32552         * @option {Object} [animation]
32553         * A collection of visual animations used when items are expanded or collapsed through user interaction.
32554         * Setting this option to <strong>false</strong> will disable all animations.
32555         *
32556         * _example
32557         * $("#treeView").kendoTreeView({
32558         *     animation: {
32559         *         expand: {
32560         *             duration: 200,
32561         *             hide: true,
32562         *             show: false
32563         *         },
32564         *         collapse: {
32565         *             duration: 200,
32566         *             effects: "expandVertical",
32567         *             show: true
32568         *         }
32569         *     }
32570         * });
32571         *
32572         * @option {Animation} [animation.expand]
32573         * The animation that will be used when expanding items.
32574         *
32575         * @option {Number} [animation.expand.duration] <200> The number of milliseconds used for the animation when a
32576         * node is expanded.
32577         *
32578         * _example
32579         * $("#treeView").kendoTreeView({
32580         *     animation: {
32581         *         expand: {
32582         *             duration: 1000
32583         *         }
32584         *     }
32585         * });
32586         *
32587         * @option {String} [animation.expand.effects] <"expandVertical">
32588         * A whitespace-delimited string of animation effects that are utilized when a <strong>TreeView</strong> node
32589         * is expanded. Options include <strong>"expandVertical"</strong> and <strong>"fadeIn"</strong>.
32590         *
32591         * _exampleTitle Initialize a TreeView to expand and fade-in nodes over 5000 milliseconds
32592         * _example
32593         * $("#treeView").kendoTreeView({
32594         *     animation: {
32595         *         expand: {
32596         *             duration: 5000,
32597         *             effects: "expandVertical fadeIn"
32598         *         }
32599         *     }
32600         * });
32601         *
32602         * @option {Boolean} [animation.expand.show] <true>
32603         *
32604         * @option {Boolean} [dragAndDrop] <false>
32605         * Disables (<strong>false</strong>) or enables (<b>true</b>) drag-and-drop on the nodes of a
32606         * <strong>TreeView</strong>.
32607         *
32608         * @option {Animation} [animation.collapse]
32609         * The animation that will be used when collapsing items.
32610         *
32611         * @option {Number} [animation.collapse.duration] <200>
32612         * The number of milliseconds used for the animation when a node is expanded.
32613         *
32614         * _exampleTitle Initialize a TreeView to collapse nodes over 1000 milliseconds
32615         * _example
32616         * $("#treeView").kendoTreeView({
32617         *     animation: {
32618         *         collapse: {
32619         *             duration: 1000
32620         *         }
32621         *     }
32622         * });
32623         *
32624         * @option {String} [animation.collapse.effects]
32625         * A whitespace-delimited string of animation effects that are utilized when a <strong>TreeView</strong> node
32626         * is collapsed. Options include <strong>"fadeOut"</strong>.
32627         *
32628         * _exampleTitle Initialize a TreeView to collapse and fade-out nodes over 5000 milliseconds
32629         * _example
32630         * $("#treeView").kendoTreeView({
32631         *     animation: {
32632         *         collapse: {
32633         *             duration: 5000,
32634         *             effects: "fadeOut"
32635         *         }
32636         *     }
32637         * });
32638         *
32639         * @option {String|Function} [template] Template for rendering of the nodes of the treeview.
32640         * _example
32641         * $("#treeview").kendoTreeView({
32642         *     template: "#= item.text # <a href='\\#'>Delete</a>"
32643         * });
32644         *
32645         * @option {String|Function} [checkboxTemplate] Template for rendering of the treeview checkboxes.
32646         * _example
32647         * $("#treeview").kendoTreeView({
32648         *     template: kendo.template(
32649         *         "<input type='checkbox' name='checkedFiles[" +
32650         *             item.id +
32651         *         "]' value='true' />"
32652         *     )
32653         * });
32654         */
32655        init: function (element, options) {
32656            var that = this,
32657                clickableItems = ".k-in:not(.k-state-selected,.k-state-disabled)",
32658                MOUSEENTER = "mouseenter",
32659                dataInit;
32660
32661            if ($.isArray(options)) {
32662                dataInit = true;
32663                options = { dataSource: options };
32664            }
32665
32666            Widget.prototype.init.call(that, element, options);
32667
32668            element = that.element;
32669            options = that.options;
32670
32671            that._animation();
32672
32673            if (options.template && typeof options.template == "string") {
32674                options.template = template(options.template);
32675            }
32676
32677            // render treeview if it's not already rendered
32678            if (!element.hasClass(KTREEVIEW)) {
32679                that._wrapper();
32680
32681                if (!that.root.length) { // treeview initialized from empty element
32682                    that.root = that.wrapper.html(TreeView.renderGroup({
32683                        items: options.dataSource,
32684                        group: {
32685                            firstLevel: true,
32686                            expanded: true
32687                        },
32688                        treeview: options
32689                    })).children("ul");
32690                } else {
32691                    that._group(that.wrapper);
32692                }
32693            } else {
32694                // otherwise just initialize properties
32695                that.wrapper = element;
32696                that.root = element.children("ul").eq(0);
32697            }
32698
32699            that.wrapper
32700                .on(MOUSEENTER, ".k-in.k-state-selected", function(e) { e.preventDefault(); })
32701                .on(MOUSEENTER, clickableItems, function () { $(this).addClass(KSTATEHOVER); })
32702                .on("mouseleave", clickableItems, function () { $(this).removeClass(KSTATEHOVER); })
32703                .on(CLICK, clickableItems, proxy(that._nodeClick, that))
32704                .on("dblclick", "div:not(.k-state-disabled) .k-in", proxy(that._toggleButtonClick, that))
32705                .on(CLICK, ".k-plus,.k-minus", proxy(that._toggleButtonClick, that));
32706
32707            if (options.dragAndDrop) {
32708                that.dragging = new TreeViewDragAndDrop(that);
32709            }
32710        },
32711
32712        _animation: function() {
32713            var options = this.options;
32714
32715            if (options.animation === false) {
32716                options.animation = {
32717                    expand: { show: true, effects: {} },
32718                    collapse: { hide: true, effects: {} }
32719                };
32720            }
32721        },
32722
32723        events: [
32724            /**
32725            *
32726            * Triggered before the dragging of a node starts.
32727            *
32728            * @name kendo.ui.TreeView#dragstart
32729            * @event
32730            *
32731            * @param {Event} e
32732            *
32733            * @param {Node} e.sourceNode
32734            * The node that will be dragged.
32735            *
32736            * @exampleTitle Disable dragging of root nodes
32737            * @example
32738            * treeview.data("kendoTreeView").bind("dragstart", function(e) {
32739            *     if ($(e.sourceNode).parentsUntil(".k-treeview", ".k-item").length == 0) {
32740            *         e.preventDefault();
32741            *     }
32742            * });
32743            *
32744            */
32745            DRAGSTART,
32746
32747            /**
32748            *
32749            * Triggered while a node is being dragged.
32750            *
32751            * @name kendo.ui.TreeView#drag
32752            * @event
32753            *
32754            * @param {Event} e
32755            *
32756            * @param {Node} e.sourceNode
32757            * The node that is being dragged.
32758            *
32759            * @param {DomElement} e.dropTarget
32760            * The element that the node is placed over.
32761            *
32762            * @param {Integer} e.pageX
32763            * The x coordinate of the mouse.
32764            *
32765            * @param {Integer} e.pageY
32766            * The y coordinate of the mouse.
32767            *
32768            * @param {String} e.statusClass
32769            * The status that the drag clue shows.
32770            *
32771            * @param {Function} e.setStatusClass
32772            * Allows a custom drag clue status to be set.
32773            * <p>Pre-defined status classes are:</p>
32774            * <ul>
32775            *     <li><strong>k-insert-top</strong>
32776            *         - Indicates that the item will be inserted on top.
32777            *     </li>
32778            *     <li><strong>k-insert-middle</strong>
32779            *         - Indicates that the item will be inserted in the middle.
32780            *     </li>
32781            *     <li><strong>k-insert-bottom</strong>
32782            *         - Indicates that the item will be inserted at the bottom.
32783            *     </li>
32784            *     <li><strong>k-add</strong>
32785            *         - Indicates that the item will be added/appended.
32786            *     </li>
32787            *     <li><strong>k-denied</strong>
32788            *         - Indicates an invalid operation. Using this class will automatically
32789            *           make the drop operation invalid, so there will be no need to call
32790            *           <code>setValid(false)</code> in the <code>drop</code> event.
32791            *     </li>
32792            * </ul>
32793            *
32794            * @exampleTitle Show the user that is not permitted to drop nodes outside of the #drop-area element
32795            * @example
32796            * treeview.data("kendoTreeView").bind("drag", function(e) {
32797            *     if ($(e.dropTarget).parents("#drop-area").length ) {
32798            *         e.setStatusClass("k-denied");
32799            *     }
32800            * });
32801            *
32802            */
32803            DRAG,
32804
32805            /**
32806            *
32807            * Triggered when a node is being dropped.
32808            *
32809            * @name kendo.ui.TreeView#drop
32810            * @event
32811            *
32812            * @param {Event} e
32813            *
32814            * @param {Node} e.sourceNode
32815            * The node that is being dropped.
32816            *
32817            * @param {Node} e.destinationNode
32818            * The node that the sourceNode is being dropped upon.
32819            *
32820            * @param {Boolean} e.valid
32821            * Whether this drop operation is permitted.
32822            *
32823            * @param {Function} e.setValid
32824            * Allows the drop to be prevented.
32825            *
32826            * @param {DomElement} e.dropTarget
32827            * The element that the node is placed over.
32828            *
32829            * @param {String} e.dropPosition
32830            * Shows where the source will be dropped. One of the values <strong>over</strong>, <strong>before</strong>, or <strong>after</strong>.
32831            *
32832            */
32833            DROP,
32834
32835            /**
32836            *
32837            * Triggered after a node has been dropped.
32838            *
32839            * @name kendo.ui.TreeView#dragend
32840            * @event
32841            *
32842            * @param {Event} e
32843            *
32844            * @param {Node} e.sourceNode
32845            * The node that is being dropped.
32846            *
32847            * @param {Node} e.destinationNode
32848            * The node that the sourceNode is being dropped upon.
32849            *
32850            * @param {String} e.dropPosition
32851            * Shows where the source has been dropped. One of the values <strong>over</strong>, <strong>before</strong>, or <strong>after</strong>.
32852            *
32853            */
32854            DRAGEND,
32855            /**
32856            *
32857            * Triggered before a subgroup gets expanded.
32858            *
32859            * @name kendo.ui.TreeView#expand
32860            * @event
32861            *
32862            * @param {Event} e
32863            *
32864            * @param {Node} e.node
32865            * The expanded node
32866            *
32867            */
32868            EXPAND,
32869
32870            /**
32871            *
32872            * Triggered before a subgroup gets collapsed.
32873            *
32874            * @name kendo.ui.TreeView#collapse
32875            * @event
32876            *
32877            * @param {Event} e
32878            *
32879            * @param {Node} e.node
32880            * The collapsed node
32881            *
32882            */
32883            COLLAPSE,
32884
32885            /**
32886            *
32887            * Triggered when a node gets selected.
32888            *
32889            * @name kendo.ui.TreeView#select
32890            * @event
32891            *
32892            * @param {Event} e
32893            *
32894            * @param {Node} e.node
32895            * The selected node
32896            *
32897            */
32898            SELECT
32899        ],
32900
32901        options: {
32902            name: "TreeView",
32903            dataSource: {},
32904            animation: {
32905                expand: {
32906                    effects: "expand:vertical",
32907                    duration: 200,
32908                    show: true
32909                },
32910                collapse: {
32911                    duration: 100
32912                }
32913            },
32914            dragAndDrop: false
32915        },
32916
32917        setOptions: function(options) {
32918            var that = this;
32919
32920            if (("dragAndDrop" in options) && options.dragAndDrop && !that.options.dragAndDrop) {
32921                that.dragging = new TreeViewDragAndDrop(that);
32922            }
32923
32924            Widget.fn.setOptions.call(that, options);
32925
32926            that._animation();
32927        },
32928
32929        _trigger: function (eventName, node) {
32930            return this.trigger(eventName, {
32931                node: node.closest(NODE)[0]
32932            });
32933        },
32934
32935        _toggleButtonClick: function (e) {
32936            this.toggle($(e.target).closest(NODE));
32937        },
32938
32939        _nodeClick: function (e) {
32940            var that = this,
32941                node = $(e.target),
32942                contents = nodeContents(node.closest(NODE)),
32943                href = node.attr("href"),
32944                shouldNavigate;
32945
32946            if (href) {
32947                shouldNavigate = href == "#" || href.indexOf("#" + this.element.id + "-") >= 0;
32948            } else {
32949                shouldNavigate = contents.length && !contents.children().length;
32950            }
32951
32952            if (shouldNavigate) {
32953                e.preventDefault();
32954            }
32955
32956            if (!node.hasClass(".k-state-selected") && !that._trigger("select", node)) {
32957                that.select(node);
32958            }
32959        },
32960
32961        _wrapper: function() {
32962            var that = this,
32963                element = that.element,
32964                wrapper, root,
32965                wrapperClasses = "k-widget k-treeview k-reset";
32966
32967            if (element.is("div")) {
32968                wrapper = element;
32969                root = wrapper.children("ul").eq(0);
32970            } else { // element is ul
32971                wrapper = element.wrap('<div />').parent();
32972                root = element;
32973            }
32974
32975            that.wrapper = wrapper.addClass(wrapperClasses);
32976            that.root = root;
32977        },
32978
32979        _group: function(item) {
32980            var that = this,
32981                firstLevel = item.hasClass(KTREEVIEW),
32982                group = {
32983                    firstLevel: firstLevel,
32984                    expanded: firstLevel || item.attr(kendo.attr("expanded")) === "true"
32985                },
32986                groupElement = item.children("ul");
32987
32988            groupElement
32989                .addClass(rendering.groupCssClass(group))
32990                .css("display", group.expanded ? "" : "none");
32991
32992            that._nodes(groupElement, group);
32993        },
32994
32995        _nodes: function(groupElement, groupData) {
32996            var that = this,
32997                nodes = groupElement.children("li"),
32998                nodeData;
32999
33000            groupData = extend({ length: nodes.length }, groupData);
33001
33002            nodes.each(function(i, node) {
33003                node = $(node);
33004
33005                nodeData = { index: i, expanded: node.attr(kendo.attr("expanded")) === "true" };
33006
33007                updateNodeHtml(node);
33008
33009                updateNodeClasses(node, groupData, nodeData);
33010
33011                // iterate over child nodes
33012                that._group(node);
33013            });
33014        },
33015
33016        _processNodes: function(nodes, callback) {
33017            var that = this;
33018            that.element.find(nodes).each(function(index, item) {
33019                callback.call(that, index, $(item).closest(NODE));
33020            });
33021        },
33022
33023        /**
33024         *
33025         * Expands nodes.
33026         *
33027         * @param {Selector} nodes
33028         * The nodes that are to be expanded.
33029         *
33030         * @example
33031         * var treeview = $("#treeview").data("kendoTreeView");
33032         *
33033         * // expands the node with id="firstItem"
33034         * treeview.expand(document.getElementById("firstItem"));
33035         *
33036         * // expands all nodes
33037         * treeview.expand(".k-item");
33038         *
33039         */
33040        expand: function (nodes) {
33041            this._processNodes(nodes, function (index, item) {
33042                var contents = nodeContents(item);
33043
33044                if (contents.length > 0 && !contents.is(VISIBLE)) {
33045                    this.toggle(item);
33046                }
33047            });
33048        },
33049
33050        /**
33051         *
33052         * Collapses nodes.
33053         *
33054         * @param {Selector} nodes
33055         * The nodes that are to be collapsed.
33056         *
33057         * @example
33058         * var treeview = $("#treeview").data("kendoTreeView");
33059         *
33060         * // collapse the node with id="firstItem"
33061         * treeview.collapse(document.getElementById("firstItem"));
33062         *
33063         * // collapse all nodes
33064         * treeview.collapse(".k-item");
33065         *
33066         */
33067        collapse: function (nodes) {
33068            this._processNodes(nodes, function (index, item) {
33069                var contents = nodeContents(item);
33070
33071                if (contents.length > 0 && contents.is(VISIBLE)) {
33072                    this.toggle(item);
33073                }
33074            });
33075        },
33076
33077        /**
33078         *
33079         * Enables or disables nodes.
33080         *
33081         * @param {Selector} nodes
33082         * The nodes that are to be enabled/disabled.
33083         *
33084         * @param {Boolean} [enable=true]
33085         * Whether the nodes should be enabled or disabled.
33086         *
33087         * @example
33088         * var treeview = $("#treeview").data("kendoTreeView");
33089         *
33090         * // disable the node with id="firstItem"
33091         * treeview.enable(document.getElementById("firstItem"), false);
33092         *
33093         * // enable all nodes
33094         * treeview.enable(".k-item");
33095         *
33096         */
33097        enable: function (nodes, enable) {
33098            enable = arguments.length == 2 ? !!enable : true;
33099
33100            this._processNodes(nodes, function (index, item) {
33101                var isCollapsed = !nodeContents(item).is(VISIBLE);
33102
33103                if (!enable) {
33104                    this.collapse(item);
33105                    isCollapsed = true;
33106                }
33107
33108                item.children("div")
33109                        .children(".k-in")
33110                            .toggleClass("k-state-default", enable)
33111                            .toggleClass("k-state-disabled", !enable)
33112                        .end()
33113                        .children(".k-icon")
33114                            .toggleClass("k-plus", isCollapsed && enable)
33115                            .toggleClass("k-plus-disabled", isCollapsed && !enable)
33116                            .toggleClass("k-minus", !isCollapsed && enable)
33117                            .toggleClass("k-minus-disabled", !isCollapsed && !enable);
33118            });
33119        },
33120
33121        /**
33122         *
33123         * Gets or sets the selected node of a TreeView.
33124         *
33125         * @param {Selector} [node]
33126         * If provided, the node of a TreeView that should be selected.
33127         *
33128         * @returns {Node}
33129         * The selected node of a TreeView.
33130         *
33131         * @exampleTitle Select the node with ID, firstItem
33132         * @example
33133         * var treeView = $("#treeView").data("kendoTreeView");
33134         * treeView.select($("#firstItem"));
33135         *
33136         * @exampleTitle Get the currently selected node
33137         * @example
33138         * var treeView = $("#treeView").data("kendoTreeView");
33139         * var selectedNode = treeView.select();
33140         *
33141         */
33142        select: function (node) {
33143            var element = this.element;
33144
33145            if (!arguments.length) {
33146                return element.find(".k-state-selected").closest(NODE);
33147            }
33148
33149            node = $(node, element).closest(NODE);
33150
33151            if (node.length) {
33152                element.find(".k-in").removeClass("k-state-hover k-state-selected");
33153
33154                node.find(".k-in:first").addClass("k-state-selected");
33155            }
33156        },
33157
33158        /**
33159         *
33160         * Toggles the node of a TreeView between its expanded and collapsed 
33160states.
33161         *
33162         * @param {Selector} node
33163         * The node that should be toggled.
33164         *
33165         * @exampleTitle Toggle the state of a node with ID, firstItem
33166         * @example
33167         * var treeView = $("#treeView").data("kendoTreeView");
33168         * treeView.toggle($("#firstItem"));
33169         *
33170         */
33171        toggle: function (node) {
33172            node = $(node);
33173
33174            if (!node.find(".k-minus,.k-plus").length) {
33175                return;
33176            }
33177
33178            if (node.find("> div > .k-state-disabled").length) {
33179                return;
33180            }
33181
33182            var that = this,
33183                contents = nodeContents(node),
33184                isExpanding = !contents.is(VISIBLE),
33185                animationSettings = that.options.animation || {},
33186                animation = animationSettings.expand,
33187                collapse = extend({}, animationSettings.collapse),
33188                hasCollapseAnimation = collapse && "effects" in collapse;
33189
33190            if (contents.data("animating")) {
33191                return;
33192            }
33193
33194            if (!isExpanding) {
33195                animation = extend( hasCollapseAnimation ? collapse
33196                                    : extend({ reverse: true }, animation), { show: false, hide: true });
33197            }
33198
33199            if (contents.children().length > 0) {
33200                if (!that._trigger(isExpanding ? "expand" : "collapse", node)) {
33201                    node.find("> div > .k-icon")
33202                        .toggleClass("k-minus", isExpanding)
33203                        .toggleClass("k-plus", !isExpanding);
33204
33205                    if (!isExpanding) {
33206                        contents.css("height", contents.height()).css("height");
33207                    }
33208
33209                    contents.kendoStop(true, true).kendoAnimate(extend(animation, {
33210                        complete: function() {
33211                            if (isExpanding) {
33212                                contents.css("height", "");
33213                            }
33214                        }
33215                    }));
33216                }
33217            }
33218        },
33219
33220        /**
33221         *
33222         * Gets the text of a node in a TreeView.
33223         *
33224         * @param {Selector} node
33225         * The node of which the text is being retrieved.
33226         *
33227         * @returns {String}
33228         * The text of a node.
33229         *
33230         * @exampleTitle Get the text of the node with ID, firstItem
33231         * @example
33232         * var treeView = $("#treeView").data("kendoTreeView");
33233         * var nodeText = treeView.text($("#firstItem"));
33234         *
33235         */
33236        text: function (node) {
33237            return $(node).closest(NODE).find(">div>.k-in").text();
33238        },
33239
33240        _insertNode: function(nodeData, index, parentNode, group, insertCallback) {
33241            var that = this,
33242                updatedGroupLength = group.children().length + 1,
33243                isArrayData, fromNodeData,
33244                groupData = {
33245                    firstLevel: parentNode.hasClass(KTREEVIEW),
33246                    expanded: true,
33247                    length: updatedGroupLength
33248                }, node, i, nodeHtml = "";
33249
33250            function toNodeHtml(nodeData, index) {
33251                return TreeView.renderItem({
33252                    treeview: that.options,
33253                    group: groupData,
33254                    item: extend(nodeData, { index: index })
33255                });
33256            }
33257
33258            if (nodeData.toJSON) {
33259                nodeData = nodeData.toJSON();
33260            }
33261
33262            isArrayData = $.isArray(nodeData);
33263            fromNodeData = isArrayData || $.isPlainObject(nodeData);
33264
33265            if (fromNodeData) {
33266                if (isArrayData) {
33267                    for (i = 0; i < nodeData.length; i++) {
33268                        nodeHtml += toNodeHtml(nodeData[i], index + i);
33269                    }
33270
33271                } else {
33272                    nodeHtml = toNodeHtml(nodeData, index);
33273                }
33274
33275                node = $(nodeHtml);
33276            } else {
33277                node = $(nodeData);
33278
33279                if (group.children()[index - 1] == node[0]) {
33280                    return node;
33281                }
33282
33283                if (node.closest(".k-treeview")[0] == that.wrapper[0]) {
33284                    that.detach(node);
33285                }
33286            }
33287
33288            if (!group.length) {
33289                group = $(TreeView.renderGroup({
33290                    group: groupData
33291                })).appendTo(parentNode);
33292            }
33293
33294            insertCallback(node, group);
33295
33296            if (parentNode.hasClass("k-item")) {
33297                updateNodeHtml(parentNode);
33298                updateNodeClasses(parentNode);
33299            }
33300
33301            if (!fromNodeData) {
33302                updateNodeClasses(node);
33303            }
33304
33305            updateNodeClasses(node.prev());
33306            updateNodeClasses(node.next());
33307
33308            return node;
33309        },
33310
33311        /**
33312         *
33313         * Inserts a node after a specified node in a TreeView. This method may also be used to reorder the nodes of a
33314         * TreeView.
33315         *
33316         * @param {String|Selector} nodeData
33317         * A JSON-formatted string or selector that specifies the node to be inserted.
33318         *
33319         * @param {Node} referenceNode
33320         * The node that will be preceed the newly-appended node.
33321         *
33322         * @exampleTitle Insert a node with the text, "Y U NO insert node?" after the node with ID, firstItem
33323         * @example
33324         * var treeView = $("#treeView").data("kendoTreeView");
33325         * treeView.insertAfter({ text: "Y U NO insert node?" }, $("#firstItem"));
33326         *
33327         * @exampleTitle Moves a node with ID, secondNode after a node with ID, firstItem
33328         * @example
33329         * var treeView = $("#treeView").data("kendoTreeView");
33330         * treeView.insertAfter($("#secondNode"), $("#firstItem"));
33331         *
33332         */
33333        insertAfter: function (nodeData, referenceNode) {
33334            var group = referenceNode.parent();
33335
33336            return this._insertNode(nodeData, referenceNode.index() + 1, group.parent(), group, function(item, group) {
33337                item.insertAfter(referenceNode);
33338            });
33339        },
33340
33341        /**
33342         *
33343         * Inserts a node before another node. This method may also be used to reorder the nodes of a
33344         * TreeView.
33345         *
33346         * @param {String|Selector} nodeData
33347         * A JSON-formatted string or selector that specifies the node to be inserted.
33348         *
33349         * @param {Node} referenceNode
33350         * The node that follows the inserted node.
33351         *
33352         * @exampleTitle Inserts a new node with the text, "It's over 9000!" before the node with ID, firstItem
33353         * @example
33354         * var treeView = $("#treeView").data("kendoTreeView");
33355         * treeView.insertBefore({ text: "It's over 9000!" }, $("#firstItem"));
33356         *
33357         * @exampleTitle Moves the node with ID, secondNode before the node with ID, firstItem
33358         * @example
33359         * var treeView = $("#treeView").data("kendoTreeView");
33360         * treeView.insertBefore($("#secondNode"), $("#firstItem"));
33361         *
33362         */
33363        insertBefore: function (nodeData, referenceNode) {
33364            var group = referenceNode.parent();
33365
33366            return this._insertNode(nodeData, referenceNode.index(), group.parent(), group, function(item, group) {
33367                item.insertBefore(referenceNode);
33368            });
33369        },
33370
33371        /**
33372         *
33373         * Appends a node to a group of a TreeView. This method may also be used to reorder the nodes of a
33374         * TreeView.
33375         *
33376         * @param {String|Selector} nodeData
33377         * A JSON-formatted string or selector that specifies the node to be appended.
33378         *
33379         * @param {Node} [parentNode]
33380         * The node that will contain the newly appended node. If not specified, the new node will be appended to the
33381         * root group of the TreeView.
33382         *
33383         * @exampleTitle Append a new node with the text, "Meanwhile, in HTML5..." to the node with ID, firstItem
33384         * @example
33385         * var treeView = $("#treeView").data("kendoTreeView");
33386         * treeView.append({ text: "Meanwhile, in HTML5..." }, $("#firstItem"));
33387         *
33388         * @exampleTitle Moves the node with ID, secondNode as a last child of the node with ID, firstItem
33389         * @example
33390         * var treeView = $("#treeView").data("kendoTreeView");
33391         * treeView.append($("#secondNode"), $("#firstItem"));
33392         *
33393         */
33394        append: function (nodeData, parentNode) {
33395            parentNode = parentNode || this.element;
33396
33397            var group = subGroup(parentNode);
33398
33399            return this._insertNode(nodeData, group.children().length, parentNode, group, function(item, group) {
33400                item.appendTo(group);
33401            });
33402        },
33403
33404        _remove: function (node, keepData) {
33405            var parentNode,
33406                prevSibling, nextSibling;
33407
33408            node = $(node, this.element);
33409
33410            parentNode = node.parent().parent();
33411            prevSibling = node.prev();
33412            nextSibling = node.next();
33413
33414            node[keepData ? "detach" : "remove"]();
33415
33416            if (parentNode.hasClass("k-item")) {
33417                updateNodeHtml(parentNode);
33418                updateNodeClasses(parentNode);
33419            }
33420
33421            updateNodeClasses(prevSibling);
33422            updateNodeClasses(nextSibling);
33423
33424            return node;
33425        },
33426
33427        /**
33428         *
33429         * Removes a node from a TreeView.
33430         *
33431         * @param {Selector} node
33432         * The node that is to be removed.
33433         *
33434         * @exampleTitle Remove the node with ID, firstItem
33435         * @example
33436         * var treeView = $("#treeView").data("kendoTreeView");
33437         * treeView.remove($("#firstItem"));
33438         *
33439         */
33440        remove: function (node) {
33441            return this._remove(node, false);
33442        },
33443
33444        /**
33445         *
33446         * Removes a node from a TreeView, but keeps its jQuery.data() objects.
33447         *
33448         * @param {Selector} node
33449         * The node that is to be detached.
33450         *
33451         * @returns {jQueryObject}
33452         * The node that has been detached.
33453         *
33454         * @exampleTitle Remove the node with ID, firstItem
33455         * @example
33456         * var treeView = $("#treeView").data("kendoTreeView");
33457         * var firstItem = $("#firstItem");
33458         * firstItem.data("id", 1);
33459         * treeview.detach(firstItem);
33460         * firstItem.data("id") == 1;
33461         *
33462         */
33463        detach: function (node) {
33464            return this._remove(node, true);
33465        },
33466
33467        /**
33468         *
33469         * Searches a TreeView for a node that has specific text.
33470         *
33471         * @param {String} text
33472         * The text that is being searched for.
33473         *
33474         * @returns {jQueryObject}
33475         * All nodes that have the text.
33476         *
33477         * @exampleTitle Search a TreeView for the item that has the text, "CSS3 is da bomb!"
33478         * @example
33479         * var treeView = $("#treeView").data("kendoTreeView");
33480         * var foundNode = treeView.findByText("CSS3 is da bomb!");
33481         *
33482         */
33483        findByText: function(text) {
33484            return $(this.element).find(".k-in").filter(function(i, element) {
33485                return $(element).text() == text;
33486            }).closest(NODE);
33487        }
33488    });
33489
33490    function TreeViewDragAndDrop(treeview) {
33491        var that = this;
33492
33493        that.treeview = treeview;
33494
33495        that._draggable = new ui.Draggable(treeview.element, {
33496           filter: "div:not(.k-state-disabled) .k-in",
33497           hint: function(node) {
33498               return templates.dragClue({ text: node.text() });
33499           },
33500           cursorOffset: {
33501               left: 10,
33502               top: kendo.support.touch ? -40 / kendo.support.zoomLevel() : 10
33503           },
33504           dragstart: proxy(that.dragstart, that),
33505           dragcancel: proxy(that.dragcancel, that),
33506           drag: proxy(that.drag, that),
33507           dragend: proxy(that.dragend, that)
33508        });
33509    }
33510
33511    TreeViewDragAndDrop.prototype = /** @ignore */{
33512        _hintStatus: function(newStatus) {
33513            var statusElement = this._draggable.hint.find(".k-drag-status")[0];
33514
33515            if (newStatus) {
33516                statusElement.className = "k-icon k-drag-status " + newStatus;
33517            } else {
33518                return $.trim(statusElement.className.replace(/k-(icon|drag-status)/g, ""));
33519            }
33520        },
33521
33522        dragstart: function (e) {
33523            var that = this,
33524                treeview = that.treeview,
33525                sourceNode = that.sourceNode = e.currentTarget.closest(NODE);
33526
33527            if (treeview.trigger(DRAGSTART, { sourceNode: sourceNode[0] })) {
33528                e.preventDefault();
33529            }
33530
33531            that.dropHint = $("<div class='k-drop-hint' />")
33532                .css(VISIBILITY, "hidden")
33533                .appendTo(treeview.element);
33534        },
33535
33536        drag: function (e) {
33537            var that = this,
33538                treeview = that.treeview,
33539                sourceNode = that.sourceNode,
33540                dropTarget = that.dropTarget = $(kendo.eventTarget(e)),
33541                statusClass,
33542                hoveredItem, hoveredItemPos, itemHeight, itemTop, itemContent, delta,
33543                insertOnTop, insertOnBottom, addChild;
33544
33545            if (!dropTarget.closest(".k-treeview").length) {
33546                // dragging node outside of treeview
33547                statusClass = "k-denied";
33548            } else if ($.contains(sourceNode[0], dropTarget[0])) {
33549                // dragging node within itself
33550                statusClass = "k-denied";
33551            } else {
33552                // moving or reordering node
33553                statusClass = "k-insert-middle";
33554
33555                that.dropHint.css(VISIBILITY, "visible");
33556
33557                hoveredItem = dropTarget.closest(".k-top,.k-mid,.k-bot");
33558
33559                if (hoveredItem.length > 0) {
33560                    itemHeight = hoveredItem.outerHeight();
33561                    itemTop = hoveredItem.offset().top;
33562                    itemContent = dropTarget.closest(".k-in");
33563                    delta = itemHeight / (itemContent.length > 0 ? 4 : 2);
33564
33565                    insertOnTop = e.pageY < (itemTop + delta);
33566                    insertOnBottom = (itemTop + itemHeight - delta) < e.pageY;
33567                    addChild = itemContent.length > 0 && !insertOnTop && !insertOnBottom;
33568
33569                    itemContent.toggleClass(KSTATEHOVER, addChild);
33570                    that.dropHint.css(VISIBILITY, addChild ? "hidden" : "visible");
33571
33572                    if (addChild) {
33573                        statusClass = "k-add";
33574                    } else {
33575                        hoveredItemPos = hoveredItem.position();
33576                        hoveredItemPos.top += insertOnTop ? 0 : itemHeight;
33577
33578                        that.dropHint
33579                            .css(hoveredItemPos)
33580                            [insertOnTop ? "prependTo" : "appendTo"](dropTarget.closest(NODE).children("div:first"));
33581
33582                        if (insertOnTop && hoveredItem.hasClass("k-top")) {
33583                            statusClass = "k-insert-top";
33584                        }
33585
33586                        if (insertOnBottom && hoveredItem.hasClass("k-bot")) {
33587                            statusClass = "k-insert-bottom";
33588                        }
33589                    }
33590                } else if (dropTarget[0] != that.dropHint[0]) {
33591                    statusClass = "k-denied";
33592                }
33593            }
33594
33595            treeview.trigger(DRAG, {
33596                sourceNode: sourceNode[0],
33597                dropTarget: dropTarget[0],
33598                pageY: e.pageY,
33599                pageX: e.pageX,
33600                statusClass: statusClass.substring(2),
33601                setStatusClass: function (value) {
33602                    statusClass = value;
33603                }
33604            });
33605
33606            if (statusClass.indexOf("k-insert") !== 0) {
33607                that.dropHint.css(VISIBILITY, "hidden");
33608            }
33609
33610            that._hintStatus(statusClass);
33611        },
33612
33613        dragcancel: function(e) {
33614            this.dropHint.remove();
33615            //treeview.trigger("nodeDragCancelled", { item: sourceNode[0] });
33616        },
33617
33618        dragend: function (e) {
33619            var that = this,
33620            treeview = that.treeview,
33621            dropPosition = "over",
33622            sourceNode = that.sourceNode,
33623            destinationNode,
33624            dropHint = that.dropHint,
33625            valid, dropPrevented;
33626
33627            if (dropHint.css(VISIBILITY) == "visible") {
33628                dropPosition = dropHint.prevAll(".k-in").length > 0 ? "after" : "before";
33629                destinationNode = dropHint.closest(NODE);
33630            } else if (that.dropTarget) {
33631                destinationNode = that.dropTarget.closest(NODE);
33632            }
33633
33634            valid = that._hintStatus() != "k-denied";
33635
33636            dropPrevented = treeview.trigger(DROP, {
33637                sourceNode: sourceNode[0],
33638                destinationNode: destinationNode[0],
33639                valid: valid,
33640                setValid: function(newValid) { valid = newValid; },
33641                dropTarget: e.target,
33642                dropPosition: dropPosition
33643            });
33644
33645            dropHint.remove();
33646
33647            if (!valid || dropPrevented) {
33648                that._draggable.dropped = valid;
33649                return;
33650            }
33651
33652            that._draggable.dropped = true;
33653
33654            // perform reorder / move
33655            if (dropPosition == "over") {
33656                treeview.append(sourceNode, destinationNode);
33657                treeview.expand(destinationNode);
33658            } else if (dropPosition == "before") {
33659                treeview.insertBefore(sourceNode, destinationNode);
33660            } else if (dropPosition == "after") {
33661                treeview.insertAfter(sourceNode, destinationNode);
33662            }
33663
33664            treeview.trigger(DRAGEND, {
33665                sourceNode: sourceNode[0],
33666                destinationNode: destinationNode[0],
33667                dropPosition: dropPosition
33668            });
33669        }
33670    };
33671
33672    // client-side rendering
33673
33674    extend(TreeView, {
33675        renderItem: function (options) {
33676            options = extend({ treeview: {}, group: {} }, options);
33677
33678            var empty = templates.empty,
33679                item = options.item,
33680                treeview = options.treeview;
33681
33682            return templates.item(extend(options, {
33683                image: item.imageUrl ? templates.image : empty,
33684                sprite: item.spriteCssClass ? templates.sprite : empty,
33685                itemWrapper: templates.itemWrapper,
33686                toggleButton: item.items ? templates.toggleButton : empty,
33687                checkbox: treeview.checkboxTemplate ? templates.checkbox : empty,
33688                subGroup: function(treeview, group, item) {
33689                    return TreeView.renderGroup({
33690                        items: item.items,
33691                        treeview: treeview,
33692                        group: {
33693                           expanded: item.expanded
33694                        }
33695                    });
33696                }
33697            }, rendering));
33698        },
33699
33700        renderGroup: function (options) {
33701            return templates.group(extend({
33702                renderItems: function(options) {
33703                    var html = "",
33704                        i = 0,
33705                        items = options.items,
33706                        len = items ? items.length : 0,
33707                        group = extend({ length: len }, options.group);
33708
33709                    for (; i < len; i++) {
33710                        html += TreeView.renderItem(extend(options, {
33711                            group: group,
33712                            item: extend({ index: i }, items[i])
33713                        }));
33714                    }
33715
33716                    return html;
33717                }
33718            }, options, rendering));
33719        }
33720    });
33721
33722    rendering = /** @ignore */{
33723        wrapperCssClass: function (group, item) {
33724            var result = "k-item",
33725                index = item.index;
33726
33727            if (group.firstLevel && index === 0) {
33728                result += " k-first";
33729            }
33730
33731            if (index == group.length-1) {
33732                result += " k-last";
33733            }
33734
33735            return result;
33736        },
33737        cssClass: function(group, item) {
33738            var result = "",
33739                index = item.index,
33740                groupLength = group.length - 1;
33741
33742            if (group.firstLevel && index === 0) {
33743                result += "k-top ";
33744            }
33745
33746            if (index === 0 && index != groupLength) {
33747                result += "k-top";
33748            } else if (index == groupLength) {
33749                result += "k-bot";
33750            } else {
33751                result += "k-mid";
33752            }
33753
33754            return result;
33755        },
33756        textClass: function(item) {
33757            var result = "k-in";
33758
33759            if (item.enabled === false) {
33760                result += " k-state-disabled";
33761            }
33762
33763            if (item.selected === true) {
33764                result += " k-state-selected";
33765            }
33766
33767            return result;
33768        },
33769        textAttributes: function(item) {
33770            return item.url ? " href='" + item.url + "'" : "";
33771        },
33772        toggleButtonClass: function(item) {
33773            var result = "k-icon";
33774
33775            if (item.expanded !== true) {
33776                result += " k-plus";
33777            } else {
33778                result += " k-minus";
33779            }
33780
33781            if (item.enabled === false) {
33782                result += "-disabled";
33783            }
33784
33785            return result;
33786        },
33787        text: function(item) {
33788            return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
33789        },
33790        template: function(treeview, item) {
33791            return treeview.template(extend({ item: item }, rendering));
33792        },
33793        checkboxTemplate: function(treeview, group, item) {
33794            return treeview.checkboxTemplate(extend({
33795                treeview: treeview,
33796                group: group,
33797                item: item
33798            }, rendering));
33799        },
33800        tag: function(item) {
33801            return item.url ? "a" : "span";
33802        },
33803        groupAttributes: function(group) {
33804            return group.expanded !== true ? " style='display:none'" : "";
33805        },
33806        groupCssClass: function(group) {
33807            var cssClass = "k-group";
33808
33809            if (group.firstLevel) {
33810                cssClass += " k-treeview-lines";
33811            }
33812
33813            return cssClass;
33814        }
33815    };
33816
33817    ui.plugin(TreeView);
33818})(jQuery);
33819/**
33820 * @fileOverview Provides a Slider and RangeSlider implementation which can be used to display a rich input for
33821 * selecting values or ranges of values.
33822 */
33823
33824(function($, undefined) {
33825    /**
33826     *
33827     * @name kendo.ui.Slider.Description
33828     *
33829     * @section
33830     * <p>
33831     *  The <strong>Slider</strong> provides a rich input for selecting values or ranges of values. Unlike the HTML5
33832     *  range input, the <strong>Slider</strong> presents a consistent experience across browsers and features a rich
33833     *  API and event model.
33834     * </p>
33835     * <h3>Getting Started</h3>
33836     * <p>There are two types of <strong>Slider</strong>:</p>
33837     * <ol>
33838     *  <li><strong>Slider</strong>, which presents one thumb and two opposing buttons for selecting a single value</li>
33839     *  <li><strong>RangeSlider</strong>, which present two thumbs for defining a range of values</li>
33840     * </ol>
33841     * <h4>Slider</h4>
33842     *
33843     * @exampleTitle Create an input element
33844     * @example
33845     * <input id="slider" />
33846     *
33847     * @section
33848     * <p>
33849     *  Initialization of a <strong>Slider</strong> should occur after the DOM is fully loaded. It is recommended that
33850     *  initialization the <strong>Slider</strong> occur within a handler is provided to $(document).ready().
33851     * </p>
33852     *
33853     * @exampleTitle Initialize a Slider using a selector within $(document).ready()
33854     * @example
33855     * $(document).ready(function() {
33856     *     $("#slider").kendoSlider();
33857     * });
33858     *
33859     * @section
33860     * <h4>RangeSlider</h4>
33861     * @exampleTitle Create two HTML input elements in a div
33862     * @example
33863     * <div id="rangeSlider">
33864     *     <input />
33865     *     <input />
33866     * </div>
33867     *
33868     * @section
33869     * <p>
33870     *  Initialization of a <strong>RangeSlider</strong> should occur after the DOM is fully loaded. It is recommended
33871     *  that initialization the <strong>RangeSlider</strong> occur within a handler is provided to $(document).ready().
33872     * </p>
33873     *
33874     * @exampleTitle Initialize a RangeSlider using a selector within $(document).ready()
33875     * @example
33876     * $(document).ready(function() {
33877     *     $("#rangeSlider").kendoRangeSlider();
33878     * });
33879     *
33880     * @section
33881     * <p>
33882     *  The <strong>RangeSlider</strong>
33882 requires two inputs to capture both ends of the value range. This benefits
33883     *  scenarios where JavaScript is disabled, in which case users will be presented with two inputs, still allowing
33884     *  them to input a valid range.
33885     * </p>
33886     * <h3>Customizing Slider Behaviors</h3>
33887     * <p>
33888     *  Many facets of the <strong>Slider</strong> and <strong>RangeSlider</strong> behavior can be configured through
33889     *  properties, including:
33890     * </p>
33891     * <ul>
33892     *  <li>Minimum and/or maximum values</li>
33893     *  <li>Orientation (horizontal or vertical)</li>
33894     *  <li>Small or large step</li>
33895     *  <li>Tooltip format/placement</li>
33896     * </ul>
33897     *
33898     * @exampleTitle Initialize a Slider and its properties
33899     * @example
33900     * $("#slider").kendoSlider({
33901     *     min: 10,
33902     *     max: 50,
33903     *     orientation: "vertical",
33904     *     smallStep: 1,
33905     *     largeStep: 10
33906     * });
33907     *
33908     * @section
33909     * <h3>Accessing an Existing Slider</h3>
33910     * <p>
33911     *  You can reference an existing <strong>Slider</strong> instance via
33912     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
33913     *  use the API to control its behavior.
33914     * </p>
33915     *
33916     * @exampleTitle Accessing an existing Slider instance
33917     * @example
33918     * var slider = $("#slider").data("kendoSlider");
33919     *
33920     */
33921    var kendo = window.kendo,
33922        Widget = kendo.ui.Widget,
33923        Draggable = kendo.ui.Draggable,
33924        extend = $.extend,
33925        format = kendo.format,
33926        parse = kendo.parseFloat,
33927        proxy = $.proxy,
33928        isArray = $.isArray,
33929        math = Math,
33930        support = kendo.support,
33931        touch = support.touch,
33932        pointers = support.pointers,
33933        CHANGE = "change",
33934        SLIDE = "slide",
33935        MOUSE_DOWN = touch ? "touchstart" : "mousedown",
33936        TRACK_MOUSE_DOWN = pointers ? "MSPointerDown" : "mousedown",
33937        MOUSE_UP = touch ? "touchend" : "mouseup",
33938        MOVE_SELECTION = "moveSelection",
33939        KEY_DOWN = "keydown",
33940        CLICK = "click",
33941        MOUSE_OVER = "mouseover",
33942        DRAG_HANDLE = ".k-draghandle",
33943        TRACK_SELECTOR = ".k-slider-track",
33944        TICK_SELECTOR = ".k-tick",
33945        STATE_SELECTED = "k-state-selected",
33946        STATE_DEFAULT = "k-state-default",
33947        STATE_DISABLED = "k-state-disabled",
33948        PRECISION = 3,
33949        DISABLED = "disabled";
33950
33951    var SliderBase = Widget.extend({
33952        init: function(element, options) {
33953            var that = this;
33954
33955            Widget.fn.init.call(that, element, options);
33956
33957            options = that.options;
33958
33959            that._distance = options.max - options.min;
33960            that._isHorizontal = options.orientation == "horizontal";
33961            that._position = that._isHorizontal ? "left" : "bottom";
33962            that._size = that._isHorizontal ? "width" : "height";
33963            that._outerSize = that._isHorizontal ? "outerWidth" : "outerHeight";
33964
33965            options.tooltip.format = options.tooltip.enabled ? options.tooltip.format || "{0}" : "{0}";
33966
33967            that._createHtml();
33968            that.wrapper = that.element.closest(".k-slider");
33969            that._trackDiv = that.wrapper.find(TRACK_SELECTOR);
33970
33971            that._setTrackDivWidth();
33972
33973            that._maxSelection = that._trackDiv[that._size]();
33974
33975            var sizeBetweenTicks = that._maxSelection / ((options.max - options.min) / options.smallStep);
33976            var pixelWidths = that._calculateItemsWidth(math.floor(that._distance / options.smallStep));
33977
33978            if (options.tickPlacement != "none" && sizeBetweenTicks >= 2) {
33979                that._trackDiv.before(createSliderItems(options, that._distance));
33980                that._setItemsWidth(pixelWidths);
33981                that._setItemsTitle();
33982                that._setItemsLargeTick();
33983            }
33984
33985            that._calculateSteps(pixelWidths);
33986
33987            that[options.enabled ? "enable" : "disable"]();
33988
33989            that._keyMap = {
33990                37: step(-options.smallStep), // left arrow
33991                40: step(-options.smallStep), // down arrow
33992                39: step(+options.smallStep), // right arrow
33993                38: step(+options.smallStep), // up arrow
33994                35: setValue(options.max), // end
33995                36: setValue(options.min), // home
33996                33: step(+options.largeStep), // page up
33997                34: step(-options.largeStep)  // page down
33998            };
33999
34000            kendo.notify(that);
34001        },
34002
34003        events: [
34004            /**
34005            * Fires when the slider value changes as a result of selecting a new value with the drag handle, buttons or keyboard.
34006            * @name kendo.ui.Slider#change
34007            * @event
34008            * @param {Event} e
34009            * @param {Number} e.value Represents the updated value of the slider.
34010            **/
34011
34012            /**
34013            * Fires when the rangeSlider value changes as a result of selecting a new value with one of the drag handles or the keyboard.
34014            * @name kendo.ui.RangeSlider#change
34015            * @event
34016            * @param {Event} e
34017            * @param {Number} e.value Represents the updated array of values of the first and second drag handle.
34018            **/
34019            CHANGE,
34020
34021            /**
34022            * Fires when the user drags the drag handle to a new position.
34023            * @name kendo.ui.Slider#slide
34024            * @event
34025            * @param {Event} e
34026            * @param {Number} e.value Represents the value from the current position of the drag handle.
34027            **/
34028
34029            /**
34030            * Fires when the user drags the drag handle to a new position.
34031            * @name kendo.ui.RangeSlider#slide
34032            * @event
34033            * @param {Event} e
34034            * @param {Number} e.value Represents an array of values of the current positions of the first and second drag handle.
34035            **/
34036            SLIDE
34037        ],
34038
34039        options: {
34040            enabled: true,
34041            min: 0,
34042            max: 10,
34043            smallStep: 1,
34044            largeStep: 5,
34045            orientation: "horizontal",
34046            tickPlacement: "both",
34047            tooltip: { enabled: true, format: "{0}" }
34048        },
34049
34050        _setTrackDivWidth: function() {
34051            var that = this,
34052                trackDivPosition = parseFloat(that._trackDiv.css(that._position), 10) * 2;
34053
34054            that._trackDiv[that._size]((that.wrapper[that._size]() - 2) - trackDivPosition);
34055        },
34056
34057        _setItemsWidth: function(pixelWidths) {
34058            var that = this,
34059                options = that.options,
34060                first = 0,
34061                last = pixelWidths.length - 1,
34062                items = that.wrapper.find(TICK_SELECTOR),
34063                i,
34064                paddingTop = 0,
34065                bordersWidth = 2,
34066                count = items.length,
34067                selection = 0;
34068
34069            for (i = 0; i < count - 2; i++) {
34070                $(items[i + 1])[that._size](pixelWidths[i]);
34071            }
34072
34073            if (that._isHorizontal) {
34074                $(items[first]).addClass("k-first")[that._size](pixelWidths[last - 1]);
34075                $(items[last]).addClass("k-last")[that._size](pixelWidths[last]);
34076            } else {
34077                $(items[last]).addClass("k-first")[that._size](pixelWidths[last]);
34078                $(items[first]).addClass("k-last")[that._size](pixelWidths[last - 1]);
34079            }
34080
34081            if (that._distance % options.smallStep !== 0 && !that._isHorizontal) {
34082                for (i = 0; i < pixelWidths.length; i++) {
34083                    selection += pixelWidths[i];
34084                }
34085
34086                paddingTop = that._maxSelection - selection;
34087                paddingTop += parseFloat(that._trackDiv.css(that._position), 10) + bordersWidth;
34088
34089                that.wrapper.find(".k-slider-items").css("padding-top", paddingTop);
34090            }
34091        },
34092
34093        _setItemsTitle: function() {
34094            var that = this,
34095                options = that.options,
34096                items = that.wrapper.find(TICK_SELECTOR),
34097                titleNumber = options.min,
34098                count = items.length,
34099                i = that._isHorizontal ? 0 : count - 1,
34100                limit = that._isHorizontal ? count : -1,
34101                increment = that._isHorizontal ? 1 : -1;
34102
34103            for (; i - limit !== 0 ; i += increment) {
34104                $(items[i]).attr("title", format(options.tooltip.format, round(titleNumber)));
34105                titleNumber += options.smallStep;
34106            }
34107        },
34108
34109        _setItemsLargeTick: function() {
34110            var that = this,
34111                options = that.options,
34112                i,
34113                items = that.wrapper.find(TICK_SELECTOR),
34114                item = {},
34115                step = round(options.largeStep / options.smallStep);
34116
34117            if ((1000 * options.largeStep) % (1000 * options.smallStep) === 0) {
34118                if (that._isHorizontal) {
34119                    for (i = 0; i < items.length; i = round(i + step)) {
34120                        item = $(items[i]);
34121
34122                        item.addClass("k-tick-large")
34123                            .html("<span class='k-label'>" + item.attr("title") + "</span>");
34124                    }
34125                } else {
34126                    for (i = items.length - 1; i >= 0; i = round(i - step)) {
34127                        item = $(items[i]);
34128
34129                        item.addClass("k-tick-large")
34130                            .html("<span class='k-label'>" + item.attr("title") + "</span>");
34131
34132                        if (i !== 0 && i !== items.length - 1) {
34133                            item.css("line-height", item[that._size]() + "px");
34134                        }
34135                    }
34136                }
34137            }
34138        },
34139
34140        _calculateItemsWidth: function(itemsCount) {
34141            var that = this,
34142                options = that.options,
34143                trackDivSize = parseFloat(that._trackDiv.css(that._size)) + 1,
34144                pixelStep = trackDivSize / that._distance,
34145                itemWidth,
34146                pixelWidths,
34147                i;
34148
34149            if ((that._distance / options.smallStep) - math.floor(that._distance / options.smallStep) > 0) {
34150                trackDivSize -= ((that._distance % options.smallStep) * pixelStep);
34151            }
34152
34153            itemWidth = trackDivSize / itemsCount;
34154            pixelWidths = [];
34155
34156            for (i = 0; i < itemsCount - 1; i++) {
34157                pixelWidths[i] = itemWidth;
34158            }
34159
34160            pixelWidths[itemsCount - 1] = pixelWidths[itemsCount] = itemWidth / 2;
34161            return that._roundWidths(pixelWidths);
34162        },
34163
34164        _roundWidths: function(pixelWidthsArray) {
34165            var balance = 0,
34166                count = pixelWidthsArray.length,
34167                i;
34168
34169            for (i = 0; i < count; i++) {
34170                balance += (pixelWidthsArray[i] - math.floor(pixelWidthsArray[i]));
34171                pixelWidthsArray[i] = math.floor(pixelWidthsArray[i]);
34172            }
34173
34174            balance = math.round(balance);
34175
34176            return this._addAdditionalSize(balance, pixelWidthsArray);
34177        },
34178
34179        _addAdditionalSize: function(additionalSize, pixelWidthsArray) {
34180            if (additionalSize === 0) {
34181                return pixelWidthsArray;
34182            }
34183
34184            //set step size
34185            var step = parseFloat(pixelWidthsArray.length - 1) / parseFloat(additionalSize == 1 ? additionalSize : additionalSize - 1),
34186                i;
34187
34188            for (i = 0; i < additionalSize; i++) {
34189                pixelWidthsArray[parseInt(math.round(step * i), 10)] += 1;
34190            }
34191
34192            return pixelWidthsArray;
34193        },
34194
34195        _calculateSteps: function(pixelWidths) {
34196            var that = this,
34197                options = that.options,
34198                val = options.min,
34199                selection = 0,
34200                itemsCount = math.ceil(that._distance / options.smallStep),
34201                i = 1,
34202                lastItem;
34203
34204            itemsCount += (that._distance / options.smallStep) % 1 === 0 ? 1 : 0;
34205            pixelWidths.splice(0, 0, pixelWidths[itemsCount - 2] * 2);
34206            pixelWidths.splice(itemsCount -1, 1, pixelWidths.pop() * 2);
34207
34208            that._pixelSteps = [selection];
34209            that._values = [val];
34210
34211            if (itemsCount === 0) {
34212                return;
34213            }
34214
34215            while (i < itemsCount) {
34216                selection += (pixelWidths[i - 1] + pixelWidths[i]) / 2;
34217                that._pixelSteps[i] = selection;
34218                that._values[i] = val += options.smallStep;
34219
34220                i++;
34221            }
34222
34223            lastItem = that._distance % options.smallStep === 0 ? itemsCount - 1 : itemsCount;
34224
34225            that._pixelSteps[lastItem] = that._maxSelection;
34226            that._values[lastItem] = options.max;
34227        },
34228
34229        _getValueFromPosition: function(mousePosition, dragableArea) {
34230            var that = this,
34231                options = that.options,
34232                step = math.max(options.smallStep * (that._maxSelection / that._distance), 0),
34233                position = 0,
34234                halfStep = (step / 2),
34235                i;
34236
34237            if (that._isHorizontal) {
34238                position = mousePosition - dragableArea.startPoint;
34239            } else {
34240                position = dragableArea.startPoint - mousePosition;
34241            }
34242
34243            if (that._maxSelection - ((parseInt(that._maxSelection % step, 10) - 3) / 2) < position) {
34244                return options.max;
34245            }
34246
34247            for (i = 0; i < that._pixelSteps.length; i++) {
34248                if (math.abs(that._pixelSteps[i] - position) - 1 <= halfStep) {
34249                    return round(that._values[i]);
34250                }
34251            }
34252        },
34253
34254        _getDragableArea: function() {
34255            var that = this,
34256                offsetLeft = that._trackDiv.offset().left,
34257                offsetTop = that._trackDiv.offset().top;
34258
34259            return {
34260                startPoint: that._isHorizontal ? offsetLeft : offsetTop + that._maxSelection,
34261                endPoint: that._isHorizontal ? offsetLeft + that._maxSelection : offsetTop
34262            };
34263        },
34264
34265        _createHtml: function() {
34266            var that = this,
34267                element = that.element,
34268                options = that.options,
34269                inputs = element.find("input");
34270
34271            if (inputs.length == 2) {
34272                inputs.eq(0).val(options.selectionStart);
34273                inputs.eq(1).val(options.selectionEnd);
34274            } else {
34275                element.val(options.value);
34276            }
34277
34278            element.wrap(createWrapper(options, element, that._isHorizontal)).hide();
34279
34280            if (options.showButtons) {
34281                element.before(createButton(options, "increase", that._isHorizontal))
34282                       .before(createButton(options, "decrease", that._isHorizontal));
34283            }
34284
34285            element.before(createTrack(element));
34286        }
34287    });
34288
34289    function createWrapper (options, element, isHorizontal) {
34290        var orientationCssClass = isHorizontal ? " k-slider-horizontal" : " k-slider-vertical",
34291            style = options.style ? options.style : element.attr("style"),
34292            cssClasses = element.attr("class") ? (" " + element.attr("class")) : "",
34293            tickPlacementCssClass = "";
34294
34295        if (options.tickPlacement == "bottomRight") {
34296            tickPlacementCssClass = " k-slider-bottomright";
34297        } else if (options.tickPlacement == "topLeft") {
34298            tickPlacementCssClass = " k-slider-topleft";
34299        }
34300
34301        style = style ? " style='" + style + "'" : "";
34302
34303        return "<div class='k-widget k-slider" + orientationCssClass + cssClasses + "'" + style + ">" +
34304               "<div class='k-slider-wrap" + (options.showButtons ? " k-slider-buttons" : "") + tickPlacementCssClass +
34305               "'></div></div>";
34306    }
34307
34308    function createButton (options, type, isHorizontal) {
34309        var buttonCssClass = "";
34310
34311        if (type == "increase") {
34312            buttonCssClass = isHorizontal ? "k-arrow-next" : "k-arrow-up";
34313        } else {
34314            buttonCssClass = isHorizontal ? "k-arrow-prev" : "k-arrow-down";
34315        }
34316
34317        return "<a class='k-button k-button-" + type + "'><span class='k-icon " + buttonCssClass +
34318               "' title='" + options[type + "ButtonTitle"] + "'>" + options[type + "ButtonTitle"] + "</span></a>";
34319    }
34320
34321    function createSliderItems (options, distance) {
34322        var result = "<ul class='k-reset k-slider-items'>",
34323            count = math.floor(round(distance / options.smallStep)) + 1,
34324            i;
34325
34326        for(i = 0; i < count; i++) {
34327            result += "<li class='k-tick'>&nbsp;</li>";
34328        }
34329
34330        result += "</ul>";
34331
34332        return result;
34333    }
34334
34335    function createTrack (element) {
34336        var dragHandleCount = element.is("input") ? 1 : 2;
34337
34338        return "<div class='k-slider-track'><div class='k-slider-selection'><!-- --></div>" +
34339               "<a href='#' class='k-draghandle' title='Drag'>Drag</a>" +
34340               (dragHandleCount > 1 ? "<a href='#' class='k-draghandle' title='Drag'>Drag</a>" : "") +
34341               "</div>";
34342    }
34343
34344    function step(stepValue) {
34345        return function (value) {
34346            return value + stepValue;
34347        };
34348    }
34349
34350    function setValue(value) {
34351        return function () {
34352            return value;
34353        };
34354    }
34355
34356    function formatValue(value) {
34357        return (value + "").replace(".", kendo.cultures.current.numberFormat["."]);
34358    }
34359
34360    function round(value) {
34361        value = parseFloat(value, 10);
34362        var power = math.pow(10, PRECISION || 0);
34363        return math.round(value * power) / power;
34364    }
34365
34366    function parseAttr(element, name) {
34367        return parse(element.getAttribute(name)) || undefined;
34368    }
34369
34370    var touchLocation = function(e) {
34371        return {
34372            idx: 0,
34373            x: e.pageX,
34374            y: e.pageY
34375        };
34376    };
34377
34378    if (support.pointers) {
34379        /** @ignore */
34380        touchLocation = function(e) {
34381            return {
34382                idx: 0,
34383                x: e.originalEvent.clientX,
34384                y: e.originalEvent.clientY
34385            };
34386        };
34387    }
34388
34389    if (support.touch) {
34390        /** @ignore */
34391        touchLocation = function(e, id) {
34392            var changedTouches = e.changedTouches || e.originalEvent.changedTouches;
34393
34394            if (id) {
34395                var output = null;
34396                $.each(changedTouches, function(idx, value) {
34397                    if (id == value.identifier) {
34398                        output = {
34399                            idx: value.identifier,
34400                            x: value.pageX,
34401                            y: value.pageY
34402                        };
34403                    }
34404                });
34405                return output;
34406            } else {
34407                return {
34408                    idx: changedTouches[0].identifier,
34409                    x: changedTouches[0].pageX,
34410                    y: changedTouches[0].pageY
34411                };
34412            }
34413        };
34414    }
34415
34416    var Slider = SliderBase.extend(/** @lends kendo.ui.Slider.prototype */{
34417        /**
34418         *
34419         * Creates a Slider instance.
34420         *
34421         * @constructs
34422         * @extends kendo.ui.Widget
34423         *
34424         * @param {DomElement} element DOM element
34425         * @param {Object} options Configuration options.
34426         *
34427         * @option {Number} [min] <0>
34428         * The minimum value of the <strong>Slider</strong>.
34429         *
34430         * @option {Number} [max] <10>
34431         * The maximum value of the <strong>Slider</strong>.
34432         *
34433         * @option {Boolean} [showButtons] <true>
34434         * Can be used to show (<b>true</b>) or hide (<b>false</b>) the
34435         * increase and decrease buttons of a <strong>Slider</strong>.
34436         *
34437         * @option {Object} [tooltip]
34438         * Configuration of the <strong>Slider</strong> tooltip.
34439         *
34440         * @option {Boolean} [tooltip.enabled] <true>
34441         * Disables (<b>false</b>) or enables (<b>true</b>) the tooltip of
34442         * the <strong>Slider</strong>.
34443         *
34444         * @option {String} [tooltip.format] <"{0}">
34445         * Format string for the text of the tooltip. Note: The applied
34446         * format will also influence the appearance of the <strong>Slider</strong>
34447         * tick labels.
34448         *
34449         * @option {Number} [value] <0>
34450         * The underlying value of the <strong>Slider</strong>.
34451         *
34452         * @option {String} [orientation] <"horizontal">
34453         * The orientation of a <strong>Slider</strong>; <strong>"horizontal"</strong> or <strong>"vertical"</strong>.
34454         *
34455         * @option {String} [tickPlacement] <"both">
34456         * Denotes the location of the tick marks in the <strong>Slider</strong>. The available options are:
34457         * <div class="details-list">
34458         *    <dl>
34459         *         <dt>
34460         *              <code>"topLeft"</code>
34461         *         </dt>
34462         *         <dd>
34463         *              Tick marks are located on the top of the horizontal widget or on the left of
34464         *   the vertical widget.
34465         *         </dd>
34466         *         <dt>
34467         *              <code>"bottomRight"</code>
34468         *         </dt>
34469         *         <dd>
34470         *             Tick marks are located on the bottom of the horizontal widget or on the
34471         *   right side of the vertical widget.
34472         *         </dd>
34473         *         <dt>
34474         *              <code>"both"</code>
34475         *         </dt>
34476         *         <dd>
34477         *             Tick marks are located on both sides of the widget.
34478         *         </dd>
34479         *         <dt>
34480         *              <code>"none"</code>
34481         *         </dt>
34482         *         <dd>
34483         *             Tick marks are not visible.
34484         *         </dd>
34485         *    </dl>
34486         * </div>
34487         * @option {Number} [smallStep] <1>
34488         * The small step value of the <strong>Slider</strong>. The underlying value will be changed when the end user
34489         * (1) clicks on the increase or decrease buttons of the <strong>Slider</strong>, (2) presses the arrow keys
34490         * (the drag handle must be focused), or (3) drags the drag handle.
34491         *
34492         * @option {Number} [largeStep] <5>
34493         * The delta with which the value will change when the user presses the Page Up or Page Down key (the drag
34494         * handle must be focused). Note: The allied largeStep will also set large tick for every large step.
34495         *
34496         * @option {String} [increaseButtonTitle] <"Increase">
34497         * The title of the increase button of the <strong>Slider</strong>.
34498         *
34499         * @option {String} [decreaseButtonTitle] <"Decrease">
34500         * The title of the decrease button of the <strong>Slider</strong>.
34501         *
34502         */
34503        init: function(element, options) {
34504            var that = this,
34505                dragHandle;
34506
34507            element.type = "text";
34508
34509            element = $(element);
34510
34511            options = extend({}, {
34512                value: parseAttr(element[0], "value"),
34513                min: parseAttr(element[0], "min"),
34514                max: parseAttr(element[0], "max"),
34515                smallStep: parseAttr(element[0], "step")
34516            }, options);
34517
34518            if (options && options.enabled === undefined) {
34519                options.enabled = !element.is("[disabled]");
34520            }
34521
34522            SliderBase.fn.init.call(that, element, options);
34523            options = that.options;
34524
34525            that._setValueInRange(options.value);
34526            dragHandle = that.wrapper.find(DRAG_HANDLE);
34527
34528            new Slider.Selection(dragHandle, that, options);
34529            that._drag = new Slider.Drag(dragHandle, "", that, options);
34530        },
34531
34532        options: {
34533            name: "Slider",
34534            value: 0,
34535            showButtons: true,
34536            increaseButtonTitle: "Increase",
34537            decreaseButtonTitle: "Decrease",
34538            tooltip: { format: "{0}" }
34539        },
34540
34541        /**
34542         * Enable/Disable the <strong>Slider</strong> widget.
34543         * @param {Boolean} enable
34544         * The argument, which defines whether to enable/disable the <strong>Slider</strong>.
34545         * @example
34546         * // get a reference to the slider widget
34547         * var slider = $("#slider").data("kendoSlider");
34548         *
34549         * // disables the slider
34550         * slider.enable(false);
34551         *
34552         * // enables the slider
34553         * slider.enable(true);
34554         */
34555        enable: function (enable) {
34556            var that = this,
34557                options = that.options,
34558                clickHandler,
34559                move;
34560
34561            that.disable();
34562            if (enable === false) {
34563                return;
34564            }
34565
34566            that.wrapper
34567                .removeClass(STATE_DISABLED)
34568                .addClass(STATE_DEFAULT);
34569
34570            that.wrapper.find("input").removeAttr(DISABLED);
34571
34572            clickHandler = function (e) {
34573                var location = touchLocation(e),
34574                    mousePosition = that._isHorizontal ? location.x : location.y,
34575                    dragableArea = that._getDragableArea(),
34576                    target = $(e.target);
34577
34578                if (target.hasClass("k-draghandle")) {
34579                    target.addClass(STATE_SELECTED);
34580                    return;
34581                }
34582
34583                that._update(that._getValueFromPosition(mousePosition, dragableArea));
34584
34585                that._drag.dragstart(e);
34586            };
34587
34588            that.wrapper
34589                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR)
34590                    .bind(TRACK_MOUSE_DOWN, clickHandler)
34591                    .end().bind(TRACK_MOUSE_DOWN, function() {
34592                        $(document.documentElement).one("selectstart", kendo.preventDefault);
34593                    });
34594
34595            that.wrapper
34596                .find(DRAG_HANDLE)
34597                .bind(MOUSE_UP, function (e) {
34598                    $(e.target).removeClass(STATE_SELECTED);
34599                })
34600                .bind(CLICK, function (e) {
34601                    e.preventDefault();
34602                });
34603
34604            move = proxy(function (sign) {
34605                that._setValueInRange(that._nextValueByIndex(that._valueIndex + (sign * 1)));
34606            }, that);
34607
34608            if (options.showButtons) {
34609                var mouseDownHandler = proxy(function(e, sign) {
34610                    if (e.which === 1 || (touch && e.which === 0)) {
34611                        move(sign);
34612
34613                        this.timeout = setTimeout(proxy(function () {
34614                            this.timer = setInterval(function () {
34615                                move(sign);
34616                            }, 60);
34617                        }, this), 200);
34618                    }
34619                }, that);
34620
34621                that.wrapper.find(".k-button")
34622                    .bind(MOUSE_UP, proxy(function (e) {
34623                        this._clearTimer();
34624                    }, that))
34625                    .bind(MOUSE_OVER, function (e) {
34626                        $(e.currentTarget).addClass("k-state-hover");
34627                    })
34628                    .bind("mouseout", proxy(function (e) {
34629                        $(e.currentTarget).removeClass("k-state-hover");
34630                        this._clearTimer();
34631                    }, that))
34632                    .eq(0)
34633                    .bind(MOUSE_DOWN, proxy(function (e) {
34634                        mouseDownHandler(e, 1);
34635                    }, that))
34636                    .click(false)
34637                    .end()
34638                    .eq(1)
34639                    .bind(MOUSE_DOWN, proxy(function (e) {
34640                        mouseDownHandler(e, -1);
34641                    }, that))
34642                    .click(false);
34643            }
34644
34645            that.wrapper
34646                .find(DRAG_HANDLE).bind(KEY_DOWN, proxy(this._key
34646down, that));
34647
34648            options.enabled = true;
34649        },
34650
34651        disable: function () {
34652            var that = this;
34653
34654            that.wrapper
34655                .removeClass(STATE_DEFAULT)
34656                .addClass(STATE_DISABLED);
34657
34658            $(that.element).attr(DISABLED, DISABLED);
34659
34660            that.wrapper
34661                .find(".k-button")
34662                .unbind(MOUSE_DOWN)
34663                .bind(MOUSE_DOWN, false)
34664                .unbind(MOUSE_UP)
34665                .bind(MOUSE_UP, false)
34666                .unbind("mouseleave")
34667                .bind("mouseleave", false)
34668                .unbind(MOUSE_OVER)
34669                .bind(MOUSE_OVER, false);
34670
34671            that.wrapper
34672                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).unbind(TRACK_MOUSE_DOWN);
34673
34674            that.wrapper
34675                .find(DRAG_HANDLE)
34676                .unbind(MOUSE_UP)
34677                .unbind(KEY_DOWN)
34678                .unbind(CLICK)
34679                .bind(KEY_DOWN, false);
34680
34681            that.options.enabled = false;
34682        },
34683
34684        _update: function (val) {
34685            var that = this,
34686                change = that.value() != val;
34687
34688            that.value(val);
34689
34690            if (change) {
34691                that.trigger(CHANGE, { value: that.options.value });
34692            }
34693        },
34694
34695        /**
34696         *
34697         * Gets or sets the value of a <strong>Slider</strong>. It accepts a string or number as parameters and returns
34698         * a number representing the underlying value.
34699         *
34700         * @param {String} [value]
34701         * The value to be set for a Slider.
34702         *
34703         * @example
34704         * var slider = $("#slider").data("kendoSlider");
34705         * var sliderValue = slider.value();
34706         *
34707         */
34708        value: function (value) {
34709            var that = this,
34710                options = that.options;
34711
34712            value = round(value);
34713            if (isNaN(value)) {
34714                return options.value;
34715            }
34716
34717            if (value >= options.min && value <= options.max) {
34718                if (options.value != value) {
34719                    that.element.attr("value", formatValue(value));
34720                    options.value = value;
34721                    that._refresh();
34722                }
34723            }
34724        },
34725
34726        _refresh: function () {
34727            this.trigger(MOVE_SELECTION, { value: this.options.value });
34728        },
34729
34730        _clearTimer: function (e) {
34731            clearTimeout(this.timeout);
34732            clearInterval(this.timer);
34733        },
34734
34735        _keydown: function (e) {
34736            var that = this;
34737
34738            if (e.keyCode in that._keyMap) {
34739                that._setValueInRange(that._keyMap[e.keyCode](that.options.value));
34740                e.preventDefault();
34741            }
34742        },
34743
34744        _setValueInRange: function (val) {
34745            var that = this,
34746                options = that.options;
34747
34748            val = round(val);
34749            if (isNaN(val)) {
34750                that._update(options.min);
34751                return;
34752            }
34753
34754            val = math.max(math.min(val, options.max), options.min);
34755            that._update(val);
34756        },
34757
34758        _nextValueByIndex: function (index) {
34759            var count = this._values.length;
34760            return this._values[math.max(0, math.min(index, count - 1))];
34761        }
34762    });
34763
34764    Slider.Selection = function (dragHandle, that, options) {
34765        function moveSelection (val) {
34766            var selectionValue = val - options.min,
34767                index = that._valueIndex = math.ceil(round(selectionValue / options.smallStep)),
34768                selection = parseInt(that._pixelSteps[index], 10),
34769                selectionDiv = that._trackDiv.find(".k-slider-selection"),
34770                halfDragHanndle = parseInt(dragHandle[that._outerSize]() / 2, 10);
34771
34772            selectionDiv[that._size](selection);
34773            dragHandle.css(that._position, selection - halfDragHanndle);
34774        }
34775
34776        moveSelection(options.value);
34777
34778        that.bind([CHANGE, SLIDE, MOVE_SELECTION], function (e) {
34779            moveSelection(parseFloat(e.value, 10));
34780        });
34781    };
34782
34783    Slider.Drag = function (dragHandle, type, owner, options) {
34784        var that = this;
34785        that.owner = owner;
34786        that.options = options;
34787        that.dragHandle = dragHandle;
34788        that.dragHandleSize = dragHandle[owner._outerSize]();
34789        that.type = type;
34790
34791        that.draggable = new Draggable(dragHandle, {
34792            threshold: 0,
34793            dragstart: proxy(that._dragstart, that),
34794            drag: proxy(that.drag, that),
34795            dragend: proxy(that.dragend, that),
34796            dragcancel: proxy(that.dragcancel, that)
34797        });
34798
34799        dragHandle.click(false);
34800    };
34801
34802    Slider.Drag.prototype = {
34803        dragstart: function(e) {
34804            // HACK to initiate click on the line
34805            this.draggable.drag._start(e);
34806        },
34807
34808        _dragstart: function(e) {
34809            var that = this,
34810                owner = that.owner,
34811                options = that.options,
34812                tooltip = options.tooltip,
34813                html = '',
34814                tooltipTemplate,
34815                formattedSelectionStart,
34816                formattedSelectionEnd;
34817
34818            if (!options.enabled) {
34819                e.preventDefault();
34820                return;
34821            }
34822
34823            owner.element.unbind(MOUSE_OVER);
34824            that.dragHandle.addClass(STATE_SELECTED);
34825
34826            that.dragableArea = owner._getDragableArea();
34827            that.step = math.max(options.smallStep * (owner._maxSelection / owner._distance), 0);
34828
34829            if (that.type) {
34830                that.selectionStart = options.selectionStart;
34831                that.selectionEnd = options.selectionEnd;
34832                owner._setZIndex(that.type);
34833            } else {
34834                that.oldVal = that.val = options.value;
34835            }
34836
34837            if (tooltip.enabled) {
34838                if (tooltip.template) {
34839                    tooltipTemplate = that.tooltipTemplate = kendo.template(tooltip.template);
34840                }
34841
34842                that.tooltipDiv = $("<div class='k-widget k-tooltip'><!-- --></div>").appendTo(document.body);
34843
34844                if (that.type) {
34845                    if (that.tooltipTemplate) {
34846                        html = tooltipTemplate({
34847                            selectionStart: that.selectionStart,
34848                            selectionEnd: that.selectionEnd
34849                        });
34850                    } else {
34851                        formattedSelectionStart = format(tooltip.format, that.selectionStart);
34852                        formattedSelectionEnd = format(tooltip.format, that.selectionEnd);
34853
34854                        html = formattedSelectionStart + ' - ' + formattedSelectionEnd;
34855                    }
34856                } else {
34857                    that.tooltipInnerDiv = "<div class='k-callout k-callout-" + (owner._isHorizontal ? 's' : 'e') + "'><!-- --></div>";
34858                    if (that.tooltipTemplate) {
34859                        html = tooltipTemplate({
34860                            value: that.val
34861                        });
34862                    } else {
34863                        html = format(tooltip.format, that.val);
34864                    }
34865                    html += that.tooltipInnerDiv;
34866                }
34867
34868                that.tooltipDiv.html(html);
34869
34870                that.moveTooltip();
34871            }
34872        },
34873
34874        drag: function (e) {
34875            var that = this,
34876                owner = that.owner,
34877                options = that.options,
34878                x = e.x.location,
34879                y = e.y.location,
34880                startPoint = that.dragableArea.startPoint,
34881                endPoint = that.dragableArea.endPoint,
34882                tooltip = options.tooltip,
34883                html = "",
34884                tooltipTemplate = that.tooltipTemplate,
34885                slideParams,
34886                formattedSelectionStart,
34887                formattedSelectionEnd;
34888
34889            e.preventDefault();
34890
34891            if (owner._isHorizontal) {
34892                that.val = that.constrainValue(x, startPoint, endPoint, x >= endPoint);
34893            } else {
34894                that.val = that.constrainValue(y, endPoint, startPoint, y <= endPoint);
34895            }
34896
34897            if (that.oldVal != that.val) {
34898                that.oldVal = that.val;
34899
34900                if (that.type) {
34901                    if (that.type == "firstHandle") {
34902                        if (that.val < that.selectionEnd) {
34903                            that.selectionStart = that.val;
34904                        } else {
34905                            that.selectionStart = that.selectionEnd = that.val;
34906                        }
34907                    } else {
34908                        if (that.val > that.selectionStart) {
34909                            that.selectionEnd = that.val;
34910                        } else {
34911                            that.selectionStart = that.selectionEnd = that.val;
34912                        }
34913                    }
34914                    slideParams = {
34915                        values: [that.selectionStart, that.selectionEnd],
34916                        value: [that.selectionStart, that.selectionEnd]
34917                    };
34918                } else {
34919                    slideParams = { value: that.val };
34920                }
34921
34922                owner.trigger(SLIDE, slideParams);
34923
34924                if (tooltip.enabled) {
34925                    if (that.type) {
34926                        if (that.tooltipTemplate) {
34927                            html = tooltipTemplate({
34928                                selectionStart: that.selectionStart,
34929                                selectionEnd: that.selectionEnd
34930                        });
34931                        } else {
34932                            formattedSelectionStart = format(tooltip.format, that.selectionStart);
34933                            formattedSelectionEnd = format(tooltip.format, that.selectionEnd);
34934                            html = formattedSelectionStart + " - " + formattedSelectionEnd;
34935                        }
34936                    } else {
34937                        if (that.tooltipTemplate) {
34938                            html = tooltipTemplate({
34939                                value: that.val
34940                            });
34941                        } else {
34942                            html = format(tooltip.format, that.val);
34943                        }
34944
34945                        html += that.tooltipInnerDiv;
34946                    }
34947                    that.tooltipDiv.html(html);
34948                    that.moveTooltip();
34949                }
34950            }
34951        },
34952
34953        dragcancel: function(e) {
34954            this.owner._refresh();
34955            return this._end();
34956        },
34957
34958        dragend: function(e) {
34959            var that = this,
34960                owner = that.owner;
34961
34962            if (that.type) {
34963                owner._update(that.selectionStart, that.selectionEnd);
34964            } else {
34965                owner._update(that.val);
34966            }
34967
34968            return that._end();
34969        },
34970
34971        _end: function() {
34972            var that = this,
34973                owner = that.owner;
34974
34975            if (owner.options.tooltip.enabled && owner.options.enabled) {
34976                that.tooltipDiv.remove();
34977            }
34978
34979            that.dragHandle.removeClass(STATE_SELECTED);
34980            owner.element.bind(MOUSE_OVER);
34981
34982            return false;
34983        },
34984
34985        moveTooltip: function () {
34986            var that = this,
34987                owner = that.owner,
34988                positionTop = 0,
34989                positionLeft = 0,
34990                dragHandleOffset = that.dragHandle.offset(),
34991                margin = 4,
34992                callout = that.tooltipDiv.find(".k-callout"),
34993                dragHandles,
34994                firstDragHandleOffset,
34995                secondDragHandleOffset;
34996
34997            if (that.type) {
34998                dragHandles = owner.wrapper.find(DRAG_HANDLE);
34999                firstDragHandleOffset = dragHandles.eq(0).offset();
35000                secondDragHandleOffset = dragHandles.eq(1).offset();
35001
35002                if (owner._isHorizontal) {
35003                    positionTop = secondDragHandleOffset.top;
35004                    positionLeft = firstDragHandleOffset.left + ((secondDragHandleOffset.left - firstDragHandleOffset.left) / 2);
35005                } else {
35006                    positionTop = firstDragHandleOffset.top + ((secondDragHandleOffset.top - firstDragHandleOffset.top) / 2);
35007                    positionLeft = secondDragHandleOffset.left;
35008                }
35009            } else {
35010                positionTop = dragHandleOffset.top;
35011                positionLeft = dragHandleOffset.left;
35012            }
35013            if (owner._isHorizontal) {
35014                positionLeft -= parseInt((that.tooltipDiv.outerWidth() - that.dragHandle[owner._outerSize]()) / 2, 10);
35015                positionTop -= that.tooltipDiv.outerHeight() + callout.height() + margin;
35016            } else {
35017                positionTop -= parseInt((that.tooltipDiv.outerHeight() - that.dragHandle[owner._outerSize]()) / 2, 10);
35018                positionLeft -= that.tooltipDiv.outerWidth() + callout.width() + margin;
35019            }
35020
35021            that.tooltipDiv.css({ top: positionTop, left: positionLeft });
35022        },
35023
35024        constrainValue: function (position, min, max, maxOverflow) {
35025            var that = this,
35026                val = 0;
35027
35028            if (min < position && position < max) {
35029                val = that.owner._getValueFromPosition(position, that.dragableArea);
35030            } else
35031                if (maxOverflow) {
35032                    val = that.options.max;
35033                } else {
35034                    val = that.options.min;
35035                }
35036
35037            return val;
35038        }
35039
35040    };
35041
35042    kendo.ui.plugin(Slider);
35043
35044    var RangeSlider = SliderBase.extend(/** @lends kendo.ui.RangeSlider.prototype */{
35045        /**
35046         *
35047         * Creates a RangeSlider instance.
35048         *
35049         * @constructs
35050         * @extends kendo.ui.Widget
35051         *
35052         * @param {DomElement} element DOM element
35053         * @param {Object} options Configuration options.
35054         *
35055         * @option {Number} [min] <0>
35056         * The minimum value of the <strong>RangeSlider</strong>.
35057         *
35058         * @option {Number} [max] <10>
35059         * The maximum value of the <strong>RangeSlider</strong>.
35060         *
35061         * @option {Object} [tooltip]
35062         * Configuration of the <strong>RangeSlider</strong> tooltip.
35063         *
35064         * @option {String} [tooltip.format] <"{0}">
35065         * Format string for the text of the tooltip. Note: The applied format will also influence the appearance of
35066         * the <strong>RangeSlider</strong> tick labels.
35067         *
35068         * @option {Boolean} [tooltip.enabled] <true>
35069         * Disables (<b>false</b>) or enables (<b>true</b>) the tooltip of the <strong>RangeSlider</strong>.
35070         *
35071         * @option {String} [orientation] <"horizontal">
35072         * The orientation of a <strong>RangeSlider</strong>; <strong>"horizontal"</strong> or
35073         * <strong>"vertical"</strong>.
35074         *
35075         * @option {String} [tickPlacement] <"both">
35076         * Denotes the location of the tick marks in the <strong>RangeSlider</strong>. The available options are:
35077         * <div class="details-list">
35078         *    <dl>
35079         *         <dt>
35080         *              <code>"topLeft"</code>
35081         *         </dt>
35082         *         <dd>
35083         *              Tick marks are located on the top of the horizontal widget or on the left of
35084         *   the vertical widget.
35085         *         </dd>
35086         *         <dt>
35087         *              <code>"bottomRight"</code>
35088         *         </dt>
35089         *         <dd>
35090         *             Tick marks are located on the bottom of the horizontal widget or on the
35091         *   right side of the vertical widget.
35092         *         </dd>
35093         *         <dt>
35094         *              <code>"both"</code>
35095         *         </dt>
35096         *         <dd>
35097         *             Tick marks are located on both sides of the widget.
35098         *         </dd>
35099         *         <dt>
35100         *              <code>"none"</code>
35101         *         </dt>
35102         *         <dd>
35103         *             Tick marks are not visible.
35104         *         </dd>
35105         *    </dl>
35106         * </div>
35107         *
35108         * @option {Number} [smallStep] <1>
35109         * The small step value of the <strong>RangeSlider</strong>. The underlying value will be changed when the end
35110         * user (1) clicks on the increase or decrease buttons of the <strong>RangeSlider</strong>, (2) presses the
35111         * arrow keys (the drag handle must be focused), or (3) drags the drag handle.
35112         *
35113         * @option {Number} [largeStep] <5>
35114         * The delta with which the value will change when the user presses the Page Up or Page Down key (the drag
35115         * handle must be focused). Note: The allied largeStep will also set large tick for every large step.
35116         *
35117         * @option {Number} [selectionStart] <0>
35118         * The selection start value of the <strong>RangeSlider</strong>.
35119         *
35120         * @option {Number} [selectionEnd] <10>
35121         * The selection end value of the <strong>RangeSlider</strong>.
35122         *
35123         */
35124        init: function(element, options) {
35125            var that = this,
35126                inputs = $(element).find("input"),
35127                firstInput = inputs.eq(0)[0],
35128                secondInput = inputs.eq(1)[0];
35129
35130            firstInput.type = "text";
35131            secondInput.type = "text";
35132
35133            options = extend({}, {
35134                selectionStart: parseAttr(firstInput, "value"),
35135                min: parseAttr(firstInput, "min"),
35136                max: parseAttr(firstInput, "max"),
35137                smallStep: parseAttr(firstInput, "step")
35138            }, {
35139                selectionEnd: parseAttr(secondInput, "value"),
35140                min: parseAttr(secondInput, "min"),
35141                max: parseAttr(secondInput, "max"),
35142                smallStep: parseAttr(secondInput, "step")
35143            }, options);
35144
35145            if (options && options.enabled === undefined) {
35146                options.enabled = !inputs.is("[disabled]");
35147            }
35148
35149            SliderBase.fn.init.call(that, element, options);
35150            options = that.options;
35151            that._setValueInRange(options.selectionStart, options.selectionEnd);
35152
35153            var dragHandles = that.wrapper.find(DRAG_HANDLE);
35154
35155            new RangeSlider.Selection(dragHandles, that, options);
35156            that._firstHandleDrag = new Slider.Drag(dragHandles.eq(0), "firstHandle", that, options);
35157            that._lastHandleDrag = new Slider.Drag(dragHandles.eq(1), "lastHandle" , that, options);
35158        },
35159
35160        options: {
35161            name: "RangeSlider",
35162            selectionStart: 0,
35163            selectionEnd: 10,
35164            tooltip: { format: "{0}" }
35165        },
35166
35167        /**
35168         * Enable/Disable the <strong>RangeSlider</strong> widget.
35169         * @param {Boolean} enable
35170         * The argument, which defines whether to enable/disable the <strong>RangeSlider</strong>.
35171         * @example
35172         * // get a reference to the range slider widget
35173         * var rangeSlider = $("#rangeSlider").data("kendoRangeSlider");
35174         *
35175         * // disables the range slider
35176         * rangeSlider.enable(false);
35177         *
35178         * // enables the range slider
35179         * rangeSlider.enable(true);
35180         */
35181        enable: function (enable) {
35182            var that = this,
35183                options = that.options,
35184                clickHandler;
35185
35186            that.disable();
35187            if (enable === false) {
35188                return;
35189            }
35190
35191            that.wrapper
35192                .removeClass(STATE_DISABLED)
35193                .addClass(STATE_DEFAULT);
35194
35195            that.wrapper.find("input").removeAttr(DISABLED);
35196
35197            clickHandler = function (e) {
35198                var location = touchLocation(e),
35199                    mousePosition = that._isHorizontal ? location.x : location.y,
35200                    dragableArea = that._getDragableArea(),
35201                    val = that._getValueFromPosition(mousePosition, dragableArea),
35202                    target = $(e.target);
35203
35204                if (target.hasClass("k-draghandle")) {
35205                    target.addClass(STATE_SELECTED);
35206                    return;
35207                }
35208
35209                if (val < options.selectionStart) {
35210                    that._setValueInRange(val, options.selectionEnd);
35211                    that._firstHandleDrag.dragstart(e);
35212                } else if (val > that.selectionEnd) {
35213                    that._setValueInRange(options.selectionStart, val);
35214                    that._lastHandleDrag.dragstart(e);
35215                } else {
35216                    if (val - options.selectionStart <= options.selectionEnd - val) {
35217                        that._setValueInRange(val, options.selectionEnd);
35218                        that._firstHandleDrag.dragstart(e);
35219                    } else {
35220                        that._setValueInRange(options.selectionStart, val);
35221                        that._lastHandleDrag.dragstart(e);
35222                    }
35223                }
35224            };
35225
35226            that.wrapper
35227                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR)
35228                    .bind(TRACK_MOUSE_DOWN, clickHandler)
35229                    .end().bind(TRACK_MOUSE_DOWN, function() {
35230                        $(document.documentElement).one("selectstart", kendo.preventDefault);
35231                    });
35232
35233            that.wrapper
35234                .find(DRAG_HANDLE)
35235                .bind(MOUSE_UP, function (e) {
35236                    $(e.target).removeClass(STATE_SELECTED);
35237                })
35238                .bind(CLICK, function (e) {
35239                    e.preventDefault();
35240                });
35241
35242            that.wrapper.find(DRAG_HANDLE)
35243                .eq(0).bind(KEY_DOWN,
35244                    proxy(function(e) {
35245                        this._keydown(e, "firstHandle");
35246                    }, that)
35247                )
35248                .end()
35249                .eq(1).bind(KEY_DOWN,
35250                    proxy(function(e) {
35251                        this._keydown(e, "lastHandle");
35252                    }, that)
35253                );
35254
35255            that.options.enabled = true;
35256        },
35257
35258        disable: function () {
35259            var that = this;
35260
35261            that.wrapper
35262                .removeClass(STATE_DEFAULT)
35263                .addClass(STATE_DISABLED);
35264
35265            that.wrapper.find("input").attr(DISABLED, DISABLED);
35266
35267            that.wrapper
35268                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).unbind(TRACK_MOUSE_DOWN);
35269
35270            that.wrapper
35271                .find(DRAG_HANDLE)
35272                .unbind(MOUSE_UP)
35273                .unbind(KEY_DOWN)
35274                .unbind(CLICK)
35275                .bind(KEY_DOWN, false);
35276
35277            that.options.enabled = false;
35278        },
35279
35280        _keydown: function (e, handle) {
35281            var that = this,
35282                selectionStartValue = that.options.selectionStart,
35283                selectionEndValue = that.options.selectionEnd;
35284
35285            if (e.keyCode in that._keyMap) {
35286                if (handle == "firstHandle") {
35287                    selectionStartValue = that._keyMap[e.keyCode](selectionStartValue);
35288
35289                    if (selectionStartValue > selectionEndValue) {
35290                        selectionEndValue = selectionStartValue;
35291                    }
35292                } else {
35293                    selectionEndValue = that._keyMap[e.keyCode](selectionEndValue);
35294
35295                    if (selectionStartValue > selectionEndValue) {
35296                        selectionStartValue = selectionEndValue;
35297                    }
35298                }
35299
35300                that._setValueInRange(selectionStartValue, selectionEndValue);
35301                e.preventDefault();
35302            }
35303        },
35304
35305        _update: function (selectionStart, selectionEnd) {
35306            var that = this,
35307                values = that.value();
35308
35309            var change = values[0] != selectionStart || values[1] != selectionEnd;
35310
35311            that.value([selectionStart, selectionEnd]);
35312
35313            if (change) {
35314                that.trigger(CHANGE, {
35315                    values: [selectionStart, selectionEnd],
35316                    value: [selectionStart, selectionEnd]
35317                });
35318            }
35319        },
35320
35321        /**
35322         *
35323         * The value method gets or sets the start and end values of the <strong>RangeSlider</strong>. It
35324         * accepts an array as parameter, and returns an object array with the start and end
35325         * selection values.
35326         *
35327         * @example
35328         * var rangeSider = $("#rangeSlider").data("kendoRangeSlider");
35329         * rangeSlider.value();
35330         *
35331         */
35332        value: function(value) {
35333            if (value && value.length) {
35334                return this._value(value[0], value[1]);
35335            } else {
35336                return this._value();
35337            }
35338        },
35339
35340        _value: function(start, end) {
35341            var that = this,
35342                options = that.options,
35343                selectionStart = options.selectionStart,
35344                selectionEnd = options.selectionEnd;
35345
35346            if (isNaN(start) && isNaN(end)) {
35347                return [selectionStart, selectionEnd];
35348            } else {
35349                start = round(start);
35350                end = round(end);
35351            }
35352
35353            if (start >= options.min && start <= options.max &&
35354                end >= options.min && end <= options.max && start <= end) {
35355                if (selectionStart != start || selectionEnd != end) {
35356                    that.element.find("input")
35357                        .eq(0).attr("value", formatValue(start))
35358                        .end()
35359                        .eq(1).attr("value", formatValue(end));
35360
35361                    options.selectionStart = start;
35362                    options.selectionEnd = end;
35363                    that._refresh();
35364                }
35365            }
35366        },
35367
35368        values: function (start, end) {
35369            if (isArray(start)) {
35370                return this._value(start[0], start[1]);
35371            } else {
35372                return this._value(start, end);
35373            }
35374        },
35375
35376        _refresh: function() {
35377            var that = this,
35378                options = that.options;
35379
35380            that.trigger(MOVE_SELECTION, {
35381                values: [options.selectionStart, options.selectionEnd],
35382                value: [options.selectionStart, options.selectionEnd]
35383            });
35384
35385            if (options.selectionStart == options.max && options.selectionEnd == options.max) {
35386                that._setZIndex("firstHandle");
35387            }
35388        },
35389
35390        _setValueInRange: function (selectionStart, selectionEnd) {
35391            var options = this.options;
35392
35393            selectionStart = math.max(math.min(selectionStart, options.max), options.min);
35394
35395            selectionEnd = math.max(math.min(selectionEnd, options.max), options.min);
35396
35397            if (selectionStart == options.max && selectionEnd == options.max) {
35398                this._setZIndex("firstHandle");
35399            }
35400
35401            this._update(math.min(selectionStart, selectionEnd), math.max(selectionStart, selectionEnd));
35402        },
35403
35404        _setZIndex: function (type) {
35405            this.wrapper.find(DRAG_HANDLE).each(function (index) {
35406                $(this).css("z-index", type == "firstHandle" ? 1 - index : index);
35407            });
35408        }
35409    });
35410
35411    RangeSlider.Selection = function (dragHandles, that, options) {
35412        function moveSelection(value) {
35413            value = value || [];
35414            var selectionStartValue = value[0] - options.min,
35415                selectionEndValue = value[1] - options.min,
35416                selectionStartIndex = math.ceil(round(selectionStartValue / options.smallStep)),
35417                selectionEndIndex = math.ceil(round(selectionEndValue / options.smallStep)),
35418                selectionStart = that._pixelSteps[selectionStartIndex],
35419                selectionEnd = that._pixelSteps[selectionEndIndex],
35420                halfHandle = parseInt(dragHandles.eq(0)[that._outerSize]() / 2, 10);
35421
35422            dragHandles.eq(0).css(that._position, selectionStart - halfHandle)
35423                       .end()
35424                       .eq(1).css(that._position, selectionEnd - halfHandle);
35425
35426            makeSelection(selectionStart, selectionEnd);
35427        }
35428
35429        function makeSelection(selectionStart, selectionEnd) {
35430            var selection = 0,
35431                selectionPosition = 0,
35432                selectionDiv = that._trackDiv.find(".k-slider-selection");
35433
35434            selection = math.abs(selectionStart - selectionEnd);
35435            selectionPosition = selectionStart < selectionEnd ? selectionStart : selectionEnd;
35436
35437            selectionDiv[that._size](selection);
35438            selectionDiv.css(that._position, selectionPosition - 1);
35439        }
35440
35441        moveSelection(that.value());
35442
35443        that.bind([ CHANGE, SLIDE, MOVE_SELECTION ], function (e) {
35444            moveSelection(e.values);
35445        });
35446    };
35447
35448    kendo.ui.plugin(RangeSlider);
35449
35450})(jQuery);
35451/**
35452 * @fileOverview Provides a Splitter implementation which can be used to display a dynamic layout of resizable and
35453 * collapsible panes.
35454 */
35455
35456(function ($, undefined) {
35457    /**
35458     * @name kendo.ui.Splitter.Description
35459     *
35460     * @section
35461     * <p>
35462     *  The <strong>Splitter</strong> provides a dynamic layout of resizable and collapsible panes. It converts the
35463     *  children of an HTML element in to the interactive layout, adding resize and collapse handles based on
35464     *  configuration. A <strong>Splitter</strong> can be mixed in a vertical or horizontal orientation to build
35465     *  complex layouts.
35466     * </p>
35467     * <h3>Getting Started</h3>
35468     * <p>
35469     *  The layout and structure of a <strong>Splitter</strong> is defined within the DOM as a div with child elements.
35470     * </p>
35471     *
35472     * @exampleTitle Create a div with children that will become panes
35473     * @example
35474     * <div id="splitter">
35475     *     <div>Area 1</div>
35476     *     <div>Area 2</div>
35477     * </div>
35478     *
35479     * @section
35480     * <p>
35481     *  Initialization of a <strong>Splitter</strong> should occur after the DOM is fully loaded. It is recommended
35482     *  that initialization the <strong>Splitter</strong> occur within a handler is provided to $(document).ready().
35483     * </p>
35484     *
35485     * @exampleTitle Initialize the Splitter using a selector within $(document).ready()
35486     * @example
35487     * $(document).ready(function() {
35488     *     $("#splitter").kendoSplitter();
35489     * });
35490     *
35491     * @section
35492     * <p>
35493     *  When the <strong>Splitter</strong> is initialized, a vertical split bar will be placed between the two div
35494     *  elements. This bar can be moved by a user left and right to adjust the size on the panes.
35495     * </p>
35496     * <h3>Configuring Splitter Behaviors</h3>
35497     * <p>
35498     *  The <strong>Splitter</strong> has a default configuration specified during initialization. However, these
35499     *  options may be overriden to control the following properties:
35500     * </p>
35501     * <ul>
35502     *  <li>Maximum and/or minimum pane sizes</li>
35503     *  <li>Resizable and collapsible/expandable pane behaviors</li>
35504     *  <li>Orientation (horizontal or vertical)</li>
35505     * </ul>
35506     * <p>
35507     *  The properties of a pane must be set during initialization and set for each individual pane in a
35508     *  <strong>Splitter</strong>.
35509     * </p>
35510     *
35511     * @exampleTitle Initialize a Splitter and the properties of its panes
35512     * @example
35513     * $("#splitter").kendoSplitter({
35514     *     panes: [
35515     *         { collapsible: true, min: "100px", max: "300px" },
35516     *         { collapsible: true }
35517     *     ],
35518     *     orientation: "vertical"
35519     * });
35520     *
35521     * @section
35522     * <h3>Nested Splitter Layouts</h3>
35523     * <p>To achieve complex layouts, the <strong>Splitter</strong> supports nested layouts.</p>
35524     *
35525     * @exampleTitle Creating nested Splitter layout
35526     * @example
35527     * <div id="horizontalSplitter">
35528     *     <div><p>Left Side Pane Content</p></div>
35529     *     <div>
35530     *         <div id="verticalSplitter">
35531     *             <div><p>Right Side, Top Pane Content</p></div>
35532     *             <div><p>Right Side, Bottom Pane Content</p></div>
35533     *         </div>
35534     *     </div>
35535     * </div>
35536     *
35537     * @exampleTitle Initialize two Splitters with differing orientations
35538     * @example
35539     * $("horizontalSplitter").kendoSplitter();
35540     * $("verticalSplitter").kendoSplitter({ orientation: "vertical" });
35541     *
35542     * @section
35543     * <h3>Loading Content with AJAX</h3>
35544     * <p>
35545     *  While any valid technique for loading content via AJAX may be used, <strong>Splitter</strong> provides built-in
35546     *  support for asynchronously loading content from URLs. These URLs should return HTML fragments that can be
35547     *  loaded in the pane of a <strong>Splitter</strong>. If you want to load a whole page in an IFRAME, you may do so
35548     *  by specifying the complete URL (i.e. http://kendoui.com/).
35549     * </p>
35550     *
35551     * @exampleTitle Loading Splitter content asynchronously
35552     * @example
35553     * <div id="splitter">
35554     *     <div>Area 1 with Static Content</div>
35555     *     <div></div>
35556     *     <div></div>
35557     * </div>
35558     *
35559     * @exampleTitle Initialize Splitter; configure async loading for one pane; and an iframe for a third pane
35560     * @example
35561     * $(document).ready(function() {
35562     *     $("#splitter").kendoSplitter({
35563     *         panes: [
35564     *             {},
35565     *             { contentUrl: "html-content-snippet.html" },
35566     *             { contentUrl: "http://kendoui.com/" }
35567     *         ]
35568     *     });
35569     * });
35570     *
35571     * @section
35572     * <h3>Accessing an Existing Splitter</h3>
35573     * <p>
35574     *  You can reference an existing <strong>Splitter</strong> instance via
35575     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
35576     *  use the API to control its behavior.
35577     * </p>
35578     *
35579     * @exampleTitle Accessing an existing Splitter instance
35580     * @example
35581     * var splitter = $("#splitter").data("kendoSplitter");
35582     *
35583     */
35584    var kendo = window.kendo,
35585        ui = kendo.ui,
35586        extend = $.extend,
35587        proxy = $.proxy,
35588        Widget = ui.Widget,
35589        pxUnitsRegex = /^\d+(\.\d+)?px$/i,
35590        percentageUnitsRegex = /^\d+(\.\d+)?%$/i,
35591        EXPAND = "expand",
35592        COLLAPSE = "collapse",
35593        CONTENTLOAD = "contentLoad",
35594        RESIZE = "resize",
35595        LAYOUTCHANGE = "layoutChange",
35596        HORIZONTAL = "horizontal",
35597        VERTICAL = "vertical",
35598        MOUSEENTER = "mouseenter",
35599        CLICK = "click",
35600        PANE = "pane",
35601        MOUSELEAVE = "mouseleave",
35602        KPANE = "k-" + PANE,
35603        PANECLASS = "." + KPANE;
35604
35605    function isPercentageSize(size) {
35606        return percentageUnitsRegex.test(size);
35607    }
35608
35609    function isPixelSize(size) {
35610        return pxUnitsRegex.test(size);
35611    }
35612
35613    function isFluid(size) {
35614        return !isPercentageSize(size) && !isPixelSize(size);
35615    }
35616
35617    function panePropertyAccessor(propertyName, triggersResize) {
35618        return function(pane, value) {
35619            var paneConfig = $(pane).data(PANE);
35620
35621            if (arguments.length == 1) {
35622                return paneConfig[propertyName];
35623            }
35624
35625            paneConfig[propertyName] = value;
35626
35627            if (triggersResize) {
35628                var splitter = this.element.data("kendoSplitter");
35629                splitter.trigger(RESIZE);
35630            }
35631        };
35632    }
35633
35634    var Splitter = Widget.extend(/** @lends kendo.ui.Splitter.prototype */ {
35635        /**
35636         * Creates a Splitter instance.
35637         *
35638         * @constructs
35639         * @extends kendo.ui.Widget
35640         *
35641         * @param {DomElement} element DOM element
35642         * @param {Object} options Configuration options.
35643         *
35644         * @option {String} [orientation] <horizontal>
35645         * Specifies the orientation of the <strong>Splitter</strong>.
35646         * <div class="details-list">
35647         *  <dl>
35648         *   <dt>"horizontal"</dt>
35649         *   <dd>Define horizontal orientation of the splitter.</dd>
35650         *   <dt>"vertical"</dt>
35651         *   <dd>Define vertical orientation of the splitter.</dd>
35652         *  </dl>
35653         * </div>
35654         *
35655         * @option {Array} [panes]
35656         * An array of pane definitions.
35657         *
35658         * _example
35659         * $("#splitter").kendoSplitter({
35660         *     panes: [
35661         *         { size: "200px", min: "100px", max: "300px" },
35662         *         { size: "20%", resizable: false },
35663         *         { collapsed: true, collapsible: true }
35664         *     ]
35665         * });
35666         *
35667         * @option {String} [panes.size]
35668         * Specifies the size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). Note: This
35669         * value must not exceed <strong>panes.max</strong> or be less then <strong>panes.min</strong>.
35670         *
35671         * @option {String} [panes.min]
35672         * Specifies the minimum size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). The
35673         * size of a resized pane cannot be less than the defined minimum size.
35674         *
35675         * @option {String} [panes.max]
35676         * Specifies the maximum size of a pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). The
35677         * size of a resized pane cannot exceed the defined maximum size.
35678         *
35679         * @option {Boolean} [panes.collapsed] <false>
35680         * Specifies whether a pane is initially collapsed (<strong>true</strong>) or expanded (<strong>true</strong>).
35681         *
35682         * @option {Boolean} [panes.collapsible] <false>
35683         * Specifies whether a pane is collapsible (<strong>true</strong>) or not collapsible (<strong>false</strong>).
35684         *
35685         * @option {Boolean} [panes.scrollable] <true>
35686         * Specifies whether a pane is scrollable (<strong>true</strong>) or not scrollable (<strong>false</strong>).
35687         *
35688         * @option {Boolean} [panes.resizable] <true>
35689         * Specifies whether a pane is resizable (<strong>true</strong>) or not resizable (<strong>false</strong>).
35690         *
35691         * @option {Boolean} [panes.contentUrl] <true>
35692         * Specifies the URL from which to load the content of a pane.
35693         *
35694         */
35695        init: function(element, options) {
35696            var that = this,
35697                triggerResize = function() {
35698                    that.trigger(RESIZE);
35699                };
35700
35701            Widget.fn.init.call(that, element, options);
35702
35703            that.wrapper = that.element;
35704
35705            that.orientation = that.options.orientation.toLowerCase() != VERTICAL ? HORIZONTAL : VERTICAL;
35706
35707            that.bind(RESIZE, proxy(that._resize, that));
35708
35709            that._initPanes();
35710
35711            that._attachEvents();
35712
35713            $(window).resize(triggerResize);
35714
35715            that.resizing = new PaneResizing(that);
35716        },
35717        events: [
35718            /**
35719            * Triggered when a pane of a Splitter is expanded.
35720            *
35721            * @name kendo.ui.Splitter#expand
35722            * @event
35723            *
35724            * @param {Event} e
35725            *
35726            * @param {Element} e.pane
35727            * The expanding pane of the Splitter.
35728            *
35729            * @exampleTitle Attach expand event handler during initialization; detach via unbind()
35730            * @example
35731            * // event handler for expand
35732            * var onExpand = function(e) {
35733            *     // access the expanded item via e.pane (HTMLElement)
35734            * };
35735            *
35736            * // attach expand event handler during initialization
35737            * var splitter = $("#splitter").kendoSplitter({
35738            *     expand: onExpand
35739            * });
35740            *
35741            * // detach expand event handler via unbind()
35742            * splitter.data("kendoSplitter").unbind("expand", onExpand);
35743            *
35744            * @exampleTitle Attach expand event handler via bind(); detach via unbind()
35745            * @example
35746            * // event handler for expand
35747            * var onExpand = function(e) {
35748            *     // access the expanded item via e.pane (HTMLElement)
35749            * };
35750            *
35751            * // attach expand event handler via bind()
35752            * $("#splitter").data("kendoSplitter").bind("expand", onExpand);
35753            *
35754            * // detach expand event handler via unbind()
35755            * $("#splitter").data("kendoSplitter").unbind("expand", onExpand);
35756            *
35757            */
35758            EXPAND,
35759
35760            /**
35761            * Triggered when a pane of a Splitter is collapsed.
35762            *
35763            * @name kendo.ui.Splitter#collapse
35764            * @event
35765            *
35766            * @param {Event} e
35767            * @param {Element} e.pane
35768            * The collapsing pane of the Splitter.
35769            *
35770            * @exampleTitle Attach expand event handler during initialization; detach via unbind()
35771            * @example
35772            * // event handler for expand
35773            * var onCollapse = function(e) {
35774            *     // access the collapsed item via e.pane (HTMLElement)
35775            * };
35776            *
35777            * // attach collapse event handler during initialization
35778            * var splitter = $("#splitter").kendoSplitter({
35779            *     collapse: onCollapse
35780            * });
35781            *
35782            * // detach collapse event handler via unbind()
35783            * splitter.data("kendoSplitter").unbind("collapse", onCollapse);
35784            *
35785            * @exampleTitle Attach collapse event handler via bind(); detach via unbind()
35786            * @example
35787            * // event handler for collapse
35788            * var onExpand = function(e) {
35789            *     // access the collapsed item via e.pane (HTMLElement)
35790            * };
35791            *
35792            * // attach collapse event handler via bind()
35793            * $("#splitter").data("kendoSplitter").bind("collapse", onCollapse);
35794            *
35795            * // detach collapse event handler via unbind()
35796            * $("#splitter").data("kendoSplitter").unbind("collapse", onCollapse);
35797            *
35798            */
35799            COLLAPSE,
35800
35801            /**
35802            * Triggered when the content for a pane has finished loading.
35803            *
35804            * @name kendo.ui.Splitter#contentLoad
35805            * @event
35806            *
35807            * @param {Event} e
35808            *
35809            * @param {HTMLElement} e.pane
35810            * The pane whose content has been loaded.
35811            *
35812            * @exampleTitle Attach contentLoad event handler during initialization; detach via unbind()
35813            * @example
35814            * // event handler for contentLoad
35815            * var onContentLoad = function(e) {
35816            *     // access the loaded pane via e.pane (HTMLElement)
35817            * };
35818            *
35819            * // attach contentLoad event handler during initialization
35820            * var splitter = $("#splitter").kendoSplitter({
35821            *     contentLoad: onContentLoad
35822            * });
35823            *
35824            * // detach contentLoad event handler via unbind()
35825            * splitter.data("kendoSplitter").unbind("contentLoad", onContentLoad);
35826            *
35827            * @exampleTitle Attach contentLoad event handler via bind(); detach via unbind()
35828            * @example
35829            * // event handler for contentLoad
35830            * var onContentLoad = function(e) {
35831            *     // access the loaded pane via e.pane (HTMLElement)
35832            * };
35833            *
35834            * // attach contentLoad event handler via bind()
35835            * $("#splitter").data("kendoSplitter").bind("contentLoad", onContentLoad);
35836            *
35837            * // detach contentLoad event handler via unbind()
35838            * $("#splitter").data("kendoSplitter").unbind("contentLoad", onContentLoad);
35839            *
35840            */
35841            CONTENTLOAD,
35842
35843            /**
35844            * Triggered when a pane is resized.
35845            *
35846            * @name kendo.ui.Splitter#resize
35847            * @event
35848            * @param {Event} e
35849            *
35850            * @exampleTitle Attach resize event handler during initialization; detach via unbind()
35851            * @example
35852            * // event handler for resize
35853            * var onResize = function(e) {
35854            *     // ...
35855            * };
35856            *
35857            * // attach resize event handler during initialization
35858            * var splitter = $("#splitter").kendoSplitter({
35859            *     resize: onResize
35860            * });
35861            *
35862            * // detach resize event handler via unbind()
35863            * splitter.data("kendoSplitter").unbind("resize", onResize);
35864
35865            * @exampleTitle Attach resize event handler via bind(); detach via unbind()
35866            * @example
35867            * // event handler for resize
35868            * var onResize = function(e) {
35869            *     // ...
35870            * };
35871            *
35872            * // attach resize event handler via bind()
35873            * $("#splitter").data("kendoSplitter").bind("resize", onResize);
35874            *
35875            * // detach resize event handler via unbind()
35876            * $("#splitter").data("kendoSplitter").unbind("resize", onResize);
35877            *
35878            */
35879            RESIZE,
35880
35881            /**
35882            * Fires when the splitter layout has changed
35883            *
35884            * @name kendo.ui.Splitter#layoutChange
35885            * @event
35886            *
35887            * @exampleTitle Attach layoutChange event handler during initialization; detach via unbind()
35888            * @example
35889            * // event handler for resize
35890            * var onLayoutChange = function(e) {
35891            *     // ...
35892            * };
35893            *
35894            * $("#splitter").kendoSplitter({
35895            *     layoutChange: onLayoutChange
35896            * });
35897            *
35898            * @exampleTitle Attach layoutChange event handler via bind(); detach via unbind()
35899            * @example
35900            * // event handler for layoutChange
35901            * var onLayoutChange = function(e) {
35902            *     // ...
35903            * };
35904            *
35905            * // attach layoutChange event handler via bind()
35906            * $("#splitter").bind("layoutChange", onLayoutChange);
35907            *
35908            * // detach layoutChange event handler via unbind()
35909            * $("#splitter").unbind("layoutChange", onLayoutChange);
35910            *
35911            */
35912            LAYOUTCHANGE
35913        ],
35914
35915        _attachEvents: function() {
35916            var that = this,
35917                orientation = that.options.orientation,
35918                splitbarSelector = ".k-splitbar-draggable-" + orientation,
35919                expandCollapseSelector = ".k-splitbar .k-icon:not(.k-resize-handle)",
35920                triggerResize = function() {
35921                    that.trigger(RESIZE);
35922                };
35923
35924            that.element
35925                .delegate(splitbarSelector, MOUSEENTER, function() { $(this).addClass("k-splitbar-" + that.orientation + "-hover"); })
35926                .delegate(splitbarSelector, MOUSELEAVE, function() { $(this).removeClass("k-splitbar-" + that.orientation + "-hover"); })
35927                .delegate(splitbarSelector, "mousedown", function() { that._contentFrames(this).after("<div class='k-overlay' />"); })
35928                .delegate(splitbarSelector, "mouseup", function() { that._contentFrames(this).next(".k-overlay").remove(); })
35929                .delegate(expandCollapseSelector, MOUSEENTER, function() { $(this).addClass("k-state-hover"); })
35930                .delegate(expandCollapseSelector, MOUSELEAVE, function() { $(this).removeClass('k-state-hover'); })
35931                .delegate(".k-splitbar .k-collapse-next, .k-splitbar .k-collapse-prev", CLICK, that._arrowClick(COLLAPSE))
35932                .delegate(".k-splitbar .k-expand-next, .k-splitbar .k-expand-prev", CLICK, that._arrowClick(EXPAND))
35933                .delegate(".k-splitbar", "dblclick", proxy(that._dbclick, that))
35934                .parent().closest(".k-splitter").each(function() {
35935                    $(this).data("kendoSplitter").bind(RESIZE,  triggerResize);
35936                });
35937        },
35938
35939        options: {
35940            name: "Splitter",
35941            orientation: HORIZONTAL
35942        },
35943
35944        _initPanes: function() {
35945            var that = this,
35946                panesConfig = that.options.panes || [];
35947
35948            that.element
35949                .addClass("k-widget").addClass("k-splitter")
35950                .children()
35951                    .addClass(KPANE)
35952                    .each(function (index, pane) {
35953                        var config = panesConfig && panesConfig[index];
35954
35955                        pane = $(pane);
35956
35957                        pane.data(PANE, config ? config : {})
35958                            .toggleClass("k-scrollable", config ? config.scrollable !== false : true);
35959                        that.ajaxRequest(pane);
35960                    })
35961                .end();
35962            that.trigger(RESIZE);
35963        },
35964
35965        /**
35966         * Loads the content of a pane from a local or remote URL.
35967         *
35968         * @param {Selector | DOM Element} pane
35969         * The targetted pane whose content is to be loaded via a URL.
35970         *
35971         * @param {String} url
35972         * A local or remote URL from which the content of the pane is to be loaded.
35973         *
35974         * @param {Object | String} data
35975         * Any data that is necessary to be sent to the server.
35976         *
35977         * @example
35978         * // get a reference to the splitter
35979         * var splitter = $("#splitter").data("kendoSplitter");
35980         * // load content into the pane with ID, pane1
35981         * splitter.ajaxRequest("#pane1", "/customer/profile", { id: 42 });
35982         *
35983         */
35984        ajaxRequest: function(pane, url, data) {
35985            pane = $(pane);
35986
35987            var that = this,
35988                paneConfig = pane.data(PANE);
35989
35990            url = url || paneConfig.contentUrl;
35991
35992            if (url) {
35993                pane.append("<span class='k-icon k-loading k-pane-loading' />");
35994
35995                if (kendo.isLocalUrl(url)) {
35996                    $.ajax({
35997                        url: url,
35998                        data: data || {},
35999                        type: "GET",
36000                        dataType: "html",
36001                        success: function (data) {
36002                            pane.html(data);
36003
36004                            that.trigger(CONTENTLOAD, { pane: pane[0] });
36005                        }
36006                    });
36007                } else {
36008                    pane.removeClass("k-scrollable")
36009                        .html("<iframe src='" + url + "' frameborder='0' class='k-content-frame'>" +
36010                                "This page requires frames in order to show content" +
36011                              "</iframe>");
36012                }
36013            }
36014        },
36015        _triggerAction: function(type, pane) {
36016            if (!this.trigger(type, { pane: pane[0] })) {
36017                this[type](pane[0]);
36018            }
36019        },
36020        _dbclick: function(e) {
36021            var that = this,
36022                target = $(e.target),
36023                arrow;
36024
36025            if (target.closest(".k-splitter")[0] != that.element[0]) {
36026                return;
36027            }
36028
36029            arrow = target.children(".k-icon:not(.k-resize-handle)");
36030
36031            if (arrow.length !== 1) {
36032                return;
36033            }
36034
36035            if (arrow.is(".k-collapse-prev")) {
36036                that._triggerAction(COLLAPSE, target.prev());
36037            } else if (arrow.is(".k-collapse-next")) {
36038                that._triggerAction(COLLAPSE, target.next());
36039            } else if (arrow.is(".k-expand-prev")) {
36040                that._triggerAction(EXPAND, target.prev());
36041            } else if (arrow.is(".k-expand-next")) {
36042                that._triggerAction(EXPAND, target.next());
36043            }
36044        },
36045        _arrowClick: function (arrowType) {
36046            var that = this;
36047
36048            return function(e) {
36049                var target = $(e.target),
36050                    pane;
36051
36052                if (target.closest(".k-splitter")[0] != that.element[0]) {
36053                    return;
36054                }
36055
36056                if (target.is(".k-" + arrowType + "-prev")) {
36057                    pane = target.parent().prev();
36058                } else {
36059                    pane = target.parent().next();
36060                }
36061                that._triggerAction(arrowType, pane);
36062            };
36063        },
36064        _updateSplitBar: function(splitbar, previousPane, nextPane) {
36065            var catIconIf = function(iconType, condition) {
36066                   return condition ? "<div class='k-icon " + iconType + "' />" : "";
36067                },
36068                orientation = this.orientation,
36069                draggable = (previousPane.resizable !== false) && (nextPane.resizable !== false),
36070                prevCollapsible = previousPane.collapsible,
36071                prevCollapsed = previousPane.collapsed,
36072                nextCollapsible = nextPane.collapsible,
36073                nextCollapsed = nextPane.collapsed;
36074
36075            splitbar.addClass("k-splitbar k-state-default k-splitbar-" + orientation)
36076                .removeClass("k-splitbar-" + orientation + "-hover")
36077                .toggleClass("k-splitbar-draggable-" + orientation,
36078                    draggable && !prevCollapsed && !nextCollapsed)
36079                .toggleClass("k-splitbar-static-" + orientation,
36080                    !draggable && !prevCollapsible && !nextCollapsible)
36081                .html(
36082                    catIconIf("k-collapse-prev", prevCollapsible && !prevCollapsed && !nextCollapsed) +
36083                    catIconIf("k-expand-prev", prevCollapsible && prevCollapsed && !nextCollapsed) +
36084                    catIconIf("k-resize-handle", draggable) +
36085                    catIconIf("k-collapse-next", nextCollapsible && !nextCollapsed && !prevCollapsed) +
36086                    catIconIf("k-expand-next", nextCollapsible && nextCollapsed && !prevCollapsed)
36087                );
36088        },
36089        _updateSplitBars: function() {
36090            var that = this;
36091
36092            this.element.children(".k-splitbar").each(function() {
36093                var splitbar = $(this),
36094                    previousPane = splitbar.prev(PANECLASS).data(PANE),
36095                    nextPane = splitbar.next(PANECLASS).data(PANE);
36096
36097                if (!nextPane) {
36098                    return;
36099                }
36100
36101                that._updateSplitBar(splitbar, previousPane, nextPane);
36102            });
36103        },
36104        _contentFrames: function(splitbar) {
36105            return $(splitbar).siblings(PANECLASS).find("> .k-content-frame");
36106        },
36107        _resize: function() {
36108            var that = this,
36109                element = that.element,
36110                panes = element.children(":not(.k-splitbar)"),
36111                isHorizontal = that.orientation == HORIZONTAL,
36112                splitBars = element.children(".k-splitbar"),
36113                splitBarsCount = splitBars.length,
36114                sizingProperty = isHorizontal ? "width" : "height",
36115                totalSize = element[sizingProperty]();
36116
36117            if (splitBarsCount === 0) {
36118                splitBarsCount = panes.length - 1;
36119                panes.slice(0, splitBarsCount).after("<div class='k-splitbar' />");
36120                that._updateSplitBars();
36121                splitBars = element.children(".k-splitbar");
36122            } else {
36123                that._updateSplitBars();
36124            }
36125
36126            // discard splitbar sizes from total size
36127            splitBars.each(function() {
36128                totalSize -= this[isHorizontal ? "offsetWidth" : "offsetHeight"];
36129            });
36130
36131            var sizedPanesWidth = 0,
36132                sizedPanesCount = 0,
36133                freeSizedPanes = $();
36134
36135            panes.css({ position: "absolute", top: 0 })
36136                [sizingProperty](function() {
36137                    var config = $(this).data(PANE) || {}, size;
36138
36139                    if (config.collapsed) {
36140                        size = 0;
36141                    } else if (isFluid(config.size)) {
36142                        freeSizedPanes = freeSizedPanes.add(this);
36143                        return;
36144                    } else { // sized in px/%, not collapsed
36145                        size = parseInt(config.size, 10);
36146
36147                        if (isPercentageSize(config.size)) {
36148                            size = Math.floor(size * totalSize / 100);
36149                        }
36150                    }
36151
36152                    sizedPanesCount++;
36153                    sizedPanesWidth += size;
36154
36155                    return size;
36156                });
36157
36158            totalSize -= sizedPanesWidth;
36159
36160            var freeSizePanesCount = freeSizedPanes.length,
36161                freeSizePaneWidth = Math.floor(totalSize / freeSizePanesCount);
36162
36163            freeSizedPanes
36164                .slice(0, freeSizePanesCount - 1)
36165                    .css(sizingProperty, freeSizePaneWidth)
36166                .end()
36167                .eq(freeSizePanesCount - 1)
36168                    .css(sizingProperty, totalSize - (freeSizePanesCount - 1) * freeSizePaneWidth);
36169
36170            // arrange panes
36171            var sum = 0,
36172                alternateSizingProperty = isHorizontal ? "height" : "width",
36173                positioningProperty = isHorizontal ? "left" : "top",
36174                sizingDomProperty = isHorizontal ? "offsetWidth" : "offsetHeight";
36175
36176            if (freeSizePanesCount === 0) {
36177                var lastNonCollapsedPane = panes.filter(function() {
36178                    return !(($(this).data(PANE) || {}).collapsed);
36179                }).last();
36180
36181                lastNonCollapsedPane[sizingProperty](totalSize + lastNonCollapsedPane[0][sizingDomProperty]);
36182            }
36183
36184            element.children()
36185                .css(alternateSizingProperty, element[alternateSizingProperty]())
36186                .each(function (i, child) {
36187                    child.style[positioningProperty] = Math.floor(sum) + "px";
36188                    sum += child[sizingDomProperty];
36189                });
36190
36191            that.trigger(LAYOUTCHANGE);
36192        },
36193
36194        /**
36195         * Toggles the state of a specified pane (i.e. collapsed or expanded). Invoking this method will force the
36196         * <strong>Splitter</strong> to redraw and it will trigger layoutChange and resize events. Note: Invoking the
36197         * method will not trigger collapse or expand events.
36198         *
36199         * @param {Selector | DOM Element} pane
36200         * The pane to be collapsed.
36201         *
36202         * @param {Boolean} expand (Optional)
36203         * Represents the desired state of the specified pane; to be expanded (<strong>true</strong>) or collapsed
36204         * (<strong>false</strong>). If undefined, toggle() will collapse the pane if it is expanded or will expand the
36205         * pane if it is collapsed.
36206         *
36207         * @example
36208         * // get a reference to the splitter
36209         * var splitter = $("#splitter").data("kendoSplitter");
36210         * // toggle the state of the pane with ID, pane1
36211         * splitter.toggle("#pane1");
36212         * // toggle the state of the pane with ID, pane1 to be expanded
36213         * splitter.toggle("#pane1", true);
36214         * // toggle the state of the pane with ID, pane1 to be collapsed
36215         * splitter.toggle("#pane1", false);
36216         *
36217         */
36218        toggle: function(pane, expand) {
36219            var paneConfig;
36220
36221            pane = $(pane);
36222            paneConfig = pane.data(PANE);
36223
36224            if (arguments.length == 1) {
36225                expand = paneConfig.collapsed === undefined ? false : paneConfig.collapsed;
36226            }
36227
36228            paneConfig.collapsed = !expand;
36229
36230            this.trigger(RESIZE);
36231        },
36232
36233        /**
36234         * Collapses a specified pane. Invoking this method will force the <strong>Splitter</strong> to redraw and it
36235         * will trigger layoutChange and resize events. Note: Invoking the method will not trigger a collapse event.
36236         *
36237         * @param {Selector | DOM Element} pane
36238         * The pane to be collapsed.
36239         *
36240         * @example
36241         * // get a reference to the splitter
36242         * var splitter = $("#splitter").data("kendoSplitter");
36243         * // collapse the pane with ID, pane1
36244         * splitter.collapse("#pane1");
36245         *
36246         */
36247        collapse: function(pane) {
36248            this.toggle(pane, false);
36249        },
36250
36251        /**
36252         * Expands a specified pane. Invoking this method will force the <strong>Splitter</strong> to redraw and it
36253         * will trigger layoutChange and resize events. Note: Invoking the method will not trigger an expand event.
36254         *
36255         * @param {Selector | DOM Element} pane
36256         * The pane to be expanded.
36257         *
36258         * @example
36259         * // get a reference to the splitter
36260         * var splitter = $("#splitter").data("kendoSplitter");
36261         * // expand the pane with ID, pane1
36262         * splitter.expand("#pane1");
36263         *
36264         */
36265        expand: function(pane) {
36266            this.toggle(pane, true);
36267        },
36268
36269        /**
36270         * Set the size of the pane. Setting this value will cause the <strong>Splitter</strong> to redraw and it will
36271         * trigger layoutChange and resize events.
36272         *
36273         * @name kendo.ui.Splitter#size
36274         * @function
36275         *
36276         * @param {Selector | DOM Element} pane
36277         * The pane to be resized.
36278         *
36279         * @param {String} value
36280         * The new size of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%"). Note: This value
36281         * must not exceed <strong>panes.max</strong> or be less then <strong>panes.min</strong>.
36282         *
36283         * @example
36284         * // get a reference to the splitter
36285         * var splitter = $("#splitter").data("kendoSplitter");
36286         * // set the size of the pane with ID, pane1
36287         * splitter.size("#pane1", "200px");
36288         *
36289         */
36290        size: panePropertyAccessor("size", true),
36291
36292        /**
36293         * Sets the minimum size of a pane. Setting this value will not cause the <strong>Splitter</strong> to
36294         * redraw, nor will it trigger any events.
36295         *
36296         * @name kendo.ui.Splitter#min
36297         * @function
36298         *
36299         * @param {Selector | DOM Element} pane
36300         * The pane being targetted for a new minimum size configuration value.
36301         *
36302         * @param {String} value
36303         * The minimum size value of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%").
36304         *
36305         * @example
36306         * // get a reference to the splitter
36307         * var splitter = $("#splitter").data("kendoSplitter");
36308         * // set the minimum size of the pane with ID, pane1
36309         * splitter.min("#pane1", "100px");
36310         *
36311         */
36312        min: panePropertyAccessor("min"),
36313
36314        /**
36315         * Sets the maximum size of a pane. Setting this value will not cause the <strong>Splitter</strong> to
36316         * redraw, nor will it trigger any events.
36317         *
36318         * @name kendo.ui.Splitter#max
36319         * @function
36320         *
36321         * @param {Selector | DOM Element} pane
36322         * The pane being targetted for a new minimum size configuration value.
36323         *
36324         * @param {String} value
36325         * The maximum size value of the pane defined as pixels (i.e. "200px") or as a percentage (i.e. "50%").
36326         *
36327         * @example
36328         * // get a reference to the splitter
36329         * var splitter = $("#splitter").data("kendoSplitter");
36330         * // set the maximum size of the pane with ID, pane1
36331         * splitter.max("#pane1", "300px");
36332         *
36333         */
36334        max: panePropertyAccessor("max")
36335    });
36336
36337    ui.plugin(Splitter);
36338
36339    var verticalDefaults = {
36340            sizingProperty: "height",
36341            sizingDomProperty: "offsetHeight",
36342            alternateSizingProperty: "width",
36343            positioningProperty: "top",
36344            mousePositioningProperty: "pageY"
36345        };
36346
36347    var horizontalDefaults = {
36348            sizingProperty: "width",
36349            sizingDomProperty: "offsetWidth",
36350            alternateSizingProperty: "height",
36351            positioningProperty: "left",
36352            mousePositioningProperty: "pageX"
36353        };
36354
36355    function PaneResizing(splitter) {
36356        var that = this,
36357            orientation = splitter.orientation;
36358
36359        that.owner = splitter;
36360        that._element = splitter.element;
36361        that.orientation = orientation;
36362
36363        extend(that, orientation === HORIZONTAL ? horizontalDefaults : verticalDefaults);
36364
36365        that._resizable = new kendo.ui.Resizable(splitter.element, {
36366            orientation: orientation,
36367            handle: splitter.element.children(".k-splitbar-draggable-" + orientation),
36368            hint: proxy(that._createHint, that),
36369            start: proxy(that._start, that),
36370            max: proxy(that._max, that),
36371            min: proxy(that._min, that),
36372            invalidClass:"k-restricted-size-" + orientation,
36373            resizeend: proxy(that._stop, that)
36374        });
36375    }
36376
36377    PaneResizing.prototype = {
36378        _createHint: function(handle) {
36379            var that = this;
36380            return $("<div class='k-ghost-splitbar k-ghost-splitbar-" + that.orientation + " k-state-default' />")
36381                        .css(that.alternateSizingProperty, handle[that.alternateSizingProperty]());
36382        },
36383        _start: function(e) {
36384            var that = this,
36385                splitbar = $(e.currentTarget),
36386                previousPane = splitbar.prev(),
36387                nextPane = splitbar.next(),
36388                previousPaneConfig = previousPane.data(PANE),
36389                nextPaneConfig = nextPane.data(PANE),
36390                prevBoundary = parseInt(previousPane[0].style[that.positioningProperty], 10),
36391                nextBoundary = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - splitbar[0][that.sizingDomProperty],
36392                totalSize = that._element.css(that.sizingProperty),
36393                toPx = function (value) {
36394                    var val = parseInt(value, 10);
36395                    return (isPixelSize(value) ? val : (totalSize * val) / 100) || 0;
36396                },
36397                prevMinSize = toPx(previousPaneConfig.min),
36398                prevMaxSize = toPx(previousPaneConfig.max) || nextBoundary - prevBoundary,
36399                nextMinSize = toPx(nextPaneConfig.min),
36400                nextMaxSize = toPx(nextPaneConfig.max) || nextBoundary - prevBoundary;
36401
36402            that.previousPane = previousPane;
36403            that.nextPane = nextPane;
36404            that._maxPosition = Math.min(nextBoundary - nextMinSize, prevBoundary + prevMaxSize);
36405            that._minPosition = Math.max(prevBoundary + prevMinSize, nextBoundary - nextMaxSize);
36406        },
36407        _max: function(e) {
36408              return this._maxPosition;
36409        },
36410        _min: function(e) {
36411            return this._minPosition;
36412        },
36413        _stop: function(e) {
36414            var that = this,
36415                splitbar = $(e.currentTarget),
36416                owner = that.owner;
36417
36418            owner._contentFrames(splitbar).next(".k-overlay").remove();
36419
36420            if (e.keyCode !== kendo.keys.ESC) {
36421                var ghostPosition = e.position,
36422                    previousPane = splitbar.prev(),
36423                    nextPane = splitbar.next(),
36424                    previousPaneConfig = previousPane.data(PANE),
36425                    nextPaneConfig = nextPane.data(PANE),
36426                    previousPaneNewSize = ghostPosition - parseInt(previousPane[0].style[that.positioningProperty], 10),
36427                    nextPaneNewSize = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - ghostPosition - splitbar[0][that.sizingDomProperty],
36428                    fluidPanesCount = that._element.children(PANECLASS).filter(function() { return isFluid($(this).data(PANE).size); }).length;
36429
36430                if (!isFluid(previousPaneConfig.size) || fluidPanesCount > 1) {
36431                    if (isFluid(previousPaneConfig.size)) {
36432                        fluidPanesCount--;
36433                    }
36434
36435                    previousPaneConfig.size = previousPaneNewSize + "px";
36436                }
36437
36438                if (!isFluid(nextPaneConfig.size) || fluidPanesCount > 1) {
36439                    nextPaneConfig.size = nextPaneNewSize + "px";
36440                }
36441
36442                owner.trigger(RESIZE);
36443            }
36444
36445            return false;
36446        }
36447    };
36448
36449})(jQuery);
36450(function($, undefined) {
36451    /**
36452     * @name kendo.ui.Upload.Description
36453     *
36454     * @section
36455     * <p>
36456     *  An <strong>Upload</strong> uses progressive enhancement to deliver the best possible uploading experience to
36457     *  users without requiring extra developer effort. Features highlights:
36458     * </p>
36459     * <ul>
36460     *  <li>Asynchronous and synchronous (on form submit) file upload</li>
36461     *  <li>Multiple file selection</li>
36462     *  <li>Removing uploaded files</li>
36463     *  <li>Progress tracking *</li>
36464     *  <li>File drag-and-drop *</li>
36465     *  <li>Cancelling upload in-progress *</li>
36466     * </ul>
36467     * <p>(*) These features are automatically enabled if supported by the browser.</p>
36468     * <p>An <strong>Upload</strong> is a standards-based widget; no plug-ins required.</p>
36469     * <h3>Getting Started</h3>
36470     *
36471     * @exampleTitle 1. Create a simple HTML form and input element of type "file"
36472     * @example
36473     * <!-- Kendo will automatically set the form enctype attribute to "multi-part/form-data" -->
36474     * <form method="post" action="handler.php">
36475     *     <div>
36476     *         <input name="photos[]" id="photos" type="file" />
36477     *     </div>
36478     * </form>
36479     *
36480     * @exampleTitle 2. Initialize Upload with a jQuery selector
36481     * @example
36482     * $(document).ready(function() {
36483     *     $("#photos").kendoUpload();
36484     * });
36485     *
36486     * @section
36487     * <p>
36488     *  Note the array syntax in the input name; it is used to hint the upload handler to treat photos as an array.
36489     * </p>
36490     * <p>
36491     *  Please consult the documentation of your specific server technology regarding the handling of uploaded files.
36492     * </p>
36493     * <h3>See Also</h3>
36494     * <p><a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx">Upload Modes</a></p>
36495     * <h3>Accessing an Existing Upload</h3>
36496     * <p>
36497     *  You can reference an existing <b>Upload</b> instance via
36498     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
36499     *  use the API to control its behavior.
36500     * </p>
36501     *
36502     * @exampleTitle Accessing an existing Upload instance
36503     * @example
36504     * var upload = $("#upload").data("kendoUpload");
36505     *
36506     */
36507    var kendo = window.kendo,
36508        Widget = kendo.ui.Widget,
36509        logToConsole = kendo.logToConsole,
36510        rFileExtension = /\.([^\.]+)$/,
36511        SELECT = "select",
36512        UPLOAD = "upload",
36513        SUCCESS = "success",
36514        ERROR = "error",
36515        COMPLETE = "complete",
36516        CANCEL = "cancel",
36517        LOAD = "load",
36518        REMOVE = "remove";
36519
36520    var Upload = Widget.extend(/** @lends kendo.ui.Upload.prototype */{
36521        /**
36522         *
36523         * Creates an Upload instance.
36524         *
36525         * @constructs
36526         * @extends kendo.ui.Widget
36527         *
36528         * @param {DomElement} element DOM element
36529         * @param {Object} options Configuration options.
36530         *
36531         * @option {Boolean} [enabled] <true>
36532         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) an <strong>Upload</strong>. A disabled
36533         * <strong>Upload</strong> may be re-enabled via enable().
36534         *
36535         * @option {Boolean} [multiple] <true>
36536         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability to select multiple files.
36537         * If <strong>false</strong>, users will be able to select only one file at a time. Note: This option does not
36538         * limit the total number of uploaded files in an asynchronous configuration.
36539         *
36540         * @option {Boolean} [showFileList] <true>
36541         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability to display a file listing
36542         * for uploading a file(s). Disabling a file listing may be useful you wish to customize the UI; use the
36543         * client-side events to build your own UI.
36544         *
36545         * @option {Object} [async]
36546         * Configures the ability to upload a file(s) in an asynchronous manner. Please refer to the
36547         * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode help topic</a>
36548         * for more details.
36549         *
36550         * @option {String} [async.saveUrl]
36551         * The URL of the handler that will receive the submitted files. The handler must accept POST requests
36552         * containing one or more fields with the same name as the original input name.
36553         *
36554         * @option {String} [async.saveField]
36555         * The name of the form field submitted to the save URL. The default value is the input name.
36556         *
36557         * @option {String} [async.removeUrl]
36558         * The URL of the handler responsible for removing uploaded files (if any). The handler must accept POST
36559         * requests containing one or more "fileNames" fields specifying the files to be deleted.
36560         *
36561         * @option {String} [async.removeVerb] <"DELETE">
36562         * The HTTP verb to be used by the remove action.
36563         *
36564         * @option {String} [async.removeField] <"fileNames">
36565         * The name of the form field submitted to the Remove URL.
36566         *
36567         * @option {Boolean} [async.autoUpload] <"fileNames">
36568         * The selected files will be uploaded immediately by default. You can change this behavior b
36568y setting
36569         * autoUpload to false.
36570         *
36571         * @option {Object} [localization]
36572         * Sets the strings rendered by the Upload.
36573         *
36574         * @option {String} [localization.select]
36575         * Sets the "Select..." button text.
36576         *
36577         * @option {String} [localization.cancel]
36578         * Sets the text of the cancel button text.
36579         *
36580         * @option {String} [localization.retry]
36581         * Sets the text of the retry button text.
36582         *
36583         * @option {String} [localization.remove]
36584         * Sets the text of the remove button text.
36585         *
36586         * @option {String} [localization.uploadSelectedFiles]
36587         * Sets the text of the "Upload files" button.
36588         *
36589         * @option {String} [localization.dropFilesHere] <"drop files here to upload">
36590         * Sets the drop zone hint.
36591         *
36592         * @option {String} [localization.statusUploading]
36593         * Sets the status message for files that are being uploaded.
36594         *
36595         * @option {String} [localization.statusUploaded]
36596         * Sets the status message for uploaded files.
36597         *
36598         * @option {String} [localization.statusFailed]
36599         * Sets the status message for failed uploads.
36600         *
36601         */
36602        init: function(element, options) {
36603            var that = this;
36604
36605            Widget.fn.init.call(that, element, options);
36606
36607            that.name = element.name;
36608            that.multiple = that.options.multiple;
36609            that.localization = that.options.localization;
36610
36611            var activeInput = that.element;
36612            that.wrapper = activeInput.closest(".k-upload");
36613            if (that.wrapper.length == 0) {
36614                that.wrapper = that._wrapInput(activeInput);
36615            }
36616
36617            that._activeInput(activeInput);
36618            that.toggle(that.options.enabled);
36619
36620            activeInput.closest("form").bind({
36621                "submit": $.proxy(that._onParentFormSubmit, that),
36622                "reset": $.proxy(that._onParentFormReset, that)
36623            });
36624
36625            if (that.options.async.saveUrl != undefined) {
36626                that._module = that._supportsFormData() ?
36627                new formDataUploadModule(that) :
36628                new iframeUploadModule(that);
36629            } else {
36630                that._module = new syncUploadModule(that);
36631            }
36632
36633            if (that._supportsDrop()) {
36634                that._setupDropZone();
36635            }
36636
36637            that.wrapper
36638            .delegate(".k-upload-action", "click", $.proxy(that._onFileAction, that))
36639            .delegate(".k-upload-selected", "click", $.proxy(that._onUploadSelected, that))
36640            .delegate(".k-file", "t:progress", $.proxy(that._onFileProgress, that))
36641            .delegate(".k-file", "t:upload-success", $.proxy(that._onUploadSuccess, that))
36642            .delegate(".k-file", "t:upload-error", $.proxy(that._onUploadError, that));
36643        },
36644
36645        events: [
36646            /**
36647            *
36648            * Triggered when a file(s) is selected. Note: Cancelling this event will prevent the selection from
36649            * occurring.
36650            *
36651            * @name kendo.ui.Upload#select
36652            * @event
36653            *
36654            * @param {Event} e
36655            *
36656            * @param {Array} e.files
36657            * An array of the selected files.
36658            *
36659            * <ul>
36660            *     <li>name - the name of a selected file, including its extension</li>
36661            *     <li>extension - the file extension of a selected file, including the leading dot (i.e. ".jpg")</li>
36662            *     <li>size - the size (in bytes) of a selected file (null, if unavailable)</li>
36663            *     <li>rawFile - an in-memory representation of a selected file</li>
36664            * </ul>
36665            *
36666            * @exampleTitle Wire-up an event handler that triggered when a user selects a file(s)
36667            * @example
36668            * var onSelect = function(e) {
36669            *     $.each(e.files, function(index, value) {
36670            *         console.log("Name: " + value.name);
36671            *         console.log("Size: " + value.size + " bytes");
36672            *         console.log("Extension: " + value.extension);
36673            *     });
36674            * };
36675            *
36676            * // initialize and configure an Upload widget with a select event handler
36677            * $("#photos").kendoUpload({
36678            *     // ...
36679            *     select: onSelect
36680            * });
36681            *
36682            */
36683            SELECT,
36684
36685            /**
36686            * Fires when one or more files are about to be uploaded.
36687            * Cancelling the event will prevent the upload.
36688            * <p>
36689            * Note: The upload event fires only when the upload is in
36690            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
36691            * </p>
36692            * @name kendo.ui.Upload#upload
36693            * @event
36694            * @param {Event} e
36695            * @param {Array} e.files
36696            * List of the files that will be uploaded. Each file has:
36697            * <ul>
36698            *     <li>name</li>
36699            *     <li>
36700            *         extension - the file extension
36701            *         inlcuding the leading dot - ".jpg", ".png", etc.
36702            *      </li>
36703            *     <li>size - the file size in bytes (null if not available)</li>
36704            * </ul>
36705            * @param {Object} data Optional object that will be
36706            * sent to the save handler in the form of key/value pairs.
36707            * @example
36708            * $("#photos").kendoUpload({
36709            *     // ...
36710            *     upload: onUpload
36711            * });
36712            *
36713            * function onUpload(e) {
36714            *     // Array with information about the uploaded files
36715            *     var files = e.files;
36716            *
36717            *     // Check the extension of each file and abort the upload if it is not .jpg
36718            *     $.each(files, function() {
36719            *         if (this.extension != ".jpg") {
36720            *             alert("Only .jpg files can be uploaded")
36721            *             e.preventDefault();
36722            *         }
36723            *     });
36724            * }
36725            */
36726            UPLOAD,
36727
36728            /**
36729            * Fires when an upload / remove operation has been completed successfully.
36730            * <p>
36731            * Note: The success event fires only when the upload is in
36732            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
36733            * </p>
36734            * @name kendo.ui.Upload#success
36735            * @event
36736            * @param {Event} e
36737            * @param {Array} e.files
36738            * List of the files that were uploaded or removed . Each file has:
36739            * <ul>
36740            *     <li>name</li>
36741            *     <li>
36742            *         extension - the file extension
36743            *         inlcuding the leading dot - ".jpg", ".png", etc.
36744            *      </li>
36745            *     <li>size - the file size in bytes (null if not available)</li>
36746            * </ul>
36747            * @param {String} e.operation "upload" or "remove".
36748            * @param {String} e.response the response object returned by the server.
36749            * @param {Object} e.XMLHttpRequest
36750            * This is either the original XHR used for the operation or a stub containing:
36751            * <ul>
36752            *     <li>responseText</li>
36753            *     <li>status</li>
36754            *     <li>statusText</li>
36755            * </ul>
36756            * Verify that this is an actual XHR before accessing any other fields.
36757            * @example
36758            * $("#photos").kendoUpload({
36759            *     // ...
36760            *     success: onSuccess
36761            * });
36762            *
36763            * function onSuccess(e) {
36764            *     // Array with information about the uploaded files
36765            *     var files = e.files;
36766            *
36767            *     if (e.operation == "upload") {
36768            *         alert("Successfully uploaded " + files.length + " files");
36769            *     }
36770            * }
36771            */
36772            SUCCESS,
36773
36774            /**
36775            * Fires when an upload / remove operation has failed.
36776            * <p>
36777            * Note: The error event fires only when the upload is in
36778            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
36779            * </p>
36780            * @name kendo.ui.Upload#error
36781            * @event
36782            * @param {Event} e
36783            * @param {Array} e.files
36784            * List of the files that were uploaded or removed . Each file has:
36785            * <ul>
36786            *     <li>name</li>
36787            *     <li>
36788            *         extension - the file extension
36789            *         inlcuding the leading dot - ".jpg", ".png", etc.
36790            *      </li>
36791            *     <li>size - the file size in bytes (null if not available)</li>
36792            * </ul>
36793            * @param {String} e.operation - "upload" or "remove".
36794            * @param {Object} e.XMLHttpRequest
36795            * This is either the original XHR used for the operation or a stub containing:
36796            * <ul>
36797            *     <li>responseText</li>
36798            *     <li>status</li>
36799            *     <li>statusText</li>
36800            * </ul>
36801            * Verify that this is an actual XHR before accessing any other fields.
36802            * @example
36803            * $("#photos").kendoUpload({
36804            *     // ...
36805            *     error: onError
36806            * });
36807            *
36808            * function onError(e) {
36809            *     // Array with information about the uploaded files
36810            *     var files = e.files;
36811            *
36812            *     if (e.operation == "upload") {
36813            *         alert("Failed to uploaded " + files.length + " files");
36814            *     }
36815            *
36816            *     // Suppress the default error message
36817            *     e.preventDefault();
36818            * }
36819            */
36820            ERROR,
36821
36822            /**
36823            * Fires when all active uploads have completed either successfully or with errors.
36824            * <p>
36825            * Note: The complete event fires only when the upload is in
36826            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
36827            * </p>
36828            * @name kendo.ui.Upload#complete
36829            * @event
36830            * @param {Event} e
36831            * @example
36832            * $("#photos").kendoUpload({
36833            *     // ...
36834            *     complete: onComplete
36835            * });
36836            *
36837            * function onComplete(e) {
36838            *     // The upload is now idle
36839            * }
36840            */
36841            COMPLETE,
36842
36843            /**
36844            * Fires when the upload has been cancelled while in progress.
36845            * <p>
36846            * Note: The cancel event fires only when the upload is in
36847            * <a href="http://www.kendoui.com/documentation/ui-widgets/upload/modes.aspx#async">async mode</a>.
36848            * </p>
36849            * @name kendo.ui.Upload#cancel
36850            * @event
36851            * @param {Event} e
36852            * @param {Array} e.files
36853            * List of the files that were uploaded or removed . Each file has:
36854            * <ul>
36855            *     <li>name</li>
36856            *     <li>
36857            *         extension - the file extension
36858            *         inlcuding the leading dot - ".jpg", ".png", etc.
36859            *      </li>
36860            *     <li>size - the file size in bytes (null if not available)</li>
36861            * </ul>
36862            * @example
36863            * $("#photos").kendoUpload({
36864            *     // ...
36865            *     cancel: onCancel
36866            * });
36867            *
36868            * function onCancel(e) {
36869            *     // Array with information about the uploaded files
36870            *     var files = e.files;
36871            *
36872            *     // Process the Cancel event
36873            * }
36874            */
36875            CANCEL,
36876
36877            /**
36878            * Fires when an uploaded file is about to be removed.
36879            * Cancelling the event will prevent the remove.
36880            * @name kendo.ui.Upload#remove
36881            * @event
36882            * @param {Event} e
36883            * @param {Array} e.files
36884            * List of the files that were uploaded or removed . Each file has:
36885            * <ul>
36886            *     <li>name</li>
36887            *     <li>
36888            *         extension - the file extension
36889            *         inlcuding the leading dot - ".jpg", ".png", etc.
36890            *      </li>
36891            *     <li>size - the file size in bytes (null if not available)</li>
36892            * </ul>
36893            * @param {Object} data Optional object that will be
36894            * sent to the save handler in the form of key/value pairs.
36895            * @example
36896            * $("#photos").kendoUpload({
36897            *     // ...
36898            *     remove: onRemove
36899            * });
36900            *
36901            * function onRemove(e) {
36902            *     // Array with information about the removed files
36903            *     var files = e.files;
36904            *
36905            *     // Process the Remove event
36906            *     // Optionally cancel the remove operation by calling
36907            *     // e.preventDefault()
36908            * }
36909            */
36910        REMOVE],
36911        options: {
36912            name: "Upload",
36913            enabled: true,
36914            multiple: true,
36915            showFileList: true,
36916            async: {
36917                removeVerb: "POST",
36918                autoUpload: true
36919            },
36920            localization: {
36921                "select": "Select...",
36922                "cancel": "Cancel",
36923                "retry": "Retry",
36924                "remove": "Remove",
36925                "uploadSelectedFiles": "Upload files",
36926                "dropFilesHere": "drop files here to upload",
36927                "statusUploading": "uploading",
36928                "statusUploaded": "uploaded",
36929                "statusFailed": "failed"
36930            }
36931        },
36932
36933        setOptions: function(options) {
36934            var that = this,
36935                activeInput = that.element;
36936
36937            Widget.fn.setOptions.call(that, options);
36938
36939            that.multiple = that.options.multiple;
36940
36941            activeInput.attr("multiple", that._supportsMultiple() ? that.multiple : false)
36942            that.toggle(that.options.enabled);
36943        },
36944
36945        /**
36946         * Enables the upload.
36947         * @example
36948         * var upload = $("#upload").data("kendoUpload");
36949         *
36950         * // enables the upload
36951         * upload.enable();
36952         */
36953        enable: function(enable) {
36954            enable = typeof (enable) === "undefined" ? true : enable;
36955            this.toggle(enable);
36956        },
36957
36958        /**
36959         * Disables the upload.
36960         * @example
36961         * var upload = $("#upload").data("kendoUpload");
36962         *
36963         * // disables the upload
36964         * upload.enable();
36965         */
36966        disable: function() {
36967            this.toggle(false);
36968        },
36969
36970        /**
36971         * Toggles the upload enabled state.
36972         * @param {Boolean} enable (Optional) The new enabled state.
36973         * @example
36974         * var upload = $("#upload").data("kendoUpload");
36975         *
36976         * // toggles the upload enabled state
36977         * upload.toggle();
36978         */
36979        toggle: function(enable) {
36980            enable = typeof (enable) === "undefined" ? enable : !enable;
36981            this.wrapper.toggleClass("k-state-disabled", enable);
36982        },
36983
36984        _addInput: function(input) {
36985            var that = this;
36986
36987            input
36988                .insertAfter(that.element)
36989                .data("kendoUpload", that);
36990
36991            $(that.element)
36992                .hide()
36993                .removeAttr("id");
36994
36995            that._activeInput(input);
36996        },
36997
36998        _activeInput: function(input) {
36999            var that = this,
37000                wrapper = that.wrapper;
37001
37002            that.element = input;
37003
37004            input
37005                .attr("multiple", that._supportsMultiple() ? that.multiple : false)
37006                .attr("autocomplete", "off")
37007                .click(function(e) {
37008                    if (wrapper.hasClass("k-state-disabled")) {
37009                        e.preventDefault();
37010                    }
37011                })
37012                .change($.proxy(that._onInputChange, that));
37013        },
37014
37015        _onInputChange: function(e) {
37016            var upload = this,
37017                input = $(e.target),
37018                prevented = upload.trigger(SELECT, { files: inputFiles(input) });
37019
37020            if (prevented) {
37021                upload._addInput(input.clone().val(""));
37022                input.remove();
37023            } else {
37024                input.trigger("t:select");
37025            }
37026        },
37027
37028        _onDrop: function (e) {
37029            var dt = e.originalEvent.dataTransfer,
37030                that = this,
37031                droppedFiles = dt.files;
37032
37033            stopEvent(e);
37034
37035            if (droppedFiles.length > 0) {
37036                var prevented = that.trigger(SELECT, { files: getAllFileInfo(droppedFiles) });
37037                if (!prevented) {
37038                    $(".k-dropzone", that.wrapper).trigger("t:select", [ droppedFiles ]);
37039                }
37040            }
37041        },
37042
37043        _enqueueFile: function(name, data) {
37044            var that = this,
37045                existingFileEntries,
37046                fileEntry,
37047                fileList =  $(".k-upload-files", that.wrapper);
37048
37049            if (fileList.length == 0) {
37050                fileList = $("<ul class='k-upload-files k-reset'></ul>").appendTo(that.wrapper);
37051                if (!that.options.showFileList) {
37052                    fileList.hide();
37053                }
37054            }
37055
37056            existingFileEntries = $(".k-file", fileList);
37057            fileEntry =
37058                $("<li class='k-file'><span class='k-icon'></span><span class='k-filename' title='" + name + "'>" + name + "</span></li>")
37059                .appendTo(fileList)
37060                .data(data);
37061
37062            if (!that.multiple) {
37063                existingFileEntries.trigger("t:remove");
37064            }
37065
37066            return fileEntry;
37067        },
37068
37069        _removeFileEntry: function(fileEntry) {
37070            var fileList = fileEntry.closest(".k-upload-files"),
37071                allFiles;
37072
37073            fileEntry.remove();
37074            allFiles = $(".k-file", fileList);
37075
37076            if (allFiles.find("> .k-fail").length === allFiles.length) {
37077                this._hideUploadButton();
37078            }
37079
37080            if (allFiles.length == 0) {
37081                fileList.remove();
37082            }
37083        },
37084
37085        _fileAction: function(fileElement, actionKey) {
37086            var classDictionary = { remove: "k-delete", cancel: "k-cancel", retry: "k-retry" };
37087            if (!classDictionary.hasOwnProperty(actionKey)) {
37088                return;
37089            }
37090
37091            this._clearFileAction(fileElement);
37092
37093            fileElement.append(
37094                this._renderAction(classDictionary[actionKey], this.localization[actionKey])
37095                .addClass("k-upload-action")
37096            );
37097        },
37098
37099        _fileState: function(fileEntry, stateKey) {
37100            var localization = this.localization,
37101                states = {
37102                    uploading: {
37103                        cssClass: "k-loading",
37104                        text : localization.statusUploading
37105                    },
37106                    uploaded: {
37107                        cssClass: "k-success",
37108                        text : localization.statusUploaded
37109                    },
37110                    failed: {
37111                        cssClass: "k-fail",
37112                        text : localization.statusFailed
37113                    }
37114                },
37115                currentState = states[stateKey];
37116
37117            if (currentState) {
37118                var icon = fileEntry.children(".k-icon").text(currentState.text);
37119                icon[0].className = "k-icon " + currentState.cssClass;
37120            }
37121        },
37122
37123        _renderAction: function (actionClass, actionText) {
37124            if (actionClass != "") {
37125                return $(
37126                "<button type='button' class='k-button k-button-icontext'>" +
37127                    "<span class='k-icon " + actionClass + "'></span>" +
37128                    actionText +
37129                "</button>"
37130                )
37131            }
37132            else {
37133                return $(
37134                "<button type='button' class='k-button'>" +
37135                    actionText +
37136                "</button>"
37137                )
37138            }
37139        },
37140
37141        _clearFileAction: function(fileElement) {
37142            fileElement
37143                .find(".k-upload-action").remove();
37144        },
37145
37146        _onFileAction: function(e) {
37147            var that = this;
37148
37149            if (!that.wrapper.hasClass("k-state-disabled")) {
37150                var button = $(e.target).closest(".k-upload-action"),
37151                    icon = button.find(".k-icon"),
37152                    fileEntry = button.closest(".k-file"),
37153                    eventArgs = { files: fileEntry.data("fileNames") };
37154
37155                if (icon.hasClass("k-delete")) {
37156                    if (!that.trigger(REMOVE, eventArgs)) {
37157                        fileEntry.trigger("t:remove", eventArgs.data);
37158                    }
37159                } else if (icon.hasClass("k-cancel")) {
37160                    that.trigger(CANCEL, eventArgs);
37161                    fileEntry.trigger("t:cancel");
37162                    this._checkAllComplete();
37163                } else if (icon.hasClass("k-retry")) {
37164                    fileEntry.trigger("t:retry");
37165                }
37166            }
37167
37168            return false;
37169        },
37170
37171        _onUploadSelected: function() {
37172            this.wrapper.trigger("t:saveSelected");
37173            return false;
37174        },
37175
37176        _onFileProgress: function(e, percentComplete) {
37177            var progressBar = $(".k-progress-status", e.target);
37178            if (progressBar.length == 0) {
37179                progressBar =
37180                    $("<span class='k-progress'><span class='k-progress-status' style='width: 0;'></span></span>")
37181                        .appendTo($(".k-filename", e.target))
37182                        .find(".k-progress-status");
37183            }
37184
37185            progressBar.width(percentComplete + "%");
37186        },
37187
37188        _onUploadSuccess: function(e, response, xhr) {
37189            var fileEntry = getFileEntry(e);
37190
37191            this._fileState(fileEntry, "uploaded");
37192
37193            this.trigger(SUCCESS, {
37194                files: fileEntry.data("fileNames"),
37195                response: response,
37196                operation: "upload",
37197                XMLHttpRequest: xhr
37198            });
37199
37200            if (this._supportsRemove()) {
37201                this._fileAction(fileEntry, REMOVE);
37202            } else {
37203                this._clearFileAction(fileEntry);
37204            }
37205
37206            this._checkAllComplete();
37207        },
37208
37209        _onUploadError: function(e, xhr) {
37210            var fileEntry = getFileEntry(e);
37211
37212            this._fileState(fileEntry, "failed");
37213            this._fileAction(fileEntry, "retry");
37214
37215            var prevented = this.trigger(ERROR, {
37216                operation: "upload",
37217                files: fileEntry.data("fileNames"),
37218                XMLHttpRequest: xhr
37219            });
37220
37221            logToConsole("Server response: " + xhr.responseText);
37222
37223            if (!prevented) {
37224                this._alert("Error! Upload failed. Unexpected server response - see console.");
37225            }
37226
37227            this._checkAllComplete();
37228        },
37229
37230        _showUploadButton: function() {
37231            var uploadButton = $(".k-upload-selected", this.wrapper);
37232            if (uploadButton.length == 0) {
37233                uploadButton =
37234                    this._renderAction("", this.localization["uploadSelectedFiles"])
37235                    .addClass("k-upload-selected");
37236            }
37237
37238            this.wrapper.append(uploadButton);
37239        },
37240
37241        _hideUploadButton: function() {
37242            $(".k-upload-selected", this.wrapper).remove();
37243        },
37244
37245        _onParentFormSubmit: function() {
37246            var upload = this,
37247                element = upload.element;
37248            element.trigger("t:abort");
37249
37250            if (!element.value) {
37251                var input = $(element);
37252
37253                // Prevent submitting an empty input
37254                input.attr("disabled", "disabled");
37255
37256                window.setTimeout(function() {
37257                    // Restore the input so the Upload remains functional
37258                    // in case the user cancels the form submit
37259                    input.removeAttr("disabled");
37260                }, 0);
37261            }
37262        },
37263
37264        _onParentFormReset: function() {
37265            $(".k-upload-files", this.wrapper).remove();
37266        },
37267
37268        _supportsFormData: function() {
37269            if ($.browser.safari) {
37270                return false;
37271            }
37272
37273            return typeof(FormData) != "undefined";
37274        },
37275
37276        _supportsMultiple: function() {
37277            return !$.browser.opera;
37278        },
37279
37280        _supportsDrop: function() {
37281            var userAgent = this._userAgent().toLowerCase(),
37282                isChrome = /chrome/.test(userAgent),
37283                isSafari = !isChrome && /safari/.test(userAgent),
37284                isWindowsSafari = isSafari && /windows/.test(userAgent);
37285
37286            return !isWindowsSafari && this._supportsFormData() && (this.options.async.saveUrl != undefined);
37287        },
37288
37289        _userAgent: function() {
37290            return navigator.userAgent;
37291        },
37292
37293        _setupDropZone: function() {
37294            $(".k-upload-button", this.wrapper)
37295                .wrap("<div class='k-dropzone'></div>");
37296
37297            var dropZone = $(".k-dropzone", this.wrapper)
37298                .append($("<em>" + this.localization["dropFilesHere"] + "</em>"))
37299                .bind({
37300                    "dragenter": stopEvent,
37301                    "dragover": function(e) { e.preventDefault(); },
37302                    "drop" : $.proxy(this._onDrop, this)
37303                });
37304
37305            bindDragEventWrappers(dropZone,
37306                function() { dropZone.addClass("k-dropzone-hovered"); },
37307                function() { dropZone.removeClass("k-dropzone-hovered"); });
37308
37309            bindDragEventWrappers($(document),
37310                function() { dropZone.addClass("k-dropzone-active"); },
37311                function() { dropZone.removeClass("k-dropzone-active"); });
37312        },
37313
37314        _supportsRemove: function() {
37315            return this.options.async.removeUrl != undefined;
37316        },
37317
37318        _submitRemove: function(fileNames, data, onSuccess, onError) {
37319            var upload = this,
37320                removeField = upload.options.async.removeField || "fileNames",
37321                params = $.extend(data, getAntiForgeryTokens());
37322
37323            params[removeField] = fileNames;
37324
37325            $.ajax({
37326                  type: this.options.async.removeVerb,
37327                  dataType: "json",
37328                  url: this.options.async.removeUrl,
37329                  traditional: true,
37330                  data: params,
37331                  success: onSuccess,
37332                  error: onError
37333            });
37334        },
37335
37336        _alert: function(message) {
37337            alert(message);
37338        },
37339
37340        _wrapInput: function(input) {
37341            input.wrap("<div class='k-widget k-upload'><div class='k-button k-upload-button'></div></div>");
37342            input.closest(".k-button")
37343                .append("<span>" + this.localization.select + "</span>");
37344
37345            return input.closest(".k-upload");
37346        },
37347
37348        _checkAllComplete: function() {
37349            if ($(".k-file .k-icon.k-loading", this.wrapper).length == 0) {
37350                this.trigger(COMPLETE);
37351            }
37352        }
37353    });
37354
37355    // Synchronous upload module
37356    var syncUploadModule = function(upload) {
37357        this.name = "syncUploadModule";
37358        this.element = upload.wrapper;
37359        this.upload = upload;
37360        this.element
37361            .bind("t:select", $.proxy(this.onSelect, this))
37362            .bind("t:remove", $.proxy(this.onRemove, this))
37363            .closest("form")
37364                .attr("enctype", "multipart/form-data")
37365                .attr("encoding", "multipart/form-data");
37366    };
37367
37368    syncUploadModule.prototype = /** @ignore */  {
37369        onSelect: function(e) {
37370            var upload = this.upload;
37371            var sourceInput = $(e.target);
37372            upload._addInput(sourceInput.clone().val(""));
37373            var file = upload._enqueueFile(getFileName(sourceInput), {
37374                "relatedInput" : sourceInput, "fileNames": inputFiles(sourceInput)
37375            });
37376
37377            upload._fileAction(file, REMOVE);
37378        },
37379
37380        onRemove: function(e) {
37381            var fileEntry = getFileEntry(e);
37382            fileEntry.data("relatedInput").remove();
37383
37384            this.upload._removeFileEntry(fileEntry);
37385        }
37386    };
37387
37388    // Iframe upload module
37389    var iframeUploadModule = function(upload) {
37390        this.name = "iframeUploadModule";
37391        this.element = upload.wrapper;
37392        this.upload = upload;
37393        this.iframes = [];
37394        this.element
37395            .bind("t:select", $.proxy(this.onSelect, this))
37396            .bind("t:cancel", $.proxy(this.onCancel, this))
37397            .bind("t:retry", $.proxy(this.onRetry, this))
37398            .bind("t:remove", $.proxy(this.onRemove, this))
37399            .bind("t:saveSelected", $.proxy(this.onSaveSelected, this))
37400            .bind("t:abort", $.proxy(this.onAbort, this));
37401    };
37402
37403    Upload._frameId = 0;
37404
37405    iframeUploadModule.prototype = /** @ignore */ {
37406        onSelect: function(e) {
37407            var upload = this.upload,
37408                sourceInput = $(e.target);
37409
37410            var fileEntry = this.prepareUpload(sourceInput);
37411
37412            if (upload.options.async.autoUpload) {
37413                this.performUpload(fileEntry);
37414            } else {
37415                if (upload._supportsRemove()) {
37416                    this.upload._fileAction(fileEntry, REMOVE);
37417                }
37418
37419                upload._showUploadButton();
37420            }
37421        },
37422
37423        prepareUpload: function(sourceInput) {
37424            var upload = this.upload;
37425            var activeInput = $(upload.element);
37426            var name = upload.options.async.saveField || sourceInput.attr("name");
37427            upload._addInput(sourceInput.clone().val(""));
37428
37429            sourceInput.attr("name", name);
37430
37431            var iframe = this.createFrame(upload.name + "_" + Upload._frameId++);
37432            this.registerFrame(iframe);
37433
37434            var form = this.createForm(upload.options.async.saveUrl, iframe.attr("name"))
37435                .append(activeInput);
37436
37437            var fileEntry = upload._enqueueFile(
37438                getFileName(sourceInput),
37439                { "frame": iframe, "relatedInput": activeInput, "fileNames": inputFiles(sourceInput) });
37440
37441            iframe
37442                .data({ "form": form, "file": fileEntry });
37443
37444            return fileEntry;
37445        },
37446
37447        performUpload: function(fileEntry) {
37448            var e = { files: fileEntry.data("fileNames") },
37449                iframe = fileEntry.data("frame"),
37450                upload = this.upload;
37451
37452            if (!upload.trigger(UPLOAD, e)) {
37453                upload._hideUploadButton();
37454
37455                iframe.appendTo(document.body);
37456
37457                var form = iframe.data("form")
37458                    .appendTo(document.body);
37459
37460                e.data = $.extend({ }, e.data, getAntiForgeryTokens());
37461                for (var key in e.data) {
37462                    var dataInput = form.find("input[name='" + key + "']");
37463                    if (dataInput.length == 0) {
37464                        dataInput = $("<input>", { type: "hidden", name: key })
37465                            .appendTo(form);
37466                    }
37467                    dataInput.val(e.data[key]);
37468                }
37469
37470                upload._fileAction(fileEntry, CANCEL);
37471                upload._fileState(fileEntry, "uploading");
37472
37473                iframe
37474                    .one("load", $.proxy(this.onIframeLoad, this));
37475
37476                form[0].submit();
37477            } else {
37478                upload._removeFileEntry(iframe.data("file"));
37479                this.cleanupFrame(iframe);
37480                this.unregisterFrame(iframe);
37481            }
37482        },
37483
37484        onSaveSelected: function(e) {
37485            var module = this;
37486
37487            $(".k-file", this.element).each(function() {
37488                var fileEntry = $(this),
37489                    started = isFileUploadStarted(fileEntry);
37490
37491                if (!started) {
37492                    module.performUpload(fileEntry);
37493                }
37494            });
37495        },
37496
37497        onIframeLoad: function(e) {
37498            var iframe = $(e.target);
37499
37500            try {
37501                var responseText = iframe.contents().text();
37502            } catch (e) {
37503                responseText = "Error trying to get server response: " + e;
37504            }
37505
37506            this.processResponse(iframe, responseText);
37507        },
37508
37509        processResponse: function(iframe, responseText) {
37510            var fileEntry = iframe.data("file"),
37511                module = this,
37512                fakeXHR = {
37513                    responseText: responseText
37514                };
37515
37516            tryParseJSON(responseText,
37517                function(jsonResult) {
37518                    $.extend(fakeXHR, { statusText: "OK", status: "200" });
37519                    fileEntry.trigger("t:upload-success", [ jsonResult, fakeXHR ]);
37520                    module.cleanupFrame(iframe);
37521                    module.unregisterFrame(iframe);
37522                },
37523                function() {
37524                    $.extend(fakeXHR, { statusText: "error", status: "500" });
37525                    fileEntry.trigger("t:upload-error", [ fakeXHR ]);
37526                }
37527            );
37528        },
37529
37530        onCancel: function(e) {
37531            var iframe = $(e.target).data("frame");
37532
37533            this.stopFrameSubmit(iframe);
37534            this.cleanupFrame(iframe);
37535            this.unregisterFrame(iframe);
37536            this.upload._removeFileEntry(iframe.data("file"));
37537        },
37538
37539        onRetry: function(e) {
37540            var fileEntry = getFileEntry(e);
37541            this.performUpload(fileEntry);
37542        },
37543
37544        onRemove: function(e, data) {
37545            var fileEntry = getFileEntry(e);
37546
37547            var iframe = fileEntry.data("frame");
37548            if (iframe)
37549            {
37550                this.unregisterFrame(iframe);
37551                this.upload._removeFileEntry(fileEntry);
37552                this.cleanupFrame(iframe);
37553            } else {
37554                removeUploadedFile(fileEntry, this.upload, data);
37555            }
37556        },
37557
37558        onAbort: function() {
37559            var element = this.element,
37560                module = this;
37561
37562            $.each(this.iframes, function() {
37563                $("input", this.data("form")).appendTo(element);
37564                module.stopFrameSubmit(this[0]);
37565                this.data("form").remove();
37566                this.remove();
37567            });
37568
37569            this.iframes = [];
37570        },
37571
37572        createFrame: function(id) {
37573            return $(
37574                "<iframe" +
37575                " name='" + id + "'" +
37576                " id='" + id + "'" +
37577                " style='display:none;' />"
37578            );
37579        },
37580
37581        createForm: function(action, target) {
37582            return $(
37583                "<form enctype='multipart/form-data' method='POST'" +
37584                " action='" + action + "'" +
37585                " target='" + target + "'" +
37586                "/>");
37587        },
37588
37589        stopFrameSubmit: function(frame) {
37590            if (typeof(frame.stop) != "undefined") {
37591                frame.stop();
37592            } else if (frame.document) {
37593                frame.document.execCommand("Stop");
37594                frame.contentWindow.location.href = frame.contentWindow.location.href;
37595            }
37596        },
37597
37598        registerFrame: function(frame) {
37599            this.iframes.push(frame);
37600        },
37601
37602        unregisterFrame: function(frame) {
37603            this.iframes = $.grep(this.iframes, function(value) {
37604                return value.attr("name") != frame.attr("name");
37605            });
37606        },
37607
37608        cleanupFrame: function(frame) {
37609            var form = frame.data("form");
37610
37611            frame.data("file").data("frame", null);
37612
37613            setTimeout(function () {
37614                form.remove();
37615                frame.remove();
37616            }, 1);
37617        }
37618    };
37619
37620    // FormData upload module
37621    var formDataUploadModule = function(upload) {
37622        this.name = "formDataUploadModule";
37623        this.element = upload.wrapper;
37624        this.upload = upload;
37625        this.element
37626            .bind("t:select", $.proxy(this.onSelect, this))
37627            .bind("t:cancel", $.proxy(this.onCancel, this))
37628            .bind("t:remove", $.proxy(this.onRemove, this))
37629            .bind("t:retry", $.proxy(this.onRetry, this))
37630            .bind("t:saveSelected", $.proxy(this.onSaveSelected, this))
37631            .bind("t:abort", $.proxy(this.onAbort, this));
37632    };
37633
37634    formDataUploadModule.prototype = /** @ignore */ {
37635        onSelect: function(e, rawFiles) {
37636            var upload = this.upload,
37637                module = this,
37638                sourceElement = $(e.target),
37639                files = rawFiles ? getAllFileInfo(rawFiles) : this.inputFiles(sourceElement),
37640                fileEntries = this.prepareUpload(sourceElement, files);
37641
37642            $.each(fileEntries, function() {
37643                if (upload.options.async.autoUpload) {
37644                    module.performUpload(this);
37645                } else {
37646                    if (upload._supportsRemove()) {
37647                        upload._fileAction(this, REMOVE);
37648                    }
37649                    upload._showUploadButton();
37650                }
37651            });
37652        },
37653
37654        prepareUpload: function(sourceElement, files) {
37655            var fileEntries = this.enqueueFiles(files);
37656
37657            if (sourceElement.is("input")) {
37658                $.each(fileEntries, function() {
37659                    $(this).data("relatedInput", sourceElement);
37660                });
37661                sourceElement.data("relatedFileEntries", fileEntries);
37662                this.upload._addInput(sourceElement.clone().val(""));
37663            }
37664
37665            return fileEntries;
37666        },
37667
37668        enqueueFiles: function(arrFileInfo) {
37669            var upload = this.upload
37670                fileEntries = [];
37671
37672            for (var i = 0; i < arrFileInfo.length; i++) {
37673                var currentFile = arrFileInfo[i],
37674                    name = currentFile.name;
37675
37676                var fileEntry = upload._enqueueFile(name, { "fileNames": [ currentFile ] });
37677                fileEntry.data("formData", this.createFormData(arrFileInfo[i]));
37678
37679                fileEntries.push(fileEntry);
37680            }
37681
37682            return fileEntries;
37683        },
37684
37685        inputFiles: function(sourceInput) {
37686            return inputFiles(sourceInput);
37687        },
37688
37689        performUpload: function(fileEntry) {
37690            var upload = this.upload,
37691                formData = fileEntry.data("formData"),
37692                e = { files: fileEntry.data("fileNames") };
37693
37694            if (!upload.trigger(UPLOAD, e)) {
37695                upload._fileAction(fileEntry, CANCEL);
37696                upload._hideUploadButton();
37697
37698                if (!fileEntry.data("dataAppended")) {
37699                    e.data = $.extend({ }, e.data, getAntiForgeryTokens());
37700                    for (var key in e.data) {
37701                        formData.append(key, e.data[key]);
37702                    }
37703
37704                    fileEntry.data("dataAppended", true);
37705                }
37706
37707                upload._fileState(fileEntry, "uploading");
37708
37709                this.postFormData(this.upload.options.async.saveUrl, formData, fileEntry);
37710            } else {
37711                this.removeFileEntry(fileEntry);
37712            }
37713        },
37714
37715        onSaveSelected: function(e) {
37716            var module = this;
37717
37718            $(".k-file", this.element).each(function() {
37719                var fileEntry = $(this),
37720                    started = isFileUploadStarted(fileEntry);
37721
37722                if (!started) {
37723                    module.performUpload(fileEntry);
37724                }
37725            });
37726        },
37727
37728        onCancel: function(e) {
37729            var fileEntry = getFileEntry(e);
37730            this.stopUploadRequest(fileEntry);
37731            this.removeFileEntry(fileEntry);
37732        },
37733
37734        onRetry: function(e) {
37735            var fileEntry = getFileEntry(e);
37736            this.performUpload(fileEntry);
37737        },
37738
37739        onRemove: function(e, data) {
37740            var fileEntry = getFileEntry(e);
37741
37742            if (fileEntry.children(".k-icon").is(".k-success")) {
37743                removeUploadedFile(fileEntry, this.upload, data);
37744            } else {
37745                this.removeFileEntry(fileEntry);
37746            }
37747        },
37748
37749        postFormData: function(url, data, fileEntry) {
37750            var xhr = new XMLHttpRequest(),
37751                module = this;
37752
37753            fileEntry.data("request", xhr);
37754
37755            xhr.addEventListener("load", function(e) {
37756                module.onRequestSuccess.call(module, e, fileEntry);
37757            }, false);
37758
37759            xhr.addEventListener(ERROR, function(e) {
37760                module.onRequestError.call(module, e, fileEntry);
37761            }, false);
37762
37763            xhr.upload.addEventListener("progress", function(e) {
37764                module.onRequestProgress.call(module, e, fileEntry);
37765            }, false);
37766
37767            xhr.open("POST", url);
37768            xhr.send(data);
37769        },
37770
37771        createFormData: function(fileInfo) {
37772            var formData = new FormData(),
37773            upload = this.upload;
37774
37775            formData.append(upload.options.async.saveField || upload.name, fileInfo.rawFile);
37776
37777            return formData;
37778        },
37779
37780        onRequestSuccess: function(e, fileEntry) {
37781            var xhr = e.target,
37782                module = this;
37783            tryParseJSON(xhr.responseText,
37784                function(jsonResult) {
37785                    fileEntry.trigger("t:upload-success", [ jsonResult, xhr ]);
37786                    fileEntry.trigger("t:progress", [ 100 ]);
37787                    module.cleanupFileEntry(fileEntry);
37788                },
37789                function() {
37790                    fileEntry.trigger("t:upload-error", [ xhr ]);
37791                }
37792            );
37793        },
37794
37795        onRequestError: function(e, fileEntry) {
37796            var xhr = e.target;
37797            fileEntry.trigger("t:upload-error", [ xhr ]);
37798        },
37799
37800        cleanupFileEntry: function(fileEntry) {
37801            var relatedInput = fileEntry.data("relatedInput"),
37802                uploadComplete = true;
37803
37804            if (relatedInput) {
37805                $.each(relatedInput.data("relatedFileEntries"), function() {
37806                    // Exclude removed file entries and self
37807                    if (this.parent().length > 0 && this[0] != fileEntry[0]) {
37808                        uploadComplete = uploadComplete && this.children(".k-icon").is(".k-success");
37809                    }
37810                });
37811
37812                if (uploadComplete) {
37813                    relatedInput.remove();
37814                }
37815            }
37816
37817            fileEntry.data("formData", null);
37818        },
37819
37820        removeFileEntry: function(fileEntry) {
37821            this.cleanupFileEntry(fileEntry);
37822            this.upload._removeFileEntry(fileEntry);
37823        },
37824
37825        onRequestProgress: function(e, fileEntry) {
37826            var percentComplete = Math.round(e.loaded * 100 / e.total);
37827            fileEntry.trigger("t:progress", [ percentComplete ]);
37828        },
37829
37830        stopUploadRequest: function(fileEntry) {
37831            fileEntry.data("request").abort();
37832        }
37833    };
37834
37835    // Helper functions
37836    function getFileName(input) {
37837        return $.map(inputFiles(input), function (file) {
37838            return file.name;
37839        }).join(", ");
37840    }
37841
37842    function inputFiles($input) {
37843        var input = $input[0];
37844        if (input.files) {
37845            return getAllFileInfo(input.files);
37846        } else {
37847            return [{
37848                name: stripPath(input.value),
37849                extension: getFileExtension(input.value),
37850                size: null
37851            }];
37852        }
37853    }
37854
37855    function getAllFileInfo(rawFiles) {
37856        return $.map(rawFiles, function (file) {
37857            return getFileInfo(file);
37858        });
37859    }
37860
37861    function getFileInfo(rawFile) {
37862        // Older Firefox versions (before 3.6) use fileName and fileSize
37863        var fileName = rawFile.name || rawFile.fileName;
37864        return {
37865            name: fileName,
37866            extension: getFileExtension(fileName),
37867            size: rawFile.size || rawFile.fileSize,
37868            rawFile: rawFile
37869        };
37870    }
37871
37872    function getFileExtension(fileName) {
37873        var matches = fileName.match(rFileExtension);
37874        return matches ? matches[0] : "";
37875    }
37876
37877    function stripPath(name) {
37878        var slashIndex = name.lastIndexOf("\\");
37879        return (slashIndex != -1) ? name.substr(slashIndex + 1) : n
37879ame;
37880    }
37881
37882    function removeUploadedFile(fileEntry, upload, data) {
37883        if (!upload._supportsRemove()) {
37884            return;
37885        }
37886
37887        var files = fileEntry.data("fileNames");
37888        var fileNames = $.map(files, function(file) { return file.name });
37889
37890        upload._submitRemove(fileNames, data,
37891            function onSuccess(data, textStatus, xhr) {
37892                upload._removeFileEntry(fileEntry);
37893
37894                upload.trigger(SUCCESS, {
37895                    operation: "remove",
37896                    files: files,
37897                    response: data,
37898                    XMLHttpRequest: xhr });
37899            },
37900
37901            function onError(xhr, textStatus, textStatus) {
37902                var prevented = upload.trigger(ERROR, {
37903                    operation: "remove",
37904                    files: files,
37905                    XMLHttpRequest: xhr });
37906
37907                logToConsole("Server response: " + xhr.responseText);
37908
37909                if (!prevented) {
37910                    upload._alert("Error! Remove operation failed. Unexpected response - see console.");
37911                }
37912            }
37913        );
37914    }
37915
37916    function tryParseJSON(input, onSuccess, onError) {
37917        try {
37918            var json = $.parseJSON(input);
37919            onSuccess(json);
37920        } catch (e) {
37921            onError();
37922        }
37923    }
37924
37925    function stopEvent(e) {
37926        e.stopPropagation(); e.preventDefault();
37927    }
37928
37929    function bindDragEventWrappers(element, onDragEnter, onDragLeave) {
37930        var hideInterval, lastDrag;
37931
37932        element
37933            .bind("dragenter", function(e) {
37934                onDragEnter();
37935                lastDrag = new Date();
37936
37937                if (!hideInterval) {
37938                    hideInterval = setInterval(function() {
37939                        var sinceLastDrag = new Date() - lastDrag;
37940                        if (sinceLastDrag > 100) {
37941                            onDragLeave();
37942
37943                            clearInterval(hideInterval);
37944                            hideInterval = null;
37945                        }
37946                    }, 100);
37947                }
37948            })
37949            .bind("dragover", function(e) {
37950                lastDrag = new Date();
37951            });
37952    }
37953
37954    function isFileUploadStarted(fileEntry) {
37955        return fileEntry.children(".k-icon").is(".k-loading, .k-success, .k-fail");
37956    }
37957
37958    function getFileEntry(e) {
37959        return $(e.target).closest(".k-file");
37960    }
37961
37962    function getAntiForgeryTokens() {
37963        var tokens = { },
37964            csrf_token = $("meta[name=csrf-token]").attr("content"),
37965            csrf_param = $("meta[name=csrf-param]").attr("content");
37966
37967        $("input[name^='__RequestVerificationToken']").each(function() {
37968            tokens[this.name] = this.value;
37969        });
37970
37971        if (csrf_param !== undefined && csrf_token !== undefined) {
37972          tokens[csrf_param] = csrf_token;
37973        }
37974
37975        return tokens;
37976    }
37977    kendo.ui.plugin(Upload);
37978})(jQuery);
37979/**
37980 * @fileOverview Provides a Window implementation which can be used to display content in a modal or non-modal HTML
37981 * window.
37982 */
37983
37984(function($, undefined) {
37985    /**
37986     * @name kendo.ui.Window.Description
37987     *
37988     * @section
37989     * <p>
37990     *  A <strong>Window</strong> displays content in a modal or non-modal HTML window. By default, a
37991     *  <strong>Window</strong> can be moved, resized, and closed. Its content can also be defined with either as
37992     *  static HTML or loaded dynamically via AJAX.
37993     * </p>
37994     * <p>
37995     *  A <strong>Window</strong> can be initialized from virtually any DOM element. During initialization, the
37996     *  targeted content will automatically be wrapped in the div element of the <strong>Window</strong>.
37997     * </p>
37998     * <h3>Getting Started</h3>
37999     *
38000     * @exampleTitle Create a simple HTML element with the Window content
38001     * @example
38002     * <div id="window">
38003     *     Content of the Window
38004     * </div>
38005     *
38006     * @exampleTitle Initialize the Window using a selector
38007     * @example
38008     * $(document).ready(function() {
38009     *     $("#window").kendoWindow();
38010     * });
38011     *
38012     * @section
38013     * <p>
38014     *  When a <strong>Window</strong> is initialized, it will automatically be displayed open near the location of the
38015     *  DOM element that was used to initialize the content.
38016     * </p>
38017     * <h3>Configuring Window Behaviors</h3>
38018     * <p>
38019     *  A <strong>Window</strong> provides many configuration options that can be easily set during initialization.
38020     *  Among the properties that can be controlled:
38021     * </p>
38022     * <ul>
38023     *  <li>Minimum height/width</li>
38024     *  <li>Available user actions (close/refresh/maximize/minimize) and ability to define custom ones</li>
38025     *  <li>Title</li>
38026     *  <li>Draggable and resizable behaviors</li>
38027     * </ul>
38028     *
38029     * @exampleTitle Create a modal Window with all user actions enabled
38030     * @example
38031     * $("#window").kendoWindow({
38032     *     actions: ["Custom", "Refresh", "Maximize", "Minimize", "Close"],
38033     *     draggable: false,
38034     *     height: "300px",
38035     *     modal: true,
38036     *     resizable: false,
38037     *     title: "Modal Window",
38038     *     width: "500px"
38039     * });
38040     *
38041     * @section
38042     * <p>
38043     *  The order of the values in the actions array determines the order in which the action buttons will be rendered
38044     *  in the title of a <strong>Window</strong>. The maximize action serves both as a button for expanding a
38045     *  <strong>Window</strong> to fill the screen and as a button to restore a <strong>Window</strong> to its previous
38046     *  size. The minimize action collapses a <strong>Window</strong> to its title.
38047     * </p>
38048     * <p>If a non-recognized action name is supplied, it is treated as a custom action - <strong>k-icon</strong> and <strong>k-actionname</strong>
38049     * CSS classes are rendered for it and no click event handler is attached automatically. The Kendo stylesheets have a supplied icon for
38050     * actions with the name "Custom", but any name can be used. Click events can be captured and handled in a standard way:</p>
38051     *
38052     * @exampleTitle Custom actions
38053     * @example
38054     *   $("#window").kendoWindow({
38055     *       actions: ["Custom", "Minimize", "Maximize", "Close"],
38056     *       title: "Window Title"
38057     *   }).data("kendoWindow").wrapper.find(".k-custom").click(function(e) {
38058     *       alert("Custom action button clicked");
38059     *       e.preventDefault();
38060     *   });
38061     *
38062     * <h3>Positioning and Opening a Window</h3>
38063     * <p>
38064     *  In some scenarios, it is preferable to center a <strong>Window</strong> rather than open it near the HTML
38065     *  element used to define the content. It is also common to open a <strong>Window</strong> as the result of the
38066     *  action of a user rather than on the load event of a page. The <strong>Window</strong> API provides methods for
38067     *  handling these scenarios.
38068     * </p>
38069     *
38070     * @exampleTitle Centering a Window and opening on button click
38071     * @example
38072     * <div id="window">
38073     *     Content of the Window
38074     * </div>
38075     * <button id="openButton">Open Window</button>
38076     *
38077     * @exampleTitle Initialize Window, center, and configure button click action
38078     * @example
38079     * $(document).ready(function(){
38080     *     var window = $("#window").kendoWindow({
38081     *         height: "200px",
38082     *         title: "Centered Window",
38083     *         visible: false,
38084     *         width: "200px"
38085     *     }).data("kendoWindow");
38086     * });
38087     *
38088     * $("#openButton").click(function(){
38089     *     var window = $("#window").data("kendoWindow");
38090     *     window.center();
38091     *     window.open();
38092     * });
38093     *
38094     * @section
38095     * <h3>Loading Window content via AJAX</h3>
38096     * <p>
38097     *  A <strong>Window</strong>
38097 provides built-in support for asynchronously loading content from a URL. This URL
38098     *  should return a HTML fragment that can be loaded in a Window content area.
38099     * </p>
38100     *
38101     * @exampleTitle Load Window content asynchronously
38102     * @example
38103     * <div id="window"></div>
38104     *
38105     * @exampleTitle Initialize window and configure content loading
38106     * @example
38107     * $(document).ready(function(){
38108     *     $("#window").kendoWindow({
38109     *         content: "html-content-snippet.html",
38110     *         title: "Async Window Content"
38111     *     });
38112     * });
38113     *
38114     * @section
38115     * <h3>Accessing an Existing Window</h3>
38116     * <p>
38117     *  You can reference an existing <b>Window</b> instance via
38118     *  <a href="http://api.jquery.com/jQuery.data/">jQuery.data()</a>. Once a reference has been established, you can
38119     *  use the API to control its behavior.
38120     * </p>
38121     *
38122     * @exampleTitle Accessing an existing Window instance
38123     * @example
38124     * var window = $("#window").data("kendoWindow");
38125     *
38126     */
38127    var kendo = window.kendo,
38128        Widget = kendo.ui.Widget,
38129        Draggable = kendo.ui.Draggable,
38130        isPlainObject = $.isPlainObject,
38131        proxy = $.proxy,
38132        extend = $.extend,
38133        each = $.each,
38134        template = kendo.template,
38135        BODY = "body",
38136        templates,
38137        // classNames
38138        KWINDOW = ".k-window",
38139        KWINDOWTITLEBAR = ".k-window-titlebar",
38140        KWINDOWCONTENT = ".k-window-content",
38141        KWINDOWRESIZEHANDLES = ".k-resize-handle",
38142        KOVERLAY = ".k-overlay",
38143        KCONTENTFRAME = "k-content-frame",
38144        LOADING = "k-loading",
38145        KHOVERSTATE = "k-state-hover",
38146        // constants
38147        VISIBLE = ":visible",
38148        CURSOR = "cursor",
38149        // events
38150        OPEN = "open",
38151        ACTIVATE = "activate",
38152        DEACTIVATE = "deactivate",
38153        CLOSE = "close",
38154        REFRESH = "refresh",
38155        RESIZE = "resize",
38156        DRAGSTART = "dragstart",
38157        DRAGEND = "dragend",
38158        ERROR = "error",
38159        OVERFLOW = "overflow",
38160        ZINDEX = "zIndex",
38161        MINIMIZE_MAXIMIZE = ".k-window-actions .k-minimize,.k-window-actions .k-maximize",
38162        isLocalUrl = kendo.isLocalUrl;
38163
38164    function constrain(value, low, high) {
38165        return Math.max(Math.min(value, high), low);
38166    }
38167
38168    function windowObject(element) {
38169        return element.children(KWINDOWCONTENT).data("kendoWindow");
38170    }
38171
38172    function openedModalWindows() {
38173        return $(KWINDOW).filter(function() {
38174            var wnd = $(this);
38175            return wnd.is(VISIBLE) && windowObject(wnd).options.modal;
38176        }).sort(function(a, b){
38177            return +$(a).css("zIndex") - +$(b).css("zIndex");
38178        });
38179    }
38180
38181    function sizingAction(actionId, callback) {
38182        return function() {
38183            var that = this,
38184                wrapper = that.wrapper,
38185                style = wrapper[0].style,
38186                options = that.options;
38187
38188            if (options.isMaximized || options.isMinimized) {
38189                return;
38190            }
38191
38192            that.restoreOptions = {
38193                width: style.width,
38194                height: style.height
38195            };
38196
38197            wrapper
38198                .find(KWINDOWRESIZEHANDLES).hide().end()
38199                .find(MINIMIZE_MAXIMIZE).parent().hide()
38200                    .eq(0).before(templates.action({ name: "Restore" }));
38201
38202            callback.call(that);
38203
38204            return that;
38205        };
38206    }
38207
38208    var Window = Widget.extend(/** @lends kendo.ui.Window.prototype */ {
38209        /**
38210         *
38211         * @constructs
38212         * @extends kendo.ui.Widget
38213         *
38214         * @param {DomElement} element
38215         * DOM element
38216         *
38217         * @param {Object} options
38218         * Configuration options.
38219         *
38220         * @option {Boolean} [modal] <false>
38221         * Specifies whether the window should block interaction with other page elements.
38222         *
38223         * @option {Boolean} [visible] <true>
38224         * Specifies whether the window will be initially visible.
38225         *
38226         * @option {Boolean} [draggable] <true>
38227         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability for users to move/drag a
38228         * <strong>Window</strong>.
38229         *
38230         * @option {Boolean} [resizable] <true>
38231         * Enables (<strong>true</strong>) or disables (<strong>false</strong>) the ability for users to resize a
38232         * <strong>Window</strong>.
38233         *
38234         * @option {Integer}
38234 [minWidth] <50>
38235         * The minimum width (in pixels) that may be achieved by resizing the window.
38236         *
38237         * @option {Integer} [minHeight] <50>
38238         * The minimum height (in pixels) that may be achieved by resizing the window.
38239         *
38240         * @option {Integer} [maxWidth] <Infinity>
38241         * The maximum width (in pixels) that may be achieved by resizing the window.
38242         *
38243         * @option {Integer} [maxHeight] <Infinity>
38244         * The maximum height (in pixels) that may be achieved by resizing the window.
38245         *
38246         * @option {Object|String} [content]
38247         * Specifies a URL or request options that the window should load its content from. For remote URLs, a
38248         * container iframe element is automatically created.
38249         *
38250         * @option {String} [content.template]
38251         * Template for the content of a <strong>Window</strong>.
38252         *
38253         * @option {Boolean} [iframe]
38254         * Explicitly states whether content iframe should be created.
38255         *
38256         * @option {Array} [actions] <["Close"]>
38257         * The buttons for interacting with the window. Predefined array values are "Close", "Refresh", "Minimize",
38258         * and "Maximize".
38259         *
38260         * @option {String} [title]
38261         * The text in the window title bar.
38262         *
38263         * @option {Object} [appendTo] <document.body>
38264         * The element that the Window will be appended to.
38265         *
38266         * @option {Object} [animation]
38267         * A collection of {Animation} objects, used to change default animations. A value of <strong>false</strong>
38268         * will disable all animations in the widget.
38269         *
38270         * @option {Animation} [animation.open]
38271         * The animation that will be used when a Window opens.
38272         *
38273         * @option {Animation} [animation.close]
38274         * The animation that will be used when a Window closes.
38275         *
38276         */
38277        init: function(element, options) {
38278            var that = this,
38279                wrapper,
38280                offset, visibility, display,
38281                isVisible = false,
38282                content;
38283
38284            Widget.fn.init.call(that, element, options);
38285            options = that.options;
38286            element = that.element;
38287            content = options.content;
38288
38289            that.appendTo = $(options.appendTo || document.body);
38290
38291            that._animations();
38292
38293            if (!isPlainObject(content)) {
38294                content = options.content = { url: content };
38295            }
38296
38297            if (!element.parent().is(that.appendTo)) {
38298                if (element.is(VISIBLE)) {
38299                    offset = element.offset();
38300                    isVisible = true;
38301                } else {
38302                    visibility = element.css("visibility");
38303                    display = element.css("display");
38304
38305                    element.css({ visibility: "hidden", display: "" });
38306                    offset = element.offset();
38307                    element.css({ visibility: visibility, display: display });
38308                }
38309            }
38310
38311            wrapper = that.wrapper = element.closest(KWINDOW);
38312
38313            if (!element.is(".k-content") || !wrapper[0]) {
38314                element.addClass("k-window-content k-content");
38315                that._createWindow(element, options);
38316                wrapper = that.wrapper = element.closest(KWINDOW);
38317
38318                that._dimensions();
38319            }
38320
38321            if (offset) {
38322                if (isVisible) {
38323                    wrapper.css({
38324                        top: offset.top,
38325                        left: offset.left
38326                    });
38327                } else {
38328                   wrapper.css({
38329                        top: offset.top,
38330                        left: offset.left,
38331                        visibility: "visible",
38332                        display: "none"
38333                    });
38334                }
38335            }
38336
38337            if (content) {
38338                that.refresh(content);
38339            }
38340
38341            that.toFront();
38342
38343            if (options.modal) {
38344                that._overlay(wrapper.is(VISIBLE)).css({ opacity: 0.5 });
38345            }
38346
38347            wrapper.on({
38348                mouseenter: function () { $(this).addClass(KHOVERSTATE); },
38349                mouseleave: function () { $(this).removeClass(KHOVERSTATE); },
38350                click: proxy(that._windowActionHandler, that)
38351            }, ".k-window-titlebar .k-window-action");
38352
38353            if (options.resizable) {
38354                wrapper.on("dblclick", KWINDOWTITLEBAR, proxy(that.toggleMaximization, that));
38355
38356                each("n e s w se sw ne nw".split(" "), function(index, handler) {
38357                    wrapper.append(templates.resizeHandle(handler));
38358                });
38359
38360                that.resizing = new WindowResizing(that);
38361            }
38362
38363            if (options.draggable) {
38364                that.dragging = new WindowDragging(that);
38365            }
38366
38367            wrapper.add(wrapper.find(".k-resize-handle,.k-window-titlebar"))
38368                .on("mousedown", proxy(that.toFront, that));
38369
38370            that.touchScroller = kendo.touchScroller(element);
38371
38372            $(window).resize(proxy(that._onDocumentResize, that));
38373
38374            if (wrapper.is(VISIBLE)) {
38375                that.trigger(OPEN);
38376                that.trigger(ACTIVATE);
38377            }
38378
38379            kendo.notify(that);
38380        },
38381
38382        _dimensions: function() {
38383            var that = this,
38384                wrapper = that.wrapper,
38385                element = that.element,
38386                options = that.options;
38387
38388            that.title(options.title);
38389
38390            if (options.width) {
38391                wrapper.width(options.width);
38392            }
38393
38394            if (options.height) {
38395                wrapper.height(options.height);
38396            }
38397
38398            each(["minWidth","minHeight","maxWidth","maxHeight"], function(_, prop) {
38399                var value = options[prop];
38400                if (value && value != Infinity) {
38401                    element.css(prop, value);
38402                }
38403            });
38404
38405            if (!options.visible) {
38406                wrapper.hide();
38407            }
38408        },
38409
38410        _animations: function() {
38411            var options = this.options;
38412
38413            if (options.animation === false) {
38414                options.animation = { open: { show: true, effects: {} }, close: { hide:true, effects: {} } };
38415            }
38416        },
38417
38418        setOptions: function(options) {
38419            Widget.fn.setOptions.call(this, options);
38420            this._animations();
38421            this._dimensions();
38422        },
38423
38424        events:[
38425            /**
38426             *
38427             * Triggered when a Window is opened (i.e. the open() method is called).
38428             *
38429             * @name kendo.ui.Window#open
38430             * @event
38431             * @cancellable
38432             *
38433             * @param {Event} e
38434             *
38435             * @exampleTitle Attach open event handler during initialization; detach via unbind()
38436             * @example
38437             * // event handler for expand
38438             * var onOpen = function(e) {
38439             *     // ...
38440             * };
38441             *
38442             * // attach open event handler during initialization
38443             * var kendoWindow = $("#window").kendoWindow({
38444             *     open: onOpen
38445             * });
38446             *
38447             * // detach expand event handler via unbind()
38448             * kendoWindow.data("kendoWindow").unbind("open", onOpen);
38449             *
38450             * @exampleTitle Attach open event handler via bind(); detach via unbind()
38451             * @example
38452             * // event handler for open
38453             * var onOpen = function(e) {
38454             *     // ...
38455             * };
38456             *
38457             * // attach open event handler via bind()
38458             * $("#window").data("kendoWindow").bind("open", onOpen);
38459             *
38460             * // detach open event handler via unbind()
38461             * $("#window").data("kendoWindow").unbind("open", onOpen);
38462             *
38463             */
38464            OPEN,
38465            /**
38466             *
38467             * Triggered when a Window has finished its opening animation.
38468             *
38469             * @name kendo.ui.Window#activate
38470             * @event
38471             *
38472             * @param {Event} e
38473             *
38474             * @exampleTitle Attach activate event handler during initialization; detach via unbind()
38475             * @example
38476             * // event handler for activate
38477             * var onActivate = function(e) {
38478             *     // ...
38479             * };
38480             *
38481             * // attach activate event handler during initialization
38482             * var kendoWindow = $("#window").kendoWindow({
38483             *     activate: onActivate
38484             * });
38485             *
38486             * // detach activate event handler via unbind()
38487             * kendoWindow.data("kendoWindow").unbind("activate", onActivate);
38488             *
38489             * @exampleTitle Attach activate event handler via bind(); detach via unbind()
38490             * @example
38491             * // event handler for activate
38492             * var onActivate = function(e) {
38493             *     // ...
38494             * };
38495             *
38496             * // attach activate event handler via bind()
38497             * $("#window").data("kendoWindow").bind("activate", onActivate);
38498             *
38499             * // detach activate event handler via unbind()
38500             * $("#window").data("kendoWindow").unbind("activate", onActivate);
38501             *
38502             */
38503            ACTIVATE,
38504            /**
38505             *
38506             * Triggered when a Window has finished its closing animation.
38507             *
38508             * @name kendo.ui.Window#deactivate
38509             * @event
38510             *
38511             * @param {Event} e
38512             *
38513             * @exampleTitle Attach deactivate event handler during initialization; detach via unbind()
38514             * @example
38515             * // event handler for deactivate
38516             * var onDeactivate = function(e) {
38517             *     // ...
38518             * };
38519             *
38520             * // attach deactivate event handler during initialization
38521             * var kendoWindow = $("#window").kendoWindow({
38522             *     deactivate: onDeactivate
38523             * });
38524             *
38525             * // detach deactivate event handler via unbind()
38526             * kendoWindow.data("kendoWindow").unbind("deactivate", onDeactivate);
38527             *
38528             * @exampleTitle Attach deactivate event handler via bind(); detach via unbind()
38529             * @example
38530             * // event handler for deactivate
38531             * var onDeactivate = function(e) {
38532             *     // ...
38533             * };
38534             *
38535             * // attach deactivate event handler via bind()
38536             * $("#window").data("kendoWindow").bind("deactivate", onDeactivate);
38537             *
38538             * // detach deactivate event handler via unbind()
38539             * $("#window").data("kendoWindow").unbind("deactivate", onDeactivate);
38540             *
38541             */
38542            DEACTIVATE,
38543            /**
38544             *
38545             * Triggered when a Window is closed (by a user or through the close() method).
38546             *
38547             * @name kendo.ui.Window#close
38548             * @event
38549             * @cancellable
38550             *
38551             * @param {Event} e
38552             *
38553             * @exampleTitle Attach close event handler during initialization; detach via unbind()
38554             * @example
38555             * // event handler for close
38556             * var onClose = function(e) {
38557             *     // ...
38558             * };
38559             *
38560             * // attach close event handler during initialization
38561             * var kendoWindow = $("#window").kendoWindow({
38562             *     close: onClose
38563             * });
38564             *
38565             * // detach close event handler via unbind()
38566             * kendoWindow.data("kendoWindow").unbind("close", onClose);
38567             *
38568             * @exampleTitle Attach close event handler via bind(); detach via unbind()
38569             * @example
38570             * // event handler for close
38571             * var onClose = function(e) {
38572             *     // ...
38573             * };
38574             *
38575             * // attach close event handler via bind()
38576             * $("#window").data("kendoWindow").bind("close", onClose);
38577             *
38578             * // detach close event handler via unbind()
38579             * $("#window").data("kendoWindow").unbind("close", onClose);
38580             *
38581             */
38582            CLOSE,
38583            /**
38584             *
38585             * Triggered when the content of a Window have been refreshed via AJAX.
38586             *
38587             * @name kendo.ui.Window#refresh
38588             * @event
38589             *
38590             * @param {Event} e
38591             *
38592             * @exampleTitle Attach refresh event handler during initialization; detach via unbind()
38593             * @example
38594             * // event handler for refresh
38595             * var onRefresh = function(e) {
38596             *     // ...
38597             * };
38598             *
38599             * // attach refresh event handler during initialization
38600             * var kendoWindow = $("#window").kendoWindow({
38601             *     refresh: onRefresh
38602             * });
38603             *
38604             * // detach refresh event handler via unbind()
38605             * kendoWindow.data("kendoWindow").unbind("refresh", onRefresh);
38606             *
38607             * @exampleTitle Attach refresh event handler via bind(); detach via unbind()
38608             * @example
38609             * // event handler for refresh
38610             * var onRefresh = function(e) {
38611             *     // ...
38612             * };
38613             *
38614             * // attach refresh event handler via bind()
38615             * $("#window").data("kendoWindow").bind("refresh", onRefresh);
38616             *
38617             * // detach refresh event handler via unbind()
38618             * $("#window").data("kendoWindow").unbind("refresh", onRefresh);
38619             *
38620             */
38621            REFRESH,
38622            /**
38623             *
38624             * Triggered when a Window has been resized by a user.
38625             *
38626             * @name kendo.ui.Window#resize
38627             * @event
38628             *
38629             * @param {Event} e
38630             *
38631             * @exampleTitle Attach resize event handler during initialization; detach via unbind()
38632             * @example
38633             * // event handler for resize
38634             * var onResize = function(e) {
38635             *     // ...
38636             * };
38637             *
38638             * // attach resize event handler during initialization
38639             * var kendoWindow = $("#window").kendoWindow({
38640             *     resize: onResize
38641             * });
38642             *
38643             * // detach resize event handler via unbind()
38644             * kendoWindow.data("kendoWindow").unbind("resize", onResize);
38645             *
38646             * @exampleTitle Attach resize event handler via bind(); detach via unbind()
38647             * @example
38648             * // event handler for resize
38649             * var onResize = function(e) {
38650             *     // ...
38651             * };
38652             *
38653             * // attach resize event handler via bind()
38654             * $("#window").data("kendoWindow").bind("resize", onResize);
38655             *
38656             * // detach resize event handler via unbind()
38657             * $("#window").data("kendoWindow").unbind("resize", onResize);
38658             *
38659             */
38660            RESIZE,
38661
38662            /**
38663             * Triggered when the user starts to move the window.
38664             * @name kendo.ui.Window#dragstart
38665             * @event
38666             * @param {Event} e
38667             */
38668            DRAGSTART,
38669
38670            /**
38671             *
38672             * Triggered when a Window has been moved by a user.
38673             *
38674             * @name kendo.ui.Window#dragend
38675             * @event
38676             *
38677             * @param {Event} e
38678             *
38679             * @exampleTitle Attach dragEnd event handler during initialization; detach via unbind()
38680             * @example
38681             * // event handler for dragEnd
38682             * var onDragEnd = function(e) {
38683             *     // ...
38684             * };
38685             *
38686             * // attach dragEnd event handler during initialization
38687             * var kendoWindow = $("#window").kendoWindow({
38688             *     dragend: onDragEnd
38689             * });
38690             *
38691             * // detach dragEnd event handler via unbind()
38692             * kendoWindow.data("kendoWindow").unbind("dragend", onDragEnd);
38693             *
38694             * @exampleTitle Attach dragEnd event handler via bind(); detach via unbind()
38695             * @example
38696             * // event handler for dragEnd
38697             * var onDragEnd = function(e) {
38698             *     // ...
38699             * };
38700             *
38701             * // attach dragEnd event handler via bind()
38702             * $("#window").data("kendoWindow").bind("dragend", onDragEnd);
38703             *
38704             * // detach dragEnd event handler via unbind()
38705             * $("#window").data("kendoWindow").unbind("dragend", onDragEnd);
38706             *
38707             */
38708            DRAGEND,
38709
38710            /**
38711             *
38712             * Triggered when an AJAX request for content fails.
38713             *
38714             * @name kendo.ui.Window#error
38715             * @event
38716             *
38717             * @param {Event} e
38718             *
38719             * @exampleTitle Attach error event handler during initialization; detach via unbind()
38720             * @example
38721             * // event handler for error
38722             * var onError = function(e) {
38723             *     // ...
38724             * };
38725             *
38726             * // attach dragEnd event handler during initialization
38727             * var kendoWindow = $("#window").kendoWindow({
38728             *     error: onError
38729             * });
38730             *
38731             * // detach error event handler via unbind()
38732             * kendoWindow.data("kendoWindow").unbind("error", onError);
38733             *
38734             * @exampleTitle Attach error event handler via bind(); detach via unbind()
38735             * @example
38736             * // event handler for error
38737             * var onError = function(e) {
38738             *     // ...
38739             * };
38740             *
38741             * // attach error event handler via bind()
38742             * $("#window").data("kendoWindow").bind("error", onError);
38743             *
38744             * // detach error event handler via unbind()
38745             * $("#window").data("kendoWindow").unbind("error", onError);
38746             *
38747             */
38748            ERROR
38749        ],
38750
38751        options: {
38752            name: "Window",
38753            animation: {
38754                open: {
38755                    effects: { zoom: { direction: "in" }, fade: { direction: "in" } },
38756                    duration: 350,
38757                    show: true
38758                },
38759                close: {
38760                    effects: { zoom: { direction: "out", properties: { scale: 0.7 } }, fade: { direction: "out" } },
38761                    duration: 350,
38762                    hide: true
38763                }
38764            },
38765            title: "",
38766            actions: ["Close"],
38767            modal: false,
38768            resizable: true,
38769            draggable: true,
38770            minWidth: 90,
38771            minHeight: 50,
38772            maxWidth: Infinity,
38773            maxHeight: Infinity,
38774            visible: true
38775        },
38776
38777        _overlay: function (visible) {
38778            var overlay = this.appendTo.children(".k-overlay"),
38779                wrapper = this.wrapper;
38780
38781            if (!overlay.length) {
38782                overlay = $("<div class='k-overlay' />");
38783            }
38784
38785            overlay
38786                .insertBefore(wrapper[0])
38787                .toggle(visible)
38788                .css(ZINDEX, parseInt(wrapper.css(ZINDEX), 10) - 1);
38789
38790            return overlay;
38791        },
38792
38793        _windowActionHandler: function (e) {
38794            var target = $(e.target).closest(".k-window-action").find(".k-icon"),
38795                that = this;
38796
38797            each({
38798                "k-close": that.close,
38799                "k-maximize": that.maximize,
38800                "k-minimize": that.minimize,
38801                "k-restore": that.restore,
38802                "k-refresh": that.refresh
38803            }, function (commandName, handler) {
38804                if (target.hasClass(commandName)) {
38805                    e.preventDefault();
38806                    handler.call(that);
38807                    return false;
38808                }
38809            });
38810        },
38811
38812        /**
38813         *
38814         * Centers a <strong>Window</strong> within the viewport.
38815         *
38816         * @returns {Window}
38817         * Returns the (Kendo UI) Window object to support chaining.
38818         *
38819         * @example
38820         * var kendoWindow = $("#window").data("kendoWindow");
38821         * kendoWindow.center();
38822         *
38823         */
38824        center: function () {
38825            var wrapper = this.wrapper,
38826                documentWindow = $(window);
38827
38828            wrapper.css({
38829                left: documentWindow.scrollLeft() + Math.max(0, (documentWindow.width() - wrapper.width()) / 2),
38830                top: documentWindow.scrollTop() + Math.max(0, (documentWindow.height() - wrapper.height()) / 2)
38831            });
38832
38833            return this;
38834        },
38835
38836        /**
38837         *
38838         * Gets or set the title of a <strong>Window</strong>.
38839         *
38840         * @param {String} [text]
38841         * The title of the Window.
38842         *
38843         * @returns {Window}
38844         * If a title is provided, this method will return the (Kendo UI) Window object to support chaining. Otherwise,
38845         * it will return the current title of the (Kendo UI) Window.
38846         *
38847         * @exampleTitle Get the existing title of the Window
38848         * @example
38849         * var kendoWindow = $("#window").data("kendoWindow");
38850         * var windowTitle = kendoWindow.title();
38851         *
38852         * @exampleTitle Set the title of a Window; utilize chaining (if necessary)
38853         * @example
38854         * var kendoWindow = $("#window").data("kendoWindow").title("Do a barrel roll!");
38855         *
38856         */
38857        title: function (text) {
38858            var that = this,
38859                wrapper = that.wrapper,
38860                options = that.options,
38861                titleBar = wrapper.find(KWINDOWTITLEBAR),
38862                title = titleBar.children(".k-window-title"),
38863                titleBarHeight = titleBar.outerHeight();
38864
38865            if (!arguments.length) {
38866                return title.text();
38867            }
38868
38869            if (text === false) {
38870                wrapper.addClass("k-window-titleless");
38871                titleBar.remove();
38872            } else {
38873                if (!titleBar.length) {
38874                    wrapper.prepend(templates.titlebar(extend(templates, options)));
38875                }
38876
38877                wrapper.css("padding-top", titleBarHeight);
38878                titleBar.css("margin-top", -titleBarHeight);
38879            }
38880
38881            title.text(text);
38882
38883            return that;
38884        },
38885
38886        /**
38887         *
38888         * Gets or set the content of a <strong>Window</strong>.
38889         *
38890         * @param {String} [content]
38891         * The content of the Window.
38892         *
38893         * @returns {Window}
38894         * If content is provided, this method will return the (Kendo UI) Window object to support chaining. Otherwise,
38895         * it will return the current content of the (Kendo UI) Window.
38896         *
38897         * @exampleTitle Get the existing content of the Window
38898         * @example
38899         * var kendoWindow = $("#window").data("kendoWindow");
38900         * var windowContent = kendoWindow.content();
38901         *
38902         * @exampleTitle Set the title of a Window; utilize chaining (if necessary)
38903         * @example
38904         * var kendoWindow = $("#window").data("kendoWindow").content("Kendo UI for all the things!");
38905         *
38906         */
38907        content: function (html) {
38908            var content = this.wrapper.children(KWINDOWCONTENT);
38909
38910            if (!html) {
38911                return content.html();
38912            }
38913
38914            content.html(html);
38915            return this;
38916        },
38917
38918        /**
38919         *
38920         * Opens a Window.
38921         *
38922         * @returns {Window}
38923         * Returns the (Kendo UI) Window object to support chaining.
38924         *
38925         * @exampleTitle Open a Window; utilize chaining (if necessary)
38926         * @example
38927         * var kendoWindow = $("#window").data("kendoWindow").open();
38928         *
38929         */
38930        open: function () {
38931            var that = this,
38932                wrapper = that.wrapper,
38933                showOptions = that.options.animation.open,
38934                contentElement = wrapper.children(KWINDOWCONTENT),
38935                initialOverflow = contentElement.css(OVERFLOW);
38936
38937            if (!that.trigger(OPEN)) {
38938                that.toFront();
38939
38940                if (that.options.modal) {
38941                    var overlay = that._overlay(false);
38942
38943                    if (showOptions.duration) {
38944                        overlay.kendoStop().kendoAnimate({
38945                            effects: { fade: { direction: "out", properties: { opacity: 0.5 } } },
38946                            duration: showOptions.duration,
38947                            show: true
38948                        });
38949                    } else {
38950                        overlay.css("opacity", 0.5).show();
38951                    }
38952                }
38953
38954                if (!wrapper.is(VISIBLE)) {
38955                    contentElement.css(OVERFLOW, "hidden");
38956                    wrapper.show().kendoStop().kendoAnimate({
38957                        effects: showOptions.effects,
38958                        duration: showOptions.duration,
38959                        complete: function() {
38960                            that.trigger(ACTIVATE);
38961                            contentElement.css(OVERFLOW, initialOverflow);
38962                        }
38963                    });
38964                }
38965            }
38966
38967            if (that.options.isMaximized) {
38968               $("html, body").css(OVERFLOW, "hidden");
38969            }
38970
38971            return that;
38972        },
38973
38974        /**
38975         *
38976         * Closes a Window.
38977         *
38978         * @returns {Window}
38979         * Returns the (Kendo UI) Window object to support chaining.
38980         *
38981         * @exampleTitle Close a Window; utilize chaining (if necessary)
38982         * @example
38983         * var kendoWindow = $("#window").data("kendoWindow").close();
38984         *
38985         */
38986        close: function () {
38987            var that = this,
38988                wrapper = that.wrapper,
38989                options = that.options,
38990                showOptions = options.animation.open,
38991                hideOptions = options.animation.close,
38992                modalWindows,
38993                shouldHideOverlay, overlay;
38994
38995            if (wrapper.is(VISIBLE) && !that.trigger(CLOSE)) {
38996                modalWindows = openedModalWindows();
38997
38998                shouldHideOverlay = options.modal && modalWindows.length == 1;
38999
39000                overlay = options.modal ? that._overlay(true) : $(undefined);
39001
39002                if (shouldHideOverlay) {
39003                    if (hideOptions.duration) {
39004                        overlay.kendoStop().kendoAnimate({
39005                             effects: { fadeOut: { properties: { opacity: 0 } } },
39006                             duration: hideOptions.duration,
39007                             hide: true
39008                         });
39009                    } else {
39010                        overlay.hide();
39011                    }
39012                } else if (modalWindows.length) {
39013                    windowObject(modalWindows.eq(modalWindows.length - 2))._overlay(true);
39014                }
39015
39016                wrapper.kendoStop().kendoAnimate({
39017                    effects: hideOptions.effects || showOptions.effects,
39018                    reverse: hideOptions.reverse === true,
39019                    duration: hideOptions.duration,
39020                    complete: function() {
39021                        wrapper.hide();
39022                        that.trigger(DEACTIVATE);
39023                    }
39024                });
39025            }
39026
39027            if (that.options.isMaximized) {
39028                $("html, body").css(OVERFLOW, "");
39029            }
39030
39031            return that;
39032        },
39033
39034        /**
39035         *
39036         * Brings forward a Window to the top of the z-index.
39037         *
39038         * @returns {Window}
39039         * Returns the (Kendo UI) Window object to support chaining.
39040         *
39041         * @exampleTitle Bring forward a Window; utilize chaining (if necessary)
39042         * @example
39043         * var kendoWindow = $("#window").data("kendoWindow").toFront();
39044         *
39045         */
39046        toFront: function () {
39047            var that = this,
39048                wrapper = that.wrapper,
39049                currentWindow = wrapper[0],
39050                zIndex = +wrapper.css(ZINDEX),
39051                originalZIndex = zIndex;
39052
39053            $(KWINDOW).each(function(i, element) {
39054                var windowObject = $(element),
39055                    zIndexNew = windowObject.css(ZINDEX),
39056                    contentElement = windowObject.find(KWINDOWCONTENT);
39057
39058                if (!isNaN(zIndexNew)) {
39059                    zIndex = Math.max(+zIndexNew, zIndex);
39060                }
39061
39062                // Add overlay to windows with iframes and lower z-index to prevent
39063                // trapping of events when resizing / dragging
39064                if (element != currentWindow && contentElement.find("> ." + KCONTENTFRAME).length > 0) {
39065                    contentElement.append(templates.overlay);
39066                }
39067            });
39068
39069            if (zIndex == 10001 || originalZIndex < zIndex) {
39070                wrapper.css(ZINDEX, zIndex + 2);
39071                that.element.find("> .k-overlay").remove();
39072            }
39073
39074            return that;
39075        },
39076
39077        /**
39078         *
39079         * Toggles a Window between a maximized and restored state.
39080         *
39081         * @returns {Window}
39082         * Returns the (Kendo UI) Window object to support chaining.
39083         *
39084         * @exampleTitle Toggle the state of a Window; utilize chaining (if necessary)
39085         * @example
39086         * var kendoWindow = $("#window").data("kendoWindow").toggleMaximization();
39087         *
39088         */
39089        toggleMaximization: function () {
39090            return this[this.options.isMaximized ? "restore" : "maximize"]();
39091        },
39092
39093        /**
39094         *
39095         * Restores a maximized or minimized Window to its previous state.
39096         *
39097         * @returns {Window}
39098         * Returns the (Kendo UI) Window object to support chaining.
39099         *
39100         * @exampleTitle Restore the state of a Window; utilize chaining (if necessary)
39101         * @example
39102         * var kendoWindow = $("#window").data("kendoWindow").restore();
39103         *
39104         */
39105        restore: function () {
39106            var that = this,
39107                options = that.options,
39108                restoreOptions = that.restoreOptions;
39109
39110            if (!options.isMaximized && !options.isMinimized) {
39111                return;
39112            }
39113
39114            that.wrapper
39115                .css({
39116                    position: "absolute",
39117                    left: restoreOptions.left,
39118                    top: restoreOptions.top,
39119                    width: restoreOptions.width,
39120                    height: restoreOptions.height
39121                })
39122                .find(".k-window-content,.k-resize-handle").show().end()
39123                .find(".k-window-titlebar .k-restore").parent().remove().end().end()
39124                .find(MINIMIZE_MAXIMIZE).parent().show();
39125
39126            $("html, body").css(OVERFLOW, "");
39127
39128            options.isMaximized = options.isMinimized = false;
39129
39130            that.trigger(RESIZE);
39131
39132            return that;
39133        },
39134
39135        /**
39136         *
39137         * Maximizes a Window to the entire viewing area of the user agent.
39138         *
39139         * @function
39140         * @returns {Window}
39141         * Returns the (Kendo UI) Window object to support chaining.
39142         *
39143         * @exampleTitle Maximize a Window
39144         * @example
39145         * $("#window").data("kendoWindow").maximize();
39146         *
39147         */
39148        maximize: sizingAction("maximize", function() {
39149            var that = this,
39150                wrapper = that.wrapper,
39151                position = wrapper.position();
39152
39153            extend(that.restoreOptions, {
39154                left: position.left,
39155                top: position.top
39156            });
39157
39158            wrapper.css({
39159                    left: 0,
39160                    top: 0,
39161                    position: "fixed"
39162                });
39163
39164            $("html, body").css(OVERFLOW, "hidden");
39165
39166            that.options.isMaximized = true;
39167
39168            that._onDocumentResize();
39169        }),
39170
39171        /**
39172         *
39173         * Maximizes a Window to its title bar.
39174         *
39175         * @function
39176         * @returns {Window}
39177         * Returns the (Kendo UI) Window object to support chaining.
39178         *
39179         * @exampleTitle Minimize a Window
39180         * @example
39181         * $("#window").data("kendoWindow").minimize();
39182         *
39183         */
39184        minimize: sizingAction("minimize", function() {
39185            var that = this;
39186
39187            that.wrapper.css("height", "");
39188            that.element.hide();
39189
39190            that.options.isMinimized = true;
39191        }),
39192
39193        _onDocumentResize: function () {
39194            var that = this,
39195                wrapper = that.wrapper,
39196                wnd = $(window);
39197
39198            if (!that.options.isMaximized) {
39199                return;
39200            }
39201
39202            wrapper.css({
39203                    width: wnd.width(),
39204                    height: wnd.height() - parseInt(wrapper.css("padding-top"), 10)
39205                });
39206
39207            that.trigger(RESIZE);
39208        },
39209
39210        /**
39211         *
39212         * Refreshes the content of a Window from a remote URL.
39213         *
39214         * @param {Object|String} options
39215         * Options for requesting data from the server.
39216         * If omitted, the window uses the <code>content</code> property
39217         * that was supplied when the window was created.
39218         * Any options specified here are passed to jQuery.ajax().
39219         *
39220         * @param {String} options.url
39221         * The server URL that will be requested.
39222         *
39223         * @param {Object} options.data
39224         * A JSON object containing the data that will be passed to the server.
39225         *
39226         * @param {String} options.type
39227         * The HTTP request method ("GET", "POST").
39228         *
39229         * @param {String} options.template
39230         * A template to be used for displaying the requested data.
39231         *
39232         * @returns {Window}
39233         * Returns the (Kendo UI) Window object to support chaining.
39234         *
39235         * @example
39236         * var windowObject = $("#window").data("kendoWindow");
39237         * windowObject.refresh("/feedbackForm");
39238         *
39239         * windowObject.refresh({
39240         *     url: "/feedbackForm",
39241         *     data: { userId: 42 }
39242         * });
39243         *
39244         * windowObject.refresh({
39245         *     url: "/userInfo",
39246         *     data: { userId: 42 },
39247         *     template: "Hello, #= firstName # #= lastName #"
39248         * });
39249         *
39250         */
39251        refresh: function (options) {
39252            var that = this,
39253                initOptions = that.options,
39254                element = $(that.element),
39255                iframe,
39256                showIframe = initOptions.iframe,
39257                url;
39258
39259            if (!isPlainObject(options)) {
39260                options = { url: options };
39261            }
39262
39263            options = extend({}, initOptions.content, options);
39264
39265            url = options.url;
39266
39267            if (url) {
39268                if (typeof showIframe == "undefined") {
39269                    showIframe = !isLocalUrl(url);
39270                }
39271
39272                if (!showIframe) {
39273                    // perform AJAX request
39274                    that._ajaxRequest(options);
39275                } else {
39276                    iframe = element.find("." + KCONTENTFRAME)[0];
39277
39278                    if (iframe) {
39279                        // refresh existing iframe
39280                        iframe.src = url || iframe.src;
39281                    } else {
39282                        // render new iframe
39283                        element.html(templates.contentFrame(extend({}, initOptions, { content: options })));
39284                    }
39285                }
39286            } else if (options.template) {
39287                // refresh template
39288                that.content(template(options.template)({}));
39289            }
39290
39291            return that;
39292        },
39293
39294        _ajaxRequest: function (options) {
39295            var that = this,
39296                contentTemplate = options.template,
39297                refreshIcon = that.wrapper.find(".k-window-titlebar .k-refresh"),
39298                loadingIconTimeout = setTimeout(function () {
39299                    refreshIcon.addClass(LOADING);
39300                }, 100);
39301
39302            $.ajax(extend({
39303                type: "GET",
39304                dataType: "html",
39305                cache: false,
39306                error: proxy(function (xhr, status) {
39307                    that.trigger(ERROR);
39308                }, that),
39309                complete: function () {
39310                    clearTimeout(loadingIconTimeout);
39311                    refreshIcon.removeClass(LOADING);
39312                },
39313                success: proxy(function (data, textStatus) {
39314                    if (contentTemplate) {
39315                        data = template(contentTemplate)(data || {});
39316                    }
39317
39318                    that.element.html(data);
39319
39320                    that.trigger(REFRESH);
39321                }, that)
39322            }, options));
39323        },
39324
39325        /**
39326         * Destroys the window and its modal overlay, if necessary. Useful for removing modal windows.
39327         */
39328        destroy: function () {
39329            var that = this,
39330                modalWindows,
39331                shouldHideOverlay;
39332
39333            that.wrapper.remove();
39334
39335            modalWindows = openedModalWindows();
39336
39337            shouldHideOverlay = that.options.modal && !modalWindows.length;
39338
39339            if (shouldHideOverlay) {
39340                that._overlay(false).remove();
39341            } else if (modalWindows.length > 0) {
39342                windowObject(modalWindows.eq(modalWindows.length - 2))._overlay(true);
39343            }
39344        },
39345
39346        _createWindow: function() {
39347            var that = this,
39348                contentHtml = that.element,
39349                options = that.options,
39350                iframeSrcAttributes,
39351                wrapper;
39352
39353            if (options.scrollable === false) {
39354                contentHtml.attr("style", "overflow:hidden;");
39355            }
39356
39357            if (options.iframe) {
39358                contentHtml.html(templates.contentFrame(options));
39359            }
39360
39361            wrapper = $(templates.wrapper(options));
39362
39363            if (options.title !== false) {
39364                wrapper.append(templates.titlebar(extend(templates, options)));
39365            }
39366
39367            wrapper.toggleClass("k-rtl", !!that.element.closest(".k-rtl").length);
39368
39369            // Collect the src attributes of all iframes and then set them to empty string.
39370            // This seems to fix this IE9 "feature": http://msdn.microsoft.com/en-us
39370/library/gg622929%28v=VS.85%29.aspx?ppud=4
39371            iframeSrcAttributes = contentHtml.find("iframe").map(function(iframe) {
39372                var src = this.getAttribute("src");
39373                this.src = "";
39374                return src;
39375            });
39376
39377            // Make sure the wrapper is appended to the body only once. IE9+ will throw exceptions if you move iframes in DOM
39378            wrapper
39379                .appendTo(that.appendTo)
39380                .append(contentHtml)
39381                .find("iframe").each(function(index) {
39382                   // Restore the src attribute of the iframes when they are part of the live DOM tree
39383                   this.src = iframeSrcAttributes[index];
39384                });
39385        }
39386    });
39387
39388    templates = {
39389        wrapper: template("<div class='k-widget k-window' />"),
39390        action: template(
39391            "<a href='\\#' class='k-window-action k-link'>" +
39392                "<span class='k-icon k-#= name.toLowerCase() #'>#= name #</span>" +
39393            "</a>"
39394        ),
39395        titlebar: template(
39396            "<div class='k-window-titlebar k-header'>&nbsp;" +
39397                "<span class='k-window-title'>#= title #</span>" +
39398                "<div class='k-window-actions k-header'>" +
39399                "# for (var i = 0; i < actions.length; i++) { #" +
39400                    "#= action({ name: actions[i] }) #" +
39401                "# } #" +
39402                "</div>" +
39403            "</div>"
39404        ),
39405        overlay: "<div class='k-overlay' />",
39406        contentFrame: template(
39407            "<iframe src='#= content.url #' title='#= title #' frameborder='0'" +
39408                " class='" + KCONTENTFRAME + "'>" +
39409                    "This page requires frames in order to show content" +
39410            "</iframe>"
39411        ),
39412        resizeHandle: template("<div class='k-resize-handle k-resize-#= data #'></div>")
39413    };
39414
39415
39416    function WindowResizing(wnd) {
39417        var that = this;
39418
39419        that.owner = wnd;
39420        that._draggable = new Draggable(wnd.wrapper, {
39421            filter: KWINDOWRESIZEHANDLES,
39422            group: wnd.wrapper.id + "-resizing",
39423            dragstart: proxy(that.dragstart, that),
39424            drag: proxy(that.drag, that),
39425            dragend: proxy(that.dragend, that)
39426        });
39427    }
39428
39429    WindowResizing.prototype = /** @ignore */ {
39430        dragstart: function (e) {
39431            var that = this,
39432                wnd = that.owner,
39433                wrapper = wnd.wrapper;
39434
39435            that.elementPadding = parseInt(wnd.wrapper.css("padding-top"), 10);
39436            that.initialCursorPosition = wrapper.offset();
39437
39438            that.resizeDirection = e.currentTarget.prop("className").replace("k-resize-handle k-resize-", "").split("");
39439
39440            that.initialSize = {
39441                width: wrapper.width(),
39442                height: wrapper.height()
39443            };
39444
39445            that.containerOffset = wnd.appendTo.offset(),
39446
39447            wrapper
39448                .append(templates.overlay)
39449                .find(KWINDOWRESIZEHANDLES).not(e.currentTarget).hide();
39450
39451            $(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
39452        },
39453        drag: function (e) {
39454            var that = this,
39455                wnd = that.owner,
39456                wrapper = wnd.wrapper,
39457                options = wnd.options,
39458                direction = that.resizeDirection,
39459                containerOffset = that.containerOffset,
39460                initialPosition = that.initialCursorPosition,
39461                initialSize = that.initialSize,
39462                newWidth, newHeight,
39463                windowBottom, windowRight,
39464                x = e.x.location,
39465                y = e.y.location;
39466
39467            if (direction.indexOf("e") >= 0) {
39468                newWidth = x - initialPosition.left;
39469
39470                wrapper.width(constrain(newWidth, options.minWidth, options.maxWidth));
39471            } else if (direction.indexOf("w") >= 0) {
39472                windowRight = initialPosition.left + initialSize.width;
39473                newWidth = constrain(windowRight - x, options.minWidth, options.maxWidth);
39474
39475                wrapper.css({
39476                    left: windowRight - newWidth - containerOffset.left,
39477                    width: newWidth
39478                });
39479            }
39480
39481            if (direction.indexOf("s") >= 0) {
39482                newHeight = y - initialPosition.top - that.elementPadding;
39483
39484                wrapper.height(constrain(newHeight, options.minHeight, options.maxHeight));
39485            } else if (direction.indexOf("n") >= 0) {
39486                windowBottom = initialPosition.top + initialSize.height;
39487                newHeight = constrain(windowBottom - y, options.minHeight, options.maxHeight);
39488
39489                wrapper.css({
39490                    top: windowBottom - newHeight - containerOffset.top,
39491                    height: newHeight
39492                });
39493            }
39494
39495            wnd.trigger(RESIZE);
39496        },
39497        dragend: function (e) {
39498            var that = this,
39499                wnd = that.owner,
39500                wrapper = wnd.wrapper;
39501
39502            wrapper
39503                .find(KOVERLAY).remove().end()
39504                .find(KWINDOWRESIZEHANDLES).not(e.currentTarget).show();
39505
39506            $(BODY).css(CURSOR, "");
39507
39508            if (wnd.touchScroller) {
39509               wnd.touchScroller.reset();
39510            }
39511            if (e.keyCode == 27) {
39512                wrapper.css(that.initialCursorPosition)
39513                    .css(that.initialSize);
39514            }
39515
39516            return false;
39517        }
39518    };
39519
39520    function WindowDragging(wnd) {
39521        var that = this;
39522
39523        that.owner = wnd;
39524        that._draggable = new Draggable(wnd.wrapper, {
39525            filter: KWINDOWTITLEBAR,
39526            group: wnd.wrapper.id + "-moving",
39527            dragstart: proxy(that.dragstart, that),
39528            drag: proxy(that.drag, that),
39529            dragend: proxy(that.dragend, that),
39530            dragcancel: proxy(that.dragcancel, that)
39531        });
39532    }
39533
39534    WindowDragging.prototype = /** @ignore */{
39535        dragstart: function (e) {
39536            var wnd = this.owner,
39537                element = wnd.element,
39538                actions = element.find(".k-window-actions"),
39539                containerOffset = wnd.appendTo.offset();
39540
39541            wnd.trigger(DRAGSTART);
39542
39543            wnd.initialWindowPosition = wnd.wrapper.position();
39544
39545            wnd.startPosition = {
39546                left: e.x.client - wnd.initialWindowPosition.left,
39547                top: e.y.client - wnd.initialWindowPosition.top
39548            };
39549
39550            if (actions.length > 0) {
39551                wnd.minLeftPosition = actions.outerWidth() + parseInt(actions.css("right"), 10) - element.outerWidth();
39552            } else {
39553                wnd.minLeftPosition =  20 - element.outerWidth(); // at least 20px remain visible
39554            }
39555
39556            wnd.minLeftPosition -= containerOffset.left;
39557            wnd.minTopPosition = -containerOffset.top;
39558
39559            wnd.wrapper
39560                .append(templates.overlay)
39561                .find(KWINDOWRESIZEHANDLES).hide();
39562
39563            $(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
39564        },
39565
39566        drag: function (e) {
39567            var wnd = this.owner,
39568                coordinates = {
39569                    left: Math.max(e.x.client - wnd.startPosition.left, wnd.minLeftPosition),
39570                    top: Math.max(e.y.client - wnd.startPosition.top, wnd.minTopPosition)
39571                };
39572
39573            $(wnd.wrapper).css(coordinates);
39574        },
39575
39576        dragcancel: function (e) {
39577            var wnd = this.owner;
39578
39579            wnd.wrapper
39580                .find(KWINDOWRESIZEHANDLES).show().end()
39581                .find(KOVERLAY).remove();
39582
39583            $(BODY).css(CURSOR, "");
39584
39585            e.currentTarget.closest(KWINDOW).css(wnd.initialWindowPosition);
39586        },
39587
39588        dragend: function (e) {
39589            var wnd = this.owner;
39590
39591            wnd.wrapper
39592                .find(KWINDOWRESIZEHANDLES).show().end()
39593                .find(KOVERLAY).remove();
39594
39595            $(BODY).css(CURSOR, "");
39596
39597            wnd.trigger(DRAGEND);
39598
39599            return false;
39600        }
39601    };
39602
39603    kendo.ui.plugin(Window);
39604
39605})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.