Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / builds / ext-all-sandbox-debug.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 (function(Ext){
16 if (typeof Ext === 'undefined') {
17 this.Ext = {};
18 }
19
20 Ext.buildSettings = {"baseCSSPrefix":"x4-","scopeResetCSS":true};
21 Ext.isSandboxed = true;
22 /*
23
24 This file is part of Ext JS 4
25
26 Copyright (c) 2011 Sencha Inc
27
28 Contact:  http://www.sencha.com/contact
29
30 GNU General Public License Usage
31 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
32
33 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
34
35 */
36 /**
37  * @class Ext
38  * @singleton
39  */
40 (function() {
41     var global = this,
42         objectPrototype = Object.prototype,
43         toString = objectPrototype.toString,
44         enumerables = true,
45         enumerablesTest = { toString: 1 },
46         i;
47
48     if (typeof Ext === 'undefined') {
49         global.Ext = {};
50     }
51
52     Ext.global = global;
53
54     for (i in enumerablesTest) {
55         enumerables = null;
56     }
57
58     if (enumerables) {
59         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
60                        'toLocaleString', 'toString', 'constructor'];
61     }
62
63     /**
64      * An array containing extra enumerables for old browsers
65      * @property {String[]}
66      */
67     Ext.enumerables = enumerables;
68
69     /**
70      * Copies all the properties of config to the specified object.
71      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
72      * {@link Ext.Object#merge} instead.
73      * @param {Object} object The receiver of the properties
74      * @param {Object} config The source of the properties
75      * @param {Object} defaults A different object that will also be applied for default values
76      * @return {Object} returns obj
77      */
78     Ext.apply = function(object, config, defaults) {
79         if (defaults) {
80             Ext.apply(object, defaults);
81         }
82
83         if (object && config && typeof config === 'object') {
84             var i, j, k;
85
86             for (i in config) {
87                 object[i] = config[i];
88             }
89
90             if (enumerables) {
91                 for (j = enumerables.length; j--;) {
92                     k = enumerables[j];
93                     if (config.hasOwnProperty(k)) {
94                         object[k] = config[k];
95                     }
96                 }
97             }
98         }
99
100         return object;
101     };
102
103     Ext.buildSettings = Ext.apply({
104         baseCSSPrefix: 'x-',
105         scopeResetCSS: false
106     }, Ext.buildSettings || {});
107
108     Ext.apply(Ext, {
109         /**
110          * A reusable empty function
111          */
112         emptyFn: function() {},
113
114         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
115
116         /**
117          * Copies all the properties of config to object if they don't already exist.
118          * @param {Object} object The receiver of the properties
119          * @param {Object} config The source of the properties
120          * @return {Object} returns obj
121          */
122         applyIf: function(object, config) {
123             var property;
124
125             if (object) {
126                 for (property in config) {
127                     if (object[property] === undefined) {
128                         object[property] = config[property];
129                     }
130                 }
131             }
132
133             return object;
134         },
135
136         /**
137          * Iterates either an array or an object. This method delegates to
138          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
139          *
140          * @param {Object/Array} object The object or array to be iterated.
141          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
142          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
143          * type that is being iterated.
144          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
145          * Defaults to the object being iterated itself.
146          * @markdown
147          */
148         iterate: function(object, fn, scope) {
149             if (Ext.isEmpty(object)) {
150                 return;
151             }
152
153             if (scope === undefined) {
154                 scope = object;
155             }
156
157             if (Ext.isIterable(object)) {
158                 Ext.Array.each.call(Ext.Array, object, fn, scope);
159             }
160             else {
161                 Ext.Object.each.call(Ext.Object, object, fn, scope);
162             }
163         }
164     });
165
166     Ext.apply(Ext, {
167
168         /**
169          * This method deprecated. Use {@link Ext#define Ext.define} instead.
170          * @method
171          * @param {Function} superclass
172          * @param {Object} overrides
173          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
174          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
175          */
176         extend: function() {
177             // inline overrides
178             var objectConstructor = objectPrototype.constructor,
179                 inlineOverrides = function(o) {
180                 for (var m in o) {
181                     if (!o.hasOwnProperty(m)) {
182                         continue;
183                     }
184                     this[m] = o[m];
185                 }
186             };
187
188             return function(subclass, superclass, overrides) {
189                 // First we check if the user passed in just the superClass with overrides
190                 if (Ext.isObject(superclass)) {
191                     overrides = superclass;
192                     superclass = subclass;
193                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
194                         superclass.apply(this, arguments);
195                     };
196                 }
197
198
199                 // We create a new temporary class
200                 var F = function() {},
201                     subclassProto, superclassProto = superclass.prototype;
202
203                 F.prototype = superclassProto;
204                 subclassProto = subclass.prototype = new F();
205                 subclassProto.constructor = subclass;
206                 subclass.superclass = superclassProto;
207
208                 if (superclassProto.constructor === objectConstructor) {
209                     superclassProto.constructor = superclass;
210                 }
211
212                 subclass.override = function(overrides) {
213                     Ext.override(subclass, overrides);
214                 };
215
216                 subclassProto.override = inlineOverrides;
217                 subclassProto.proto = subclassProto;
218
219                 subclass.override(overrides);
220                 subclass.extend = function(o) {
221                     return Ext.extend(subclass, o);
222                 };
223
224                 return subclass;
225             };
226         }(),
227
228         /**
229          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
230
231     Ext.define('My.cool.Class', {
232         sayHi: function() {
233             alert('Hi!');
234         }
235     }
236
237     Ext.override(My.cool.Class, {
238         sayHi: function() {
239             alert('About to say...');
240
241             this.callOverridden();
242         }
243     });
244
245     var cool = new My.cool.Class();
246     cool.sayHi(); // alerts 'About to say...'
247                   // alerts 'Hi!'
248
249          * Please note that `this.callOverridden()` only works if the class was previously
250          * created with {@link Ext#define)
251          *
252          * @param {Object} cls The class to override
253          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
254          * containing one or more methods.
255          * @method override
256          * @markdown
257          */
258         override: function(cls, overrides) {
259             if (cls.prototype.$className) {
260                 return cls.override(overrides);
261             }
262             else {
263                 Ext.apply(cls.prototype, overrides);
264             }
265         }
266     });
267
268     // A full set of static methods to do type checking
269     Ext.apply(Ext, {
270
271         /**
272          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
273          * value (second argument) otherwise.
274          *
275          * @param {Object} value The value to test
276          * @param {Object} defaultValue The value to return if the original value is empty
277          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
278          * @return {Object} value, if non-empty, else defaultValue
279          */
280         valueFrom: function(value, defaultValue, allowBlank){
281             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
282         },
283
284         /**
285          * Returns the type of the given variable in string format. List of possible values are:
286          *
287          * - `undefined`: If the given value is `undefined`
288          * - `null`: If the given value is `null`
289          * - `string`: If the given value is a string
290          * - `number`: If the given value is a number
291          * - `boolean`: If the given value is a boolean value
292          * - `date`: If the given value is a `Date` object
293          * - `function`: If the given value is a function reference
294          * - `object`: If the given value is an object
295          * - `array`: If the given value is an array
296          * - `regexp`: If the given value is a regular expression
297          * - `element`: If the given value is a DOM Element
298          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
299          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
300          *
301          * @param {Object} value
302          * @return {String}
303          * @markdown
304          */
305         typeOf: function(value) {
306             if (value === null) {
307                 return 'null';
308             }
309
310             var type = typeof value;
311
312             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
313                 return type;
314             }
315
316             var typeToString = toString.call(value);
317
318             switch(typeToString) {
319                 case '[object Array]':
320                     return 'array';
321                 case '[object Date]':
322                     return 'date';
323                 case '[object Boolean]':
324                     return 'boolean';
325                 case '[object Number]':
326                     return 'number';
327                 case '[object RegExp]':
328                     return 'regexp';
329             }
330
331             if (type === 'function') {
332                 return 'function';
333             }
334
335             if (type === 'object') {
336                 if (value.nodeType !== undefined) {
337                     if (value.nodeType === 3) {
338                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
339                     }
340                     else {
341                         return 'element';
342                     }
343                 }
344
345                 return 'object';
346             }
347
348         },
349
350         /**
351          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
352          *
353          * - `null`
354          * - `undefined`
355          * - a zero-length array
356          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
357          *
358          * @param {Object} value The value to test
359          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
360          * @return {Boolean}
361          * @markdown
362          */
363         isEmpty: function(value, allowEmptyString) {
364             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
365         },
366
367         /**
368          * Returns true if the passed value is a JavaScript Array, false otherwise.
369          *
370          * @param {Object} target The target to test
371          * @return {Boolean}
372          * @method
373          */
374         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
375             return toString.call(value) === '[object Array]';
376         },
377
378         /**
379          * Returns true if the passed value is a JavaScript Date object, false otherwise.
380          * @param {Object} object The object to test
381          * @return {Boolean}
382          */
383         isDate: function(value) {
384             return toString.call(value) === '[object Date]';
385         },
386
387         /**
388          * Returns true if the passed value is a JavaScript Object, false otherwise.
389          * @param {Object} value The value to test
390          * @return {Boolean}
391          * @method
392          */
393         isObject: (toString.call(null) === '[object Object]') ?
394         function(value) {
395             // check ownerDocument here as well to exclude DOM nodes
396             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
397         } :
398         function(value) {
399             return toString.call(value) === '[object Object]';
400         },
401
402         /**
403          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
404          * @param {Object} value The value to test
405          * @return {Boolean}
406          */
407         isPrimitive: function(value) {
408             var type = typeof value;
409
410             return type === 'string' || type === 'number' || type === 'boolean';
411         },
412
413         /**
414          * Returns true if the passed value is a JavaScript Function, false otherwise.
415          * @param {Object} value The value to test
416          * @return {Boolean}
417          * @method
418          */
419         isFunction:
420         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
421         // Object.prorotype.toString (slower)
422         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
423             return toString.call(value) === '[object Function]';
424         } : function(value) {
425             return typeof value === 'function';
426         },
427
428         /**
429          * Returns true if the passed value is a number. Returns false for non-finite numbers.
430          * @param {Object} value The value to test
431          * @return {Boolean}
432          */
433         isNumber: function(value) {
434             return typeof value === 'number' && isFinite(value);
435         },
436
437         /**
438          * Validates that a value is numeric.
439          * @param {Object} value Examples: 1, '1', '2.34'
440          * @return {Boolean} True if numeric, false otherwise
441          */
442         isNumeric: function(value) {
443             return !isNaN(parseFloat(value)) && isFinite(value);
444         },
445
446         /**
447          * Returns true if the passed value is a string.
448          * @param {Object} value The value to test
449          * @return {Boolean}
450          */
451         isString: function(value) {
452             return typeof value === 'string';
453         },
454
455         /**
456          * Returns true if the passed value is a boolean.
457          *
458          * @param {Object} value The value to test
459          * @return {Boolean}
460          */
461         isBoolean: function(value) {
462             return typeof value === 'boolean';
463         },
464
465         /**
466          * Returns true if the passed value is an HTMLElement
467          * @param {Object} value The value to test
468          * @return {Boolean}
469          */
470         isElement: function(value) {
471             return value ? value.nodeType === 1 : false;
472         },
473
474         /**
475          * Returns true if the passed value is a TextNode
476          * @param {Object} value The value to test
477          * @return {Boolean}
478          */
479         isTextNode: function(value) {
480             return value ? value.nodeName === "#text" : false;
481         },
482
483         /**
484          * Returns true if the passed value is defined.
485          * @param {Object} value The value to test
486          * @return {Boolean}
487          */
488         isDefined: function(value) {
489             return typeof value !== 'undefined';
490         },
491
492         /**
493          * Returns true if the passed value is iterable, false otherwise
494          * @param {Object} value The value to test
495          * @return {Boolean}
496          */
497         isIterable: function(value) {
498             return (value && typeof value !== 'string') ? value.length !== undefined : false;
499         }
500     });
501
502     Ext.apply(Ext, {
503
504         /**
505          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
506          * @param {Object} item The variable to clone
507          * @return {Object} clone
508          */
509         clone: function(item) {
510             if (item === null || item === undefined) {
511                 return item;
512             }
513
514             // DOM nodes
515             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
516             // recursively
517             if (item.nodeType && item.cloneNode) {
518                 return item.cloneNode(true);
519             }
520
521             var type = toString.call(item);
522
523             // Date
524             if (type === '[object Date]') {
525                 return new Date(item.getTime());
526             }
527
528             var i, j, k, clone, key;
529
530             // Array
531             if (type === '[object Array]') {
532                 i = item.length;
533
534                 clone = [];
535
536                 while (i--) {
537                     clone[i] = Ext.clone(item[i]);
538                 }
539             }
540             // Object
541             else if (type === '[object Object]' && item.constructor === Object) {
542                 clone = {};
543
544                 for (key in item) {
545                     clone[key] = Ext.clone(item[key]);
546                 }
547
548                 if (enumerables) {
549                     for (j = enumerables.length; j--;) {
550                         k = enumerables[j];
551                         clone[k] = item[k];
552                     }
553                 }
554             }
555
556             return clone || item;
557         },
558
559         /**
560          * @private
561          * Generate a unique reference of Ext in the global scope, useful for sandboxing
562          */
563         getUniqueGlobalNamespace: function() {
564             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
565
566             if (uniqueGlobalNamespace === undefined) {
567                 var i = 0;
568
569                 do {
570                     uniqueGlobalNamespace = 'ExtBox' + (++i);
571                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
572
573                 Ext.global[uniqueGlobalNamespace] = Ext;
574                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
575             }
576
577             return uniqueGlobalNamespace;
578         },
579
580         /**
581          * @private
582          */
583         functionFactory: function() {
584             var args = Array.prototype.slice.call(arguments);
585
586             if (args.length > 0) {
587                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
588                     args[args.length - 1];
589             }
590
591             return Function.prototype.constructor.apply(Function.prototype, args);
592         }
593     });
594
595     /**
596      * Old alias to {@link Ext#typeOf}
597      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
598      * @method
599      * @alias Ext#typeOf
600      */
601     Ext.type = Ext.typeOf;
602
603 })();
604
605 /**
606  * @author Jacky Nguyen <jacky@sencha.com>
607  * @docauthor Jacky Nguyen <jacky@sencha.com>
608  * @class Ext.Version
609  *
610  * A utility class that wrap around a string version number and provide convenient
611  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
612
613     var version = new Ext.Version('1.0.2beta');
614     console.log("Version is " + version); // Version is 1.0.2beta
615
616     console.log(version.getMajor()); // 1
617     console.log(version.getMinor()); // 0
618     console.log(version.getPatch()); // 2
619     console.log(version.getBuild()); // 0
620     console.log(version.getRelease()); // beta
621
622     console.log(version.isGreaterThan('1.0.1')); // True
623     console.log(version.isGreaterThan('1.0.2alpha')); // True
624     console.log(version.isGreaterThan('1.0.2RC')); // False
625     console.log(version.isGreaterThan('1.0.2')); // False
626     console.log(version.isLessThan('1.0.2')); // True
627
628     console.log(version.match(1.0)); // True
629     console.log(version.match('1.0.2')); // True
630
631  * @markdown
632  */
633 (function() {
634
635 // Current core version
636 var version = '4.0.7', Version;
637     Ext.Version = Version = Ext.extend(Object, {
638
639         /**
640          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
641          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
642          * @return {Ext.Version} this
643          */
644         constructor: function(version) {
645             var parts, releaseStartIndex;
646
647             if (version instanceof Version) {
648                 return version;
649             }
650
651             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
652
653             releaseStartIndex = this.version.search(/([^\d\.])/);
654
655             if (releaseStartIndex !== -1) {
656                 this.release = this.version.substr(releaseStartIndex, version.length);
657                 this.shortVersion = this.version.substr(0, releaseStartIndex);
658             }
659
660             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
661
662             parts = this.version.split('.');
663
664             this.major = parseInt(parts.shift() || 0, 10);
665             this.minor = parseInt(parts.shift() || 0, 10);
666             this.patch = parseInt(parts.shift() || 0, 10);
667             this.build = parseInt(parts.shift() || 0, 10);
668
669             return this;
670         },
671
672         /**
673          * Override the native toString method
674          * @private
675          * @return {String} version
676          */
677         toString: function() {
678             return this.version;
679         },
680
681         /**
682          * Override the native valueOf method
683          * @private
684          * @return {String} version
685          */
686         valueOf: function() {
687             return this.version;
688         },
689
690         /**
691          * Returns the major component value
692          * @return {Number} major
693          */
694         getMajor: function() {
695             return this.major || 0;
696         },
697
698         /**
699          * Returns the minor component value
700          * @return {Number} minor
701          */
702         getMinor: function() {
703             return this.minor || 0;
704         },
705
706         /**
707          * Returns the patch component value
708          * @return {Number} patch
709          */
710         getPatch: function() {
711             return this.patch || 0;
712         },
713
714         /**
715          * Returns the build component value
716          * @return {Number} build
717          */
718         getBuild: function() {
719             return this.build || 0;
720         },
721
722         /**
723          * Returns the release component value
724          * @return {Number} release
725          */
726         getRelease: function() {
727             return this.release || '';
728         },
729
730         /**
731          * Returns whether this version if greater than the supplied argument
732          * @param {String/Number} target The version to compare with
733          * @return {Boolean} True if this version if greater than the target, false otherwise
734          */
735         isGreaterThan: function(target) {
736             return Version.compare(this.version, target) === 1;
737         },
738
739         /**
740          * Returns whether this version if smaller than the supplied argument
741          * @param {String/Number} target The version to compare with
742          * @return {Boolean} True if this version if smaller than the target, false otherwise
743          */
744         isLessThan: function(target) {
745             return Version.compare(this.version, target) === -1;
746         },
747
748         /**
749          * Returns whether this version equals to the supplied argument
750          * @param {String/Number} target The version to compare with
751          * @return {Boolean} True if this version equals to the target, false otherwise
752          */
753         equals: function(target) {
754             return Version.compare(this.version, target) === 0;
755         },
756
757         /**
758          * Returns whether this version matches the supplied argument. Example:
759          * <pre><code>
760          * var version = new Ext.Version('1.0.2beta');
761          * console.log(version.match(1)); // True
762          * console.log(version.match(1.0)); // True
763          * console.log(version.match('1.0.2')); // True
764          * console.log(version.match('1.0.2RC')); // False
765          * </code></pre>
766          * @param {String/Number} target The version to compare with
767          * @return {Boolean} True if this version matches the target, false otherwise
768          */
769         match: function(target) {
770             target = String(target);
771             return this.version.substr(0, target.length) === target;
772         },
773
774         /**
775          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
776          * @return {Number[]}
777          */
778         toArray: function() {
779             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
780         },
781
782         /**
783          * Returns shortVersion version without dots and release
784          * @return {String}
785          */
786         getShortVersion: function() {
787             return this.shortVersion;
788         }
789     });
790
791     Ext.apply(Version, {
792         // @private
793         releaseValueMap: {
794             'dev': -6,
795             'alpha': -5,
796             'a': -5,
797             'beta': -4,
798             'b': -4,
799             'rc': -3,
800             '#': -2,
801             'p': -1,
802             'pl': -1
803         },
804
805         /**
806          * Converts a version component to a comparable value
807          *
808          * @static
809          * @param {Object} value The value to convert
810          * @return {Object}
811          */
812         getComponentValue: function(value) {
813             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
814         },
815
816         /**
817          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
818          * they are handled in the following order:
819          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
820          *
821          * @static
822          * @param {String} current The current version to compare to
823          * @param {String} target The target version to compare to
824          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
825          */
826         compare: function(current, target) {
827             var currentValue, targetValue, i;
828
829             current = new Version(current).toArray();
830             target = new Version(target).toArray();
831
832             for (i = 0; i < Math.max(current.length, target.length); i++) {
833                 currentValue = this.getComponentValue(current[i]);
834                 targetValue = this.getComponentValue(target[i]);
835
836                 if (currentValue < targetValue) {
837                     return -1;
838                 } else if (currentValue > targetValue) {
839                     return 1;
840                 }
841             }
842
843             return 0;
844         }
845     });
846
847     Ext.apply(Ext, {
848         /**
849          * @private
850          */
851         versions: {},
852
853         /**
854          * @private
855          */
856         lastRegisteredVersion: null,
857
858         /**
859          * Set version number for the given package name.
860          *
861          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
862          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
863          * @return {Ext}
864          */
865         setVersion: function(packageName, version) {
866             Ext.versions[packageName] = new Version(version);
867             Ext.lastRegisteredVersion = Ext.versions[packageName];
868
869             return this;
870         },
871
872         /**
873          * Get the version number of the supplied package name; will return the last registered version
874          * (last Ext.setVersion call) if there's no package name given.
875          *
876          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
877          * @return {Ext.Version} The version
878          */
879         getVersion: function(packageName) {
880             if (packageName === undefined) {
881                 return Ext.lastRegisteredVersion;
882             }
883
884             return Ext.versions[packageName];
885         },
886
887         /**
888          * Create a closure for deprecated code.
889          *
890     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
891     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
892     // the closure will not be invoked
893     Ext.deprecate('extjs', '4.0.0beta', function() {
894         Ext.oldMethod = Ext.newMethod;
895
896         ...
897     });
898
899          * @param {String} packageName The package name
900          * @param {String} since The last version before it's deprecated
901          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
902          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
903          * @markdown
904          */
905         deprecate: function(packageName, since, closure, scope) {
906             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
907                 closure.call(scope);
908             }
909         }
910     }); // End Versioning
911
912     Ext.setVersion('core', version);
913
914 })();
915
916 /**
917  * @class Ext.String
918  *
919  * A collection of useful static methods to deal with strings
920  * @singleton
921  */
922
923 Ext.String = {
924     trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
925     escapeRe: /('|\\)/g,
926     formatRe: /\{(\d+)\}/g,
927     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
928
929     /**
930      * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
931      * @param {String} value The string to encode
932      * @return {String} The encoded text
933      * @method
934      */
935     htmlEncode: (function() {
936         var entities = {
937             '&': '&amp;',
938             '>': '&gt;',
939             '<': '&lt;',
940             '"': '&quot;'
941         }, keys = [], p, regex;
942         
943         for (p in entities) {
944             keys.push(p);
945         }
946         
947         regex = new RegExp('(' + keys.join('|') + ')', 'g');
948         
949         return function(value) {
950             return (!value) ? value : String(value).replace(regex, function(match, capture) {
951                 return entities[capture];    
952             });
953         };
954     })(),
955
956     /**
957      * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
958      * @param {String} value The string to decode
959      * @return {String} The decoded text
960      * @method
961      */
962     htmlDecode: (function() {
963         var entities = {
964             '&amp;': '&',
965             '&gt;': '>',
966             '&lt;': '<',
967             '&quot;': '"'
968         }, keys = [], p, regex;
969         
970         for (p in entities) {
971             keys.push(p);
972         }
973         
974         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
975         
976         return function(value) {
977             return (!value) ? value : String(value).replace(regex, function(match, capture) {
978                 if (capture in entities) {
979                     return entities[capture];
980                 } else {
981                     return String.fromCharCode(parseInt(capture.substr(2), 10));
982                 }
983             });
984         };
985     })(),
986
987     /**
988      * Appends content to the query string of a URL, handling logic for whether to place
989      * a question mark or ampersand.
990      * @param {String} url The URL to append to.
991      * @param {String} string The content to append to the URL.
992      * @return (String) The resulting URL
993      */
994     urlAppend : function(url, string) {
995         if (!Ext.isEmpty(string)) {
996             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
997         }
998
999         return url;
1000     },
1001
1002     /**
1003      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
1004      * @example
1005 var s = '  foo bar  ';
1006 alert('-' + s + '-');         //alerts "- foo bar -"
1007 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
1008
1009      * @param {String} string The string to escape
1010      * @return {String} The trimmed string
1011      */
1012     trim: function(string) {
1013         return string.replace(Ext.String.trimRegex, "");
1014     },
1015
1016     /**
1017      * Capitalize the given string
1018      * @param {String} string
1019      * @return {String}
1020      */
1021     capitalize: function(string) {
1022         return string.charAt(0).toUpperCase() + string.substr(1);
1023     },
1024
1025     /**
1026      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1027      * @param {String} value The string to truncate
1028      * @param {Number} length The maximum length to allow before truncating
1029      * @param {Boolean} word True to try to find a common word break
1030      * @return {String} The converted text
1031      */
1032     ellipsis: function(value, len, word) {
1033         if (value && value.length > len) {
1034             if (word) {
1035                 var vs = value.substr(0, len - 2),
1036                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1037                 if (index !== -1 && index >= (len - 15)) {
1038                     return vs.substr(0, index) + "...";
1039                 }
1040             }
1041             return value.substr(0, len - 3) + "...";
1042         }
1043         return value;
1044     },
1045
1046     /**
1047      * Escapes the passed string for use in a regular expression
1048      * @param {String} string
1049      * @return {String}
1050      */
1051     escapeRegex: function(string) {
1052         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1053     },
1054
1055     /**
1056      * Escapes the passed string for ' and \
1057      * @param {String} string The string to escape
1058      * @return {String} The escaped string
1059      */
1060     escape: function(string) {
1061         return string.replace(Ext.String.escapeRe, "\\$1");
1062     },
1063
1064     /**
1065      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1066      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1067      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1068      * but does not change the current string.
1069      * <pre><code>
1070     // alternate sort directions
1071     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1072
1073     // instead of conditional logic:
1074     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1075        </code></pre>
1076      * @param {String} string The current string
1077      * @param {String} value The value to compare to the current string
1078      * @param {String} other The new value to use if the string already equals the first value passed in
1079      * @return {String} The new value
1080      */
1081     toggle: function(string, value, other) {
1082         return string === value ? other : value;
1083     },
1084
1085     /**
1086      * Pads the left side of a string with a specified character.  This is especially useful
1087      * for normalizing number and date strings.  Example usage:
1088      *
1089      * <pre><code>
1090 var s = Ext.String.leftPad('123', 5, '0');
1091 // s now contains the string: '00123'
1092        </code></pre>
1093      * @param {String} string The original string
1094      * @param {Number} size The total length of the output string
1095      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1096      * @return {String} The padded string
1097      */
1098     leftPad: function(string, size, character) {
1099         var result = String(string);
1100         character = character || " ";
1101         while (result.length < size) {
1102             result = character + result;
1103         }
1104         return result;
1105     },
1106
1107     /**
1108      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1109      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1110      * <pre><code>
1111 var cls = 'my-class', text = 'Some text';
1112 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1113 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1114        </code></pre>
1115      * @param {String} string The tokenized string to be formatted
1116      * @param {String} value1 The value to replace token {0}
1117      * @param {String} value2 Etc...
1118      * @return {String} The formatted string
1119      */
1120     format: function(format) {
1121         var args = Ext.Array.toArray(arguments, 1);
1122         return format.replace(Ext.String.formatRe, function(m, i) {
1123             return args[i];
1124         });
1125     },
1126
1127     /**
1128      * Returns a string with a specified number of repititions a given string pattern.
1129      * The pattern be separated by a different string.
1130      *
1131      *      var s = Ext.String.repeat('---', 4); // = '------------'
1132      *      var t = Ext.String.repeat('--', 3, '/'); // = '--/--/--'
1133      *
1134      * @param {String} pattern The pattern to repeat.
1135      * @param {Number} count The number of times to repeat the pattern (may be 0).
1136      * @param {String} sep An option string to separate each pattern.
1137      */
1138     repeat: function(pattern, count, sep) {
1139         for (var buf = [], i = count; i--; ) {
1140             buf.push(pattern);
1141         }
1142         return buf.join(sep || '');
1143     }
1144 };
1145
1146 /**
1147  * @class Ext.Number
1148  *
1149  * A collection of useful static methods to deal with numbers
1150  * @singleton
1151  */
1152
1153 (function() {
1154
1155 var isToFixedBroken = (0.9).toFixed() !== '1';
1156
1157 Ext.Number = {
1158     /**
1159      * Checks whether or not the passed number is within a desired range.  If the number is already within the
1160      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1161      * exceeded. Note that this method returns the constrained value but does not change the current number.
1162      * @param {Number} number The number to check
1163      * @param {Number} min The minimum number in the range
1164      * @param {Number} max The maximum number in the range
1165      * @return {Number} The constrained value if outside the range, otherwise the current value
1166      */
1167     constrain: function(number, min, max) {
1168         number = parseFloat(number);
1169
1170         if (!isNaN(min)) {
1171             number = Math.max(number, min);
1172         }
1173         if (!isNaN(max)) {
1174             number = Math.min(number, max);
1175         }
1176         return number;
1177     },
1178
1179     /**
1180      * Snaps the passed number between stopping points based upon a passed increment value.
1181      * @param {Number} value The unsnapped value.
1182      * @param {Number} increment The increment by which the value must move.
1183      * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1184      * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1185      * @return {Number} The value of the nearest snap target.
1186      */
1187     snap : function(value, increment, minValue, maxValue) {
1188         var newValue = value,
1189             m;
1190
1191         if (!(increment && value)) {
1192             return value;
1193         }
1194         m = value % increment;
1195         if (m !== 0) {
1196             newValue -= m;
1197             if (m * 2 >= increment) {
1198                 newValue += increment;
1199             } else if (m * 2 < -increment) {
1200                 newValue -= increment;
1201             }
1202         }
1203         return Ext.Number.constrain(newValue, minValue,  maxValue);
1204     },
1205
1206     /**
1207      * Formats a number using fixed-point notation
1208      * @param {Number} value The number to format
1209      * @param {Number} precision The number of digits to show after the decimal point
1210      */
1211     toFixed: function(value, precision) {
1212         if (isToFixedBroken) {
1213             precision = precision || 0;
1214             var pow = Math.pow(10, precision);
1215             return (Math.round(value * pow) / pow).toFixed(precision);
1216         }
1217
1218         return value.toFixed(precision);
1219     },
1220
1221     /**
1222      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1223      * it is not.
1224
1225 Ext.Number.from('1.23', 1); // returns 1.23
1226 Ext.Number.from('abc', 1); // returns 1
1227
1228      * @param {Object} value
1229      * @param {Number} defaultValue The value to return if the original value is non-numeric
1230      * @return {Number} value, if numeric, defaultValue otherwise
1231      */
1232     from: function(value, defaultValue) {
1233         if (isFinite(value)) {
1234             value = parseFloat(value);
1235         }
1236
1237         return !isNaN(value) ? value : defaultValue;
1238     }
1239 };
1240
1241 })();
1242
1243 /**
1244  * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
1245  * @member Ext
1246  * @method num
1247  * @alias Ext.Number#from
1248  */
1249 Ext.num = function() {
1250     return Ext.Number.from.apply(this, arguments);
1251 };
1252 /**
1253  * @class Ext.Array
1254  * @singleton
1255  * @author Jacky Nguyen <jacky@sencha.com>
1256  * @docauthor Jacky Nguyen <jacky@sencha.com>
1257  *
1258  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1259  */
1260 (function() {
1261
1262     var arrayPrototype = Array.prototype,
1263         slice = arrayPrototype.slice,
1264         supportsSplice = function () {
1265             var array = [],
1266                 lengthBefore,
1267                 j = 20;
1268
1269             if (!array.splice) {
1270                 return false;
1271             }
1272
1273             // This detects a bug in IE8 splice method:
1274             // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1275
1276             while (j--) {
1277                 array.push("A");
1278             }
1279
1280             array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
1281
1282             lengthBefore = array.length; //41
1283             array.splice(13, 0, "XXX"); // add one element
1284
1285             if (lengthBefore+1 != array.length) {
1286                 return false;
1287             }
1288             // end IE8 bug
1289
1290             return true;
1291         }(),
1292         supportsForEach = 'forEach' in arrayPrototype,
1293         supportsMap = 'map' in arrayPrototype,
1294         supportsIndexOf = 'indexOf' in arrayPrototype,
1295         supportsEvery = 'every' in arrayPrototype,
1296         supportsSome = 'some' in arrayPrototype,
1297         supportsFilter = 'filter' in arrayPrototype,
1298         supportsSort = function() {
1299             var a = [1,2,3,4,5].sort(function(){ return 0; });
1300             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1301         }(),
1302         supportsSliceOnNodeList = true,
1303         ExtArray;
1304
1305     try {
1306         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1307         if (typeof document !== 'undefined') {
1308             slice.call(document.getElementsByTagName('body'));
1309         }
1310     } catch (e) {
1311         supportsSliceOnNodeList = false;
1312     }
1313
1314     function fixArrayIndex (array, index) {
1315         return (index < 0) ? Math.max(0, array.length + index)
1316                            : Math.min(array.length, index);
1317     }
1318
1319     /*
1320     Does the same work as splice, but with a slightly more convenient signature. The splice
1321     method has bugs in IE8, so this is the implementation we use on that platform.
1322
1323     The rippling of items in the array can be tricky. Consider two use cases:
1324
1325                   index=2
1326                   removeCount=2
1327                  /=====\
1328         +---+---+---+---+---+---+---+---+
1329         | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1330         +---+---+---+---+---+---+---+---+
1331                          /  \/  \/  \/  \
1332                         /   /\  /\  /\   \
1333                        /   /  \/  \/  \   +--------------------------+
1334                       /   /   /\  /\   +--------------------------+   \
1335                      /   /   /  \/  +--------------------------+   \   \
1336                     /   /   /   /+--------------------------+   \   \   \
1337                    /   /   /   /                             \   \   \   \
1338                   v   v   v   v                               v   v   v   v
1339         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1340         | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1341         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1342         A                               B        \=========/
1343                                                  insert=[a,b,c]
1344
1345     In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1346     that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1347     must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1348     */
1349     function replaceSim (array, index, removeCount, insert) {
1350         var add = insert ? insert.length : 0,
1351             length = array.length,
1352             pos = fixArrayIndex(array, index);
1353
1354         // we try to use Array.push when we can for efficiency...
1355         if (pos === length) {
1356             if (add) {
1357                 array.push.apply(array, insert);
1358             }
1359         } else {
1360             var remove = Math.min(removeCount, length - pos),
1361                 tailOldPos = pos + remove,
1362                 tailNewPos = tailOldPos + add - remove,
1363                 tailCount = length - tailOldPos,
1364                 lengthAfterRemove = length - remove,
1365                 i;
1366
1367             if (tailNewPos < tailOldPos) { // case A
1368                 for (i = 0; i < tailCount; ++i) {
1369                     array[tailNewPos+i] = array[tailOldPos+i];
1370                 }
1371             } else if (tailNewPos > tailOldPos) { // case B
1372                 for (i = tailCount; i--; ) {
1373                     array[tailNewPos+i] = array[tailOldPos+i];
1374                 }
1375             } // else, add == remove (nothing to do)
1376
1377             if (add && pos === lengthAfterRemove) {
1378                 array.length = lengthAfterRemove; // truncate array
1379                 array.push.apply(array, insert);
1380             } else {
1381                 array.length = lengthAfterRemove + add; // reserves space
1382                 for (i = 0; i < add; ++i) {
1383                     array[pos+i] = insert[i];
1384                 }
1385             }
1386         }
1387
1388         return array;
1389     }
1390
1391     function replaceNative (array, index, removeCount, insert) {
1392         if (insert && insert.length) {
1393             if (index < array.length) {
1394                 array.splice.apply(array, [index, removeCount].concat(insert));
1395             } else {
1396                 array.push.apply(array, insert);
1397             }
1398         } else {
1399             array.splice(index, removeCount);
1400         }
1401         return array;
1402     }
1403
1404     function eraseSim (array, index, removeCount) {
1405         return replaceSim(array, index, removeCount);
1406     }
1407
1408     function eraseNative (array, index, removeCount) {
1409         array.splice(index, removeCount);
1410         return array;
1411     }
1412
1413     function spliceSim (array, index, removeCount) {
1414         var pos = fixArrayIndex(array, index),
1415             removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1416
1417         if (arguments.length < 4) {
1418             replaceSim(array, pos, removeCount);
1419         } else {
1420             replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1421         }
1422
1423         return removed;
1424     }
1425
1426     function spliceNative (array) {
1427         return array.splice.apply(array, slice.call(arguments, 1));
1428     }
1429
1430     var erase = supportsSplice ? eraseNative : eraseSim,
1431         replace = supportsSplice ? replaceNative : replaceSim,
1432         splice = supportsSplice ? spliceNative : spliceSim;
1433
1434     // NOTE: from here on, use erase, replace or splice (not native methods)...
1435
1436     ExtArray = Ext.Array = {
1437         /**
1438          * Iterates an array or an iterable value and invoke the given callback function for each item.
1439          *
1440          *     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1441          *
1442          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1443          *         console.log(name);
1444          *     });
1445          *
1446          *     var sum = function() {
1447          *         var sum = 0;
1448          *
1449          *         Ext.Array.each(arguments, function(value) {
1450          *             sum += value;
1451          *         });
1452          *
1453          *         return sum;
1454          *     };
1455          *
1456          *     sum(1, 2, 3); // returns 6
1457          *
1458          * The iteration can be stopped by returning false in the function callback.
1459          *
1460          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1461          *         if (name === 'Singapore') {
1462          *             return false; // break here
1463          *         }
1464          *     });
1465          *
1466          * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1467          *
1468          * @param {Array/NodeList/Object} iterable The value to be iterated. If this
1469          * argument is not iterable, the callback function is called once.
1470          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1471          * the current `index`.
1472          * @param {Object} fn.item The item at the current `index` in the passed `array`
1473          * @param {Number} fn.index The current `index` within the `array`
1474          * @param {Array} fn.allItems The `array` itself which was passed as the first argument
1475          * @param {Boolean} fn.return Return false to stop iteration.
1476          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1477          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1478          * Defaults false
1479          * @return {Boolean} See description for the `fn` parameter.
1480          */
1481         each: function(array, fn, scope, reverse) {
1482             array = ExtArray.from(array);
1483
1484             var i,
1485                 ln = array.length;
1486
1487             if (reverse !== true) {
1488                 for (i = 0; i < ln; i++) {
1489                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1490                         return i;
1491                     }
1492                 }
1493             }
1494             else {
1495                 for (i = ln - 1; i > -1; i--) {
1496                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1497                         return i;
1498                     }
1499                 }
1500             }
1501
1502             return true;
1503         },
1504
1505         /**
1506          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1507          * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
1508          * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
1509          * could be much better in modern browsers comparing with {@link Ext.Array#each}
1510          *
1511          * @param {Array} array The array to iterate
1512          * @param {Function} fn The callback function.
1513          * @param {Object} fn.item The item at the current `index` in the passed `array`
1514          * @param {Number} fn.index The current `index` within the `array`
1515          * @param {Array}  fn.allItems The `array` itself which was passed as the first argument
1516          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1517          */
1518         forEach: function(array, fn, scope) {
1519             if (supportsForEach) {
1520                 return array.forEach(fn, scope);
1521             }
1522
1523             var i = 0,
1524                 ln = array.length;
1525
1526             for (; i < ln; i++) {
1527                 fn.call(scope, array[i], i, array);
1528             }
1529         },
1530
1531         /**
1532          * Get the index of the provided `item` in the given `array`, a supplement for the
1533          * missing arrayPrototype.indexOf in Internet Explorer.
1534          *
1535          * @param {Array} array The array to check
1536          * @param {Object} item The item to look for
1537          * @param {Number} from (Optional) The index at which to begin the search
1538          * @return {Number} The index of item in the array (or -1 if it is not found)
1539          */
1540         indexOf: function(array, item, from) {
1541             if (supportsIndexOf) {
1542                 return array.indexOf(item, from);
1543             }
1544
1545             var i, length = array.length;
1546
1547             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1548                 if (array[i] === item) {
1549                     return i;
1550                 }
1551             }
1552
1553             return -1;
1554         },
1555
1556         /**
1557          * Checks whether or not the given `array` contains the specified `item`
1558          *
1559          * @param {Array} array The array to check
1560          * @param {Object} item The item to look for
1561          * @return {Boolean} True if the array contains the item, false otherwise
1562          */
1563         contains: function(array, item) {
1564             if (supportsIndexOf) {
1565                 return array.indexOf(item) !== -1;
1566             }
1567
1568             var i, ln;
1569
1570             for (i = 0, ln = array.length; i < ln; i++) {
1571                 if (array[i] === item) {
1572                     return true;
1573                 }
1574             }
1575
1576             return false;
1577         },
1578
1579         /**
1580          * Converts any iterable (numeric indices and a length property) into a true array.
1581          *
1582          *     function test() {
1583          *         var args = Ext.Array.toArray(arguments),
1584          *             fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1585          *
1586          *         alert(args.join(' '));
1587          *         alert(fromSecondToLastArgs.join(' '));
1588          *     }
1589          *
1590          *     test('just', 'testing', 'here'); // alerts 'just testing here';
1591          *                                      // alerts 'testing here';
1592          *
1593          *     Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1594          *     Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1595          *     Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1596          *
1597          * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1598          *
1599          * @param {Object} iterable the iterable object to be turned into a true Array.
1600          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1601          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1602          * index of the iterable value
1603          * @return {Array} array
1604          */
1605         toArray: function(iterable, start, end){
1606             if (!iterable || !iterable.length) {
1607                 return [];
1608             }
1609
1610             if (typeof iterable === 'string') {
1611                 iterable = iterable.split('');
1612             }
1613
1614             if (supportsSliceOnNodeList) {
1615                 return slice.call(iterable, start || 0, end || iterable.length);
1616             }
1617
1618             var array = [],
1619                 i;
1620
1621             start = start || 0;
1622             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1623
1624             for (i = start; i < end; i++) {
1625                 array.push(iterable[i]);
1626             }
1627
1628             return array;
1629         },
1630
1631         /**
1632          * Plucks the value of a property from each item in the Array. Example:
1633          *
1634          *     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1635          *
1636          * @param {Array/NodeList} array The Array of items to pluck the value from.
1637          * @param {String} propertyName The property name to pluck from each element.
1638          * @return {Array} The value from each item in the Array.
1639          */
1640         pluck: function(array, propertyName) {
1641             var ret = [],
1642                 i, ln, item;
1643
1644             for (i = 0, ln = array.length; i < ln; i++) {
1645                 item = array[i];
1646
1647                 ret.push(item[propertyName]);
1648             }
1649
1650             return ret;
1651         },
1652
1653         /**
1654          * Creates a new array with the results of calling a provided function on every element in this array.
1655          *
1656          * @param {Array} array
1657          * @param {Function} fn Callback function for each item
1658          * @param {Object} scope Callback function scope
1659          * @return {Array} results
1660          */
1661         map: function(array, fn, scope) {
1662             if (supportsMap) {
1663                 return array.map(fn, scope);
1664             }
1665
1666             var results = [],
1667                 i = 0,
1668                 len = array.length;
1669
1670             for (; i < len; i++) {
1671                 results[i] = fn.call(scope, array[i], i, array);
1672             }
1673
1674             return results;
1675         },
1676
1677         /**
1678          * Executes the specified function for each array element until the function returns a falsy value.
1679          * If such an item is found, the function will return false immediately.
1680          * Otherwise, it will return true.
1681          *
1682          * @param {Array} array
1683          * @param {Function} fn Callback function for each item
1684          * @param {Object} scope Callback function scope
1685          * @return {Boolean} True if no false value is returned by the callback function.
1686          */
1687         every: function(array, fn, scope) {
1688             if (supportsEvery) {
1689                 return array.every(fn, scope);
1690             }
1691
1692             var i = 0,
1693                 ln = array.length;
1694
1695             for (; i < ln; ++i) {
1696                 if (!fn.call(scope, array[i], i, array)) {
1697                     return false;
1698                 }
1699             }
1700
1701             return true;
1702         },
1703
1704         /**
1705          * Executes the specified function for each array element until the function returns a truthy value.
1706          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1707          *
1708          * @param {Array} array
1709          * @param {Function} fn Callback function for each item
1710          * @param {Object} scope Callback function scope
1711          * @return {Boolean} True if the callback function returns a truthy value.
1712          */
1713         some: function(array, fn, scope) {
1714             if (supportsSome) {
1715                 return array.some(fn, scope);
1716             }
1717
1718             var i = 0,
1719                 ln = array.length;
1720
1721             for (; i < ln; ++i) {
1722                 if (fn.call(scope, array[i], i, array)) {
1723                     return true;
1724                 }
1725             }
1726
1727             return false;
1728         },
1729
1730         /**
1731          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1732          *
1733          * See {@link Ext.Array#filter}
1734          *
1735          * @param {Array} array
1736          * @return {Array} results
1737          */
1738         clean: function(array) {
1739             var results = [],
1740                 i = 0,
1741                 ln = array.length,
1742                 item;
1743
1744             for (; i < ln; i++) {
1745                 item = array[i];
1746
1747                 if (!Ext.isEmpty(item)) {
1748                     results.push(item);
1749                 }
1750             }
1751
1752             return results;
1753         },
1754
1755         /**
1756          * Returns a new array with unique items
1757          *
1758          * @param {Array} array
1759          * @return {Array} results
1760          */
1761         unique: function(array) {
1762             var clone = [],
1763                 i = 0,
1764                 ln = array.length,
1765                 item;
1766
1767             for (; i < ln; i++) {
1768                 item = array[i];
1769
1770                 if (ExtArray.indexOf(clone, item) === -1) {
1771                     clone.push(item);
1772                 }
1773             }
1774
1775             return clone;
1776         },
1777
1778         /**
1779          * Creates a new array with all of the elements of this array for which
1780          * the provided filtering function returns true.
1781          *
1782          * @param {Array} array
1783          * @param {Function} fn Callback function for each item
1784          * @param {Object} scope Callback function scope
1785          * @return {Array} results
1786          */
1787         filter: function(array, fn, scope) {
1788             if (supportsFilter) {
1789                 return array.filter(fn, scope);
1790             }
1791
1792             var results = [],
1793                 i = 0,
1794                 ln = array.length;
1795
1796             for (; i < ln; i++) {
1797                 if (fn.call(scope, array[i], i, array)) {
1798                     results.push(array[i]);
1799                 }
1800             }
1801
1802             return results;
1803         },
1804
1805         /**
1806          * Converts a value to an array if it's not already an array; returns:
1807          *
1808          * - An empty array if given value is `undefined` or `null`
1809          * - Itself if given value is already an array
1810          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1811          * - An array with one item which is the given value, otherwise
1812          *
1813          * @param {Object} value The value to convert to an array if it's not already is an array
1814          * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
1815          * defaults to false
1816          * @return {Array} array
1817          */
1818         from: function(value, newReference) {
1819             if (value === undefined || value === null) {
1820                 return [];
1821             }
1822
1823             if (Ext.isArray(value)) {
1824                 return (newReference) ? slice.call(value) : value;
1825             }
1826
1827             if (value && value.length !== undefined && typeof value !== 'string') {
1828                 return Ext.toArray(value);
1829             }
1830
1831             return [value];
1832         },
1833
1834         /**
1835          * Removes the specified item from the array if it exists
1836          *
1837          * @param {Array} array The array
1838          * @param {Object} item The item to remove
1839          * @return {Array} The passed array itself
1840          */
1841         remove: function(array, item) {
1842             var index = ExtArray.indexOf(array, item);
1843
1844             if (index !== -1) {
1845                 erase(array, index, 1);
1846             }
1847
1848             return array;
1849         },
1850
1851         /**
1852          * Push an item into the array only if the array doesn't contain it yet
1853          *
1854          * @param {Array} array The array
1855          * @param {Object} item The item to include
1856          */
1857         include: function(array, item) {
1858             if (!ExtArray.contains(array, item)) {
1859                 array.push(item);
1860             }
1861         },
1862
1863         /**
1864          * Clone a flat array without referencing the previous one. Note that this is different
1865          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1866          * for Array.prototype.slice.call(array)
1867          *
1868          * @param {Array} array The array
1869          * @return {Array} The clone array
1870          */
1871         clone: function(array) {
1872             return slice.call(array);
1873         },
1874
1875         /**
1876          * Merge multiple arrays into one with unique items.
1877          *
1878          * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1879          *
1880          * @param {Array} array1
1881          * @param {Array} array2
1882          * @param {Array} etc
1883          * @return {Array} merged
1884          */
1885         merge: function() {
1886             var args = slice.call(arguments),
1887                 array = [],
1888                 i, ln;
1889
1890             for (i = 0, ln = args.length; i < ln; i++) {
1891                 array = array.concat(args[i]);
1892             }
1893
1894             return ExtArray.unique(array);
1895         },
1896
1897         /**
1898          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1899          *
1900          * @param {Array} array1
1901          * @param {Array} array2
1902          * @param {Array} etc
1903          * @return {Array} intersect
1904          */
1905         intersect: function() {
1906             var intersect = [],
1907                 arrays = slice.call(arguments),
1908                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1909
1910             if (!arrays.length) {
1911                 return intersect;
1912             }
1913
1914             // Find the smallest array
1915             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1916                 if (!minArray || array.length < minArray.length) {
1917                     minArray = array;
1918                     x = i;
1919                 }
1920             }
1921
1922             minArray = ExtArray.unique(minArray);
1923             erase(arrays, x, 1);
1924
1925             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1926             // an item in the small array, we're likely to find it before reaching the end
1927             // of the inner loop and can terminate the search early.
1928             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1929                 var count = 0;
1930
1931                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1932                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1933                         if (x === y) {
1934                             count++;
1935                             break;
1936                         }
1937                     }
1938                 }
1939
1940                 if (count === arraysLn) {
1941                     intersect.push(x);
1942                 }
1943             }
1944
1945             return intersect;
1946         },
1947
1948         /**
1949          * Perform a set difference A-B by subtracting all items in array B from array A.
1950          *
1951          * @param {Array} arrayA
1952          * @param {Array} arrayB
1953          * @return {Array} difference
1954          */
1955         difference: function(arrayA, arrayB) {
1956             var clone = slice.call(arrayA),
1957                 ln = clone.length,
1958                 i, j, lnB;
1959
1960             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1961                 for (j = 0; j < ln; j++) {
1962                     if (clone[j] === arrayB[i]) {
1963                         erase(clone, j, 1);
1964                         j--;
1965                         ln--;
1966                     }
1967                 }
1968             }
1969
1970             return clone;
1971         },
1972
1973         /**
1974          * Returns a shallow copy of a part of an array. This is equivalent to the native
1975          * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1976          * is "arguments" since the arguments object does not supply a slice method but can
1977          * be the context object to Array.prototype.slice.
1978          *
1979          * @param {Array} array The array (or arguments object).
1980          * @param {Number} begin The index at which to begin. Negative values are offsets from
1981          * the end of the array.
1982          * @param {Number} end The index at which to end. The copied items do not include
1983          * end. Negative values are offsets from the end of the array. If end is omitted,
1984          * all items up to the end of the array are copied.
1985          * @return {Array} The copied piece of the array.
1986          */
1987         // Note: IE6 will return [] on slice.call(x, undefined).
1988         slice: ([1,2].slice(1, undefined).length ?
1989             function (array, begin, end) {
1990                 return slice.call(array, begin, end);
1991             } :
1992             // at least IE6 uses arguments.length for variadic signature
1993             function (array, begin, end) {
1994                 // After tested for IE 6, the one below is of the best performance
1995                 // see http://jsperf.com/slice-fix
1996                 if (typeof begin === 'undefined') {
1997                     return slice.call(array);
1998                 }
1999                 if (typeof end === 'undefined') {
2000                     return slice.call(array, begin);
2001                 }
2002                 return slice.call(array, begin, end);
2003             }
2004         ),
2005
2006         /**
2007          * Sorts the elements of an Array.
2008          * By default, this method sorts the elements alphabetically and ascending.
2009          *
2010          * @param {Array} array The array to sort.
2011          * @param {Function} sortFn (optional) The comparison function.
2012          * @return {Array} The sorted array.
2013          */
2014         sort: function(array, sortFn) {
2015             if (supportsSort) {
2016                 if (sortFn) {
2017                     return array.sort(sortFn);
2018                 } else {
2019                     return array.sort();
2020                 }
2021             }
2022
2023             var length = array.length,
2024                 i = 0,
2025                 comparison,
2026                 j, min, tmp;
2027
2028             for (; i < length; i++) {
2029                 min = i;
2030                 for (j = i + 1; j < length; j++) {
2031                     if (sortFn) {
2032                         comparison = sortFn(array[j], array[min]);
2033                         if (comparison < 0) {
2034                             min = j;
2035                         }
2036                     } else if (array[j] < array[min]) {
2037                         min = j;
2038                     }
2039                 }
2040                 if (min !== i) {
2041                     tmp = array[i];
2042                     array[i] = array[min];
2043                     array[min] = tmp;
2044                 }
2045             }
2046
2047             return array;
2048         },
2049
2050         /**
2051          * Recursively flattens into 1-d Array. Injects Arrays inline.
2052          *
2053          * @param {Array} array The array to flatten
2054          * @return {Array} The 1-d array.
2055          */
2056         flatten: function(array) {
2057             var worker = [];
2058
2059             function rFlatten(a) {
2060                 var i, ln, v;
2061
2062                 for (i = 0, ln = a.length; i < ln; i++) {
2063                     v = a[i];
2064
2065                     if (Ext.isArray(v)) {
2066                         rFlatten(v);
2067                     } else {
2068                         worker.push(v);
2069                     }
2070                 }
2071
2072                 return worker;
2073             }
2074
2075             return rFlatten(array);
2076         },
2077
2078         /**
2079          * Returns the minimum value in the Array.
2080          *
2081          * @param {Array/NodeList} array The Array from which to select the minimum value.
2082          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2083          * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2084          * @return {Object} minValue The minimum value
2085          */
2086         min: function(array, comparisonFn) {
2087             var min = array[0],
2088                 i, ln, item;
2089
2090             for (i = 0, ln = array.length; i < ln; i++) {
2091                 item = array[i];
2092
2093                 if (comparisonFn) {
2094                     if (comparisonFn(min, item) === 1) {
2095                         min = item;
2096                     }
2097                 }
2098                 else {
2099                     if (item < min) {
2100                         min = item;
2101                     }
2102                 }
2103             }
2104
2105             return min;
2106         },
2107
2108         /**
2109          * Returns the maximum value in the Array.
2110          *
2111          * @param {Array/NodeList} array The Array from which to select the maximum value.
2112          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2113          * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2114          * @return {Object} maxValue The maximum value
2115          */
2116         max: function(array, comparisonFn) {
2117             var max = array[0],
2118                 i, ln, item;
2119
2120             for (i = 0, ln = array.length; i < ln; i++) {
2121                 item = array[i];
2122
2123                 if (comparisonFn) {
2124                     if (comparisonFn(max, item) === -1) {
2125                         max = item;
2126                     }
2127                 }
2128                 else {
2129                     if (item > max) {
2130                         max = item;
2131                     }
2132                 }
2133             }
2134
2135             return max;
2136         },
2137
2138         /**
2139          * Calculates the mean of all items in the array.
2140          *
2141          * @param {Array} array The Array to calculate the mean value of.
2142          * @return {Number} The mean.
2143          */
2144         mean: function(array) {
2145             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2146         },
2147
2148         /**
2149          * Calculates the sum of all items in the given array.
2150          *
2151          * @param {Array} array The Array to calculate the sum value of.
2152          * @return {Number} The sum.
2153          */
2154         sum: function(array) {
2155             var sum = 0,
2156                 i, ln, item;
2157
2158             for (i = 0,ln = array.length; i < ln; i++) {
2159                 item = array[i];
2160
2161                 sum += item;
2162             }
2163
2164             return sum;
2165         },
2166
2167
2168         /**
2169          * Removes items from an array. This is functionally equivalent to the splice method
2170          * of Array, but works around bugs in IE8's splice method and does not copy the
2171          * removed elements in order to return them (because very often they are ignored).
2172          *
2173          * @param {Array} array The Array on which to replace.
2174          * @param {Number} index The index in the array at which to operate.
2175          * @param {Number} removeCount The number of items to remove at index.
2176          * @return {Array} The array passed.
2177          * @method
2178          */
2179         erase: erase,
2180
2181         /**
2182          * Inserts items in to an array.
2183          *
2184          * @param {Array} array The Array on which to replace.
2185          * @param {Number} index The index in the array at which to operate.
2186          * @param {Array} items The array of items to insert at index.
2187          * @return {Array} The array passed.
2188          */
2189         insert: function (array, index, items) {
2190             return replace(array, index, 0, items);
2191         },
2192
2193         /**
2194          * Replaces items in an array. This is functionally equivalent to the splice method
2195          * of Array, but works around bugs in IE8's splice method and is often more convenient
2196          * to call because it accepts an array of items to insert rather than use a variadic
2197          * argument list.
2198          *
2199          * @param {Array} array The Array on which to replace.
2200          * @param {Number} index The index in the array at which to operate.
2201          * @param {Number} removeCount The number of items to remove at index (can be 0).
2202          * @param {Array} insert (optional) An array of items to insert at index.
2203          * @return {Array} The array passed.
2204          * @method
2205          */
2206         replace: replace,
2207
2208         /**
2209          * Replaces items in an array. This is equivalent to the splice method of Array, but
2210          * works around bugs in IE8's splice method. The signature is exactly the same as the
2211          * splice method except that the array is the first argument. All arguments following
2212          * removeCount are inserted in the array at index.
2213          *
2214          * @param {Array} array The Array on which to replace.
2215          * @param {Number} index The index in the array at which to operate.
2216          * @param {Number} removeCount The number of items to remove at index (can be 0).
2217          * @return {Array} An array containing the removed items.
2218          * @method
2219          */
2220         splice: splice
2221     };
2222
2223     /**
2224      * @method
2225      * @member Ext
2226      * @alias Ext.Array#each
2227      */
2228     Ext.each = ExtArray.each;
2229
2230     /**
2231      * @method
2232      * @member Ext.Array
2233      * @alias Ext.Array#merge
2234      */
2235     ExtArray.union = ExtArray.merge;
2236
2237     /**
2238      * Old alias to {@link Ext.Array#min}
2239      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2240      * @method
2241      * @member Ext
2242      * @alias Ext.Array#min
2243      */
2244     Ext.min = ExtArray.min;
2245
2246     /**
2247      * Old alias to {@link Ext.Array#max}
2248      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2249      * @method
2250      * @member Ext
2251      * @alias Ext.Array#max
2252      */
2253     Ext.max = ExtArray.max;
2254
2255     /**
2256      * Old alias to {@link Ext.Array#sum}
2257      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2258      * @method
2259      * @member Ext
2260      * @alias Ext.Array#sum
2261      */
2262     Ext.sum = ExtArray.sum;
2263
2264     /**
2265      * Old alias to {@link Ext.Array#mean}
2266      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2267      * @method
2268      * @member Ext
2269      * @alias Ext.Array#mean
2270      */
2271     Ext.mean = ExtArray.mean;
2272
2273     /**
2274      * Old alias to {@link Ext.Array#flatten}
2275      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2276      * @method
2277      * @member Ext
2278      * @alias Ext.Array#flatten
2279      */
2280     Ext.flatten = ExtArray.flatten;
2281
2282     /**
2283      * Old alias to {@link Ext.Array#clean}
2284      * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2285      * @method
2286      * @member Ext
2287      * @alias Ext.Array#clean
2288      */
2289     Ext.clean = ExtArray.clean;
2290
2291     /**
2292      * Old alias to {@link Ext.Array#unique}
2293      * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2294      * @method
2295      * @member Ext
2296      * @alias Ext.Array#unique
2297      */
2298     Ext.unique = ExtArray.unique;
2299
2300     /**
2301      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2302      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2303      * @method
2304      * @member Ext
2305      * @alias Ext.Array#pluck
2306      */
2307     Ext.pluck = ExtArray.pluck;
2308
2309     /**
2310      * @method
2311      * @member Ext
2312      * @alias Ext.Array#toArray
2313      */
2314     Ext.toArray = function() {
2315         return ExtArray.toArray.apply(ExtArray, arguments);
2316     };
2317 })();
2318
2319 /**
2320  * @class Ext.Function
2321  *
2322  * A collection of useful static methods to deal with function callbacks
2323  * @singleton
2324  */
2325 Ext.Function = {
2326
2327     /**
2328      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2329      * which originally accepts 2 arguments for `name` and `value`.
2330      * The wrapped function then allows "flexible" value setting of either:
2331      *
2332      * - `name` and `value` as 2 arguments
2333      * - one single object argument with multiple key - value pairs
2334      *
2335      * For example:
2336      *
2337      *     var setValue = Ext.Function.flexSetter(function(name, value) {
2338      *         this[name] = value;
2339      *     });
2340      *
2341      *     // Afterwards
2342      *     // Setting a single name - value
2343      *     setValue('name1', 'value1');
2344      *
2345      *     // Settings multiple name - value pairs
2346      *     setValue({
2347      *         name1: 'value1',
2348      *         name2: 'value2',
2349      *         name3: 'value3'
2350      *     });
2351      *
2352      * @param {Function} setter
2353      * @returns {Function} flexSetter
2354      */
2355     flexSetter: function(fn) {
2356         return function(a, b) {
2357             var k, i;
2358
2359             if (a === null) {
2360                 return this;
2361             }
2362
2363             if (typeof a !== 'string') {
2364                 for (k in a) {
2365                     if (a.hasOwnProperty(k)) {
2366                         fn.call(this, k, a[k]);
2367                     }
2368                 }
2369
2370                 if (Ext.enumerables) {
2371                     for (i = Ext.enumerables.length; i--;) {
2372                         k = Ext.enumerables[i];
2373                         if (a.hasOwnProperty(k)) {
2374                             fn.call(this, k, a[k]);
2375                         }
2376                     }
2377                 }
2378             } else {
2379                 fn.call(this, a, b);
2380             }
2381
2382             return this;
2383         };
2384     },
2385
2386     /**
2387      * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2388      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2389      *
2390      * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2391      *
2392      * @param {Function} fn The function to delegate.
2393      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2394      * **If omitted, defaults to the browser window.**
2395      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2396      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2397      * if a number the args are inserted at the specified position
2398      * @return {Function} The new function
2399      */
2400     bind: function(fn, scope, args, appendArgs) {
2401         if (arguments.length === 2) {
2402             return function() {
2403                 return fn.apply(scope, arguments);
2404             }
2405         }
2406
2407         var method = fn,
2408             slice = Array.prototype.slice;
2409
2410         return function() {
2411             var callArgs = args || arguments;
2412
2413             if (appendArgs === true) {
2414                 callArgs = slice.call(arguments, 0);
2415                 callArgs = callArgs.concat(args);
2416             }
2417             else if (typeof appendArgs == 'number') {
2418                 callArgs = slice.call(arguments, 0); // copy arguments first
2419                 Ext.Array.insert(callArgs, appendArgs, args);
2420             }
2421
2422             return method.apply(scope || window, callArgs);
2423         };
2424     },
2425
2426     /**
2427      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2428      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2429      * This is especially useful when creating callbacks.
2430      *
2431      * For example:
2432      *
2433      *     var originalFunction = function(){
2434      *         alert(Ext.Array.from(arguments).join(' '));
2435      *     };
2436      *
2437      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2438      *
2439      *     callback(); // alerts 'Hello World'
2440      *     callback('by Me'); // alerts 'Hello World by Me'
2441      *
2442      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2443      *
2444      * @param {Function} fn The original function
2445      * @param {Array} args The arguments to pass to new callback
2446      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2447      * @return {Function} The new callback function
2448      */
2449     pass: function(fn, args, scope) {
2450         if (args) {
2451             args = Ext.Array.from(args);
2452         }
2453
2454         return function() {
2455             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2456         };
2457     },
2458
2459     /**
2460      * Create an alias to the provided method property with name `methodName` of `object`.
2461      * Note that the execution scope will still be bound to the provided `object` itself.
2462      *
2463      * @param {Object/Function} object
2464      * @param {String} methodName
2465      * @return {Function} aliasFn
2466      */
2467     alias: function(object, methodName) {
2468         return function() {
2469             return object[methodName].apply(object, arguments);
2470         };
2471     },
2472
2473     /**
2474      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2475      * the original one is not called. The resulting function returns the results of the original function.
2476      * The passed function is called with the parameters of the original function. Example usage:
2477      *
2478      *     var sayHi = function(name){
2479      *         alert('Hi, ' + name);
2480      *     }
2481      *
2482      *     sayHi('Fred'); // alerts "Hi, Fred"
2483      *
2484      *     // create a new function that validates input without
2485      *     // directly modifying the original function:
2486      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2487      *         return name == 'Brian';
2488      *     });
2489      *
2490      *     sayHiToFriend('Fred');  // no alert
2491      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
2492      *
2493      * @param {Function} origFn The original function.
2494      * @param {Function} newFn The function to call before the original
2495      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2496      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2497      * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
2498      * @return {Function} The new function
2499      */
2500     createInterceptor: function(origFn, newFn, scope, returnValue) {
2501         var method = origFn;
2502         if (!Ext.isFunction(newFn)) {
2503             return origFn;
2504         }
2505         else {
2506             return function() {
2507                 var me = this,
2508                     args = arguments;
2509                 newFn.target = me;
2510                 newFn.method = origFn;
2511                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2512             };
2513         }
2514     },
2515
2516     /**
2517      * Creates a delegate (callback) which, when called, executes after a specific delay.
2518      *
2519      * @param {Function} fn The function which will be called on a delay when the returned function is called.
2520      * Optionally, a replacement (or additional) argument list may be specified.
2521      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2522      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2523      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2524      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2525      * if a number the args are inserted at the specified position.
2526      * @return {Function} A function which, when called, executes the original function after the specified delay.
2527      */
2528     createDelayed: function(fn, delay, scope, args, appendArgs) {
2529         if (scope || args) {
2530             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2531         }
2532         return function() {
2533             var me = this;
2534             setTimeout(function() {
2535                 fn.apply(me, arguments);
2536             }, delay);
2537         };
2538     },
2539
2540     /**
2541      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2542      *
2543      *     var sayHi = function(name){
2544      *         alert('Hi, ' + name);
2545      *     }
2546      *
2547      *     // executes immediately:
2548      *     sayHi('Fred');
2549      *
2550      *     // executes after 2 seconds:
2551      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2552      *
2553      *     // this syntax is sometimes useful for deferring
2554      *     // execution of an anonymous function:
2555      *     Ext.Function.defer(function(){
2556      *         alert('Anonymous');
2557      *     }, 100);
2558      *
2559      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2560      *
2561      * @param {Function} fn The function to defer.
2562      * @param {Number} millis The number of milliseconds for the setTimeout call
2563      * (if less than or equal to 0 the function is executed immediately)
2564      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2565      * **If omitted, defaults to the browser window.**
2566      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2567      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2568      * if a number the args are inserted at the specified position
2569      * @return {Number} The timeout id that can be used with clearTimeout
2570      */
2571     defer: function(fn, millis, obj, args, appendArgs) {
2572         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2573         if (millis > 0) {
2574             return setTimeout(fn, millis);
2575         }
2576         fn();
2577         return 0;
2578     },
2579
2580     /**
2581      * Create a combined function call sequence of the original function + the passed function.
2582      * The resulting function returns the results of the original function.
2583      * The passed function is called with the parameters of the original function. Example usage:
2584      *
2585      *     var sayHi = function(name){
2586      *         alert('Hi, ' + name);
2587      *     }
2588      *
2589      *     sayHi('Fred'); // alerts "Hi, Fred"
2590      *
2591      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2592      *         alert('Bye, ' + name);
2593      *     });
2594      *
2595      *     sayGoodbye('Fred'); // both alerts show
2596      *
2597      * @param {Function} origFn The original function.
2598      * @param {Function} newFn The function to sequence
2599      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2600      * If omitted, defaults to the scope in which the original function is called or the browser window.
2601      * @return {Function} The new function
2602      */
2603     createSequence: function(origFn, newFn, scope) {
2604         if (!Ext.isFunction(newFn)) {
2605             return origFn;
2606         }
2607         else {
2608             return function() {
2609                 var retval = origFn.apply(this || window, arguments);
2610                 newFn.apply(scope || this || window, arguments);
2611                 return retval;
2612             };
2613         }
2614     },
2615
2616     /**
2617      * Creates a delegate function, optionally with a bound scope which, when called, buffers
2618      * the execution of the passed function for the configured number of milliseconds.
2619      * If called again within that period, the impending invocation will be canceled, and the
2620      * timeout period will begin again.
2621      *
2622      * @param {Function} fn The function to invoke on a buffered timer.
2623      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2624      * function.
2625      * @param {Object} scope (optional) The scope (`this` reference) in which
2626      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2627      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2628      * passed by the caller.
2629      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2630      */
2631     createBuffered: function(fn, buffer, scope, args) {
2632         return function(){
2633             var timerId;
2634             return function() {
2635                 var me = this;
2636                 if (timerId) {
2637                     clearTimeout(timerId);
2638                     timerId = null;
2639                 }
2640                 timerId = setTimeout(function(){
2641                     fn.apply(scope || me, args || arguments);
2642                 }, buffer);
2643             };
2644         }();
2645     },
2646
2647     /**
2648      * Creates a throttled version of the passed function which, when called repeatedly and
2649      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2650      * previous invocation.
2651      *
2652      * This is useful for wrapping functions which may be called repeatedly, such as
2653      * a handler of a mouse move event when the processing is expensive.
2654      *
2655      * @param {Function} fn The function to execute at a regular time interval.
2656      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2657      * @param {Object} scope (optional) The scope (`this` reference) in which
2658      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2659      * @returns {Function} A function which invokes the passed function at the specified interval.
2660      */
2661     createThrottled: function(fn, interval, scope) {
2662         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2663             fn.apply(scope || this, lastArgs);
2664             lastCallTime = new Date().getTime();
2665         };
2666
2667         return function() {
2668             elapsed = new Date().getTime() - lastCallTime;
2669             lastArgs = arguments;
2670
2671             clearTimeout(timer);
2672             if (!lastCallTime || (elapsed >= interval)) {
2673                 execute();
2674             } else {
2675                 timer = setTimeout(execute, interval - elapsed);
2676             }
2677         };
2678     },
2679
2680     /**
2681      * Adds behavior to an existing method that is executed before the
2682      * original behavior of the function.  For example:
2683      * 
2684      *     var soup = {
2685      *         contents: [],
2686      *         add: function(ingredient) {
2687      *             this.contents.push(ingredient);
2688      *         }
2689      *     };
2690      *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
2691      *         if (!this.contents.length && ingredient !== "water") {
2692      *             // Always add water to start with
2693      *             this.contents.push("water");
2694      *         }
2695      *     });
2696      *     soup.add("onions");
2697      *     soup.add("salt");
2698      *     soup.contents; // will contain: water, onions, salt
2699      * 
2700      * @param {Object} object The target object
2701      * @param {String} methodName Name of the method to override
2702      * @param {Function} fn Function with the new behavior.  It will
2703      * be called with the same arguments as the original method.  The
2704      * return value of this function will be the return value of the
2705      * new method.
2706      * @return {Function} The new function just created.
2707      */
2708     interceptBefore: function(object, methodName, fn) {
2709         var method = object[methodName] || Ext.emptyFn;
2710
2711         return object[methodName] = function() {
2712             var ret = fn.apply(this, arguments);
2713             method.apply(this, arguments);
2714
2715             return ret;
2716         };
2717     },
2718
2719     /**
2720      * Adds behavior to an existing method that is executed after the
2721      * original behavior of the function.  For example:
2722      * 
2723      *     var soup = {
2724      *         contents: [],
2725      *         add: function(ingredient) {
2726      *             this.contents.push(ingredient);
2727      *         }
2728      *     };
2729      *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
2730      *         // Always add a bit of extra salt
2731      *         this.contents.push("salt");
2732      *     });
2733      *     soup.add("water");
2734      *     soup.add("onions");
2735      *     soup.contents; // will contain: water, salt, onions, salt
2736      * 
2737      * @param {Object} object The target object
2738      * @param {String} methodName Name of the method to override
2739      * @param {Function} fn Function with the new behavior.  It will
2740      * be called with the same arguments as the original method.  The
2741      * return value of this function will be the return value of the
2742      * new method.
2743      * @return {Function} The new function just created.
2744      */
2745     interceptAfter: function(object, methodName, fn) {
2746         var method = object[methodName] || Ext.emptyFn;
2747
2748         return object[methodName] = function() {
2749             method.apply(this, arguments);
2750             return fn.apply(this, arguments);
2751         };
2752     }
2753 };
2754
2755 /**
2756  * @method
2757  * @member Ext
2758  * @alias Ext.Function#defer
2759  */
2760 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2761
2762 /**
2763  * @method
2764  * @member Ext
2765  * @alias Ext.Function#pass
2766  */
2767 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2768
2769 /**
2770  * @method
2771  * @member Ext
2772  * @alias Ext.Function#bind
2773  */
2774 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2775
2776 /**
2777  * @author Jacky Nguyen <jacky@sencha.com>
2778  * @docauthor Jacky Nguyen <jacky@sencha.com>
2779  * @class Ext.Object
2780  *
2781  * A collection of useful static methods to deal with objects.
2782  *
2783  * @singleton
2784  */
2785
2786 (function() {
2787
2788 var ExtObject = Ext.Object = {
2789
2790     /**
2791      * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
2792      * query strings. For example:
2793      *
2794      *     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2795      *
2796      *     // objects then equals:
2797      *     [
2798      *         { name: 'hobbies', value: 'reading' },
2799      *         { name: 'hobbies', value: 'cooking' },
2800      *         { name: 'hobbies', value: 'swimming' },
2801      *     ];
2802      *
2803      *     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2804      *         day: 3,
2805      *         month: 8,
2806      *         year: 1987,
2807      *         extra: {
2808      *             hour: 4
2809      *             minute: 30
2810      *         }
2811      *     }, true); // Recursive
2812      *
2813      *     // objects then equals:
2814      *     [
2815      *         { name: 'dateOfBirth[day]', value: 3 },
2816      *         { name: 'dateOfBirth[month]', value: 8 },
2817      *         { name: 'dateOfBirth[year]', value: 1987 },
2818      *         { name: 'dateOfBirth[extra][hour]', value: 4 },
2819      *         { name: 'dateOfBirth[extra][minute]', value: 30 },
2820      *     ];
2821      *
2822      * @param {String} name
2823      * @param {Object/Array} value
2824      * @param {Boolean} [recursive=false] True to traverse object recursively
2825      * @return {Array}
2826      */
2827     toQueryObjects: function(name, value, recursive) {
2828         var self = ExtObject.toQueryObjects,
2829             objects = [],
2830             i, ln;
2831
2832         if (Ext.isArray(value)) {
2833             for (i = 0, ln = value.length; i < ln; i++) {
2834                 if (recursive) {
2835                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2836                 }
2837                 else {
2838                     objects.push({
2839                         name: name,
2840                         value: value[i]
2841                     });
2842                 }
2843             }
2844         }
2845         else if (Ext.isObject(value)) {
2846             for (i in value) {
2847                 if (value.hasOwnProperty(i)) {
2848                     if (recursive) {
2849                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2850                     }
2851                     else {
2852                         objects.push({
2853                             name: name,
2854                             value: value[i]
2855                         });
2856                     }
2857                 }
2858             }
2859         }
2860         else {
2861             objects.push({
2862                 name: name,
2863                 value: value
2864             });
2865         }
2866
2867         return objects;
2868     },
2869
2870     /**
2871      * Takes an object and converts it to an encoded query string.
2872      *
2873      * Non-recursive:
2874      *
2875      *     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2876      *     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2877      *     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2878      *     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2879      *     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2880      *
2881      * Recursive:
2882      *
2883      *     Ext.Object.toQueryString({
2884      *         username: 'Jacky',
2885      *         dateOfBirth: {
2886      *             day: 1,
2887      *             month: 2,
2888      *             year: 1911
2889      *         },
2890      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2891      *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2892      *     // username=Jacky
2893      *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2894      *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2895      *
2896      * @param {Object} object The object to encode
2897      * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
2898      * (PHP / Ruby on Rails servers and similar).
2899      * @return {String} queryString
2900      */
2901     toQueryString: function(object, recursive) {
2902         var paramObjects = [],
2903             params = [],
2904             i, j, ln, paramObject, value;
2905
2906         for (i in object) {
2907             if (object.hasOwnProperty(i)) {
2908                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2909             }
2910         }
2911
2912         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2913             paramObject = paramObjects[j];
2914             value = paramObject.value;
2915
2916             if (Ext.isEmpty(value)) {
2917                 value = '';
2918             }
2919             else if (Ext.isDate(value)) {
2920                 value = Ext.Date.toString(value);
2921             }
2922
2923             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2924         }
2925
2926         return params.join('&');
2927     },
2928
2929     /**
2930      * Converts a query string back into an object.
2931      *
2932      * Non-recursive:
2933      *
2934      *     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2935      *     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2936      *     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2937      *     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2938      *
2939      * Recursive:
2940      *
2941      *       Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
2942      *     // returns
2943      *     {
2944      *         username: 'Jacky',
2945      *         dateOfBirth: {
2946      *             day: '1',
2947      *             month: '2',
2948      *             year: '1911'
2949      *         },
2950      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2951      *     }
2952      *
2953      * @param {String} queryString The query string to decode
2954      * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
2955      * PHP / Ruby on Rails servers and similar.
2956      * @return {Object}
2957      */
2958     fromQueryString: function(queryString, recursive) {
2959         var parts = queryString.replace(/^\?/, '').split('&'),
2960             object = {},
2961             temp, components, name, value, i, ln,
2962             part, j, subLn, matchedKeys, matchedName,
2963             keys, key, nextKey;
2964
2965         for (i = 0, ln = parts.length; i < ln; i++) {
2966             part = parts[i];
2967
2968             if (part.length > 0) {
2969                 components = part.split('=');
2970                 name = decodeURIComponent(components[0]);
2971                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2972
2973                 if (!recursive) {
2974                     if (object.hasOwnProperty(name)) {
2975                         if (!Ext.isArray(object[name])) {
2976                             object[name] = [object[name]];
2977                         }
2978
2979                         object[name].push(value);
2980                     }
2981                     else {
2982                         object[name] = value;
2983                     }
2984                 }
2985                 else {
2986                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2987                     matchedName = name.match(/^([^\[]+)/);
2988
2989
2990                     name = matchedName[0];
2991                     keys = [];
2992
2993                     if (matchedKeys === null) {
2994                         object[name] = value;
2995                         continue;
2996                     }
2997
2998                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2999                         key = matchedKeys[j];
3000                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
3001                         keys.push(key);
3002                     }
3003
3004                     keys.unshift(name);
3005
3006                     temp = object;
3007
3008                     for (j = 0, subLn = keys.length; j < subLn; j++) {
3009                         key = keys[j];
3010
3011                         if (j === subLn - 1) {
3012                             if (Ext.isArray(temp) && key === '') {
3013                                 temp.push(value);
3014                             }
3015                             else {
3016                                 temp[key] = value;
3017                             }
3018                         }
3019                         else {
3020                             if (temp[key] === undefined || typeof temp[key] === 'string') {
3021                                 nextKey = keys[j+1];
3022
3023                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
3024                             }
3025
3026                             temp = temp[key];
3027                         }
3028                     }
3029                 }
3030             }
3031         }
3032
3033         return object;
3034     },
3035
3036     /**
3037      * Iterates through an object and invokes the given callback function for each iteration.
3038      * The iteration can be stopped by returning `false` in the callback function. For example:
3039      *
3040      *     var person = {
3041      *         name: 'Jacky'
3042      *         hairColor: 'black'
3043      *         loves: ['food', 'sleeping', 'wife']
3044      *     };
3045      *
3046      *     Ext.Object.each(person, function(key, value, myself) {
3047      *         console.log(key + ":" + value);
3048      *
3049      *         if (key === 'hairColor') {
3050      *             return false; // stop the iteration
3051      *         }
3052      *     });
3053      *
3054      * @param {Object} object The object to iterate
3055      * @param {Function} fn The callback function.
3056      * @param {String} fn.key
3057      * @param {Object} fn.value
3058      * @param {Object} fn.object The object itself
3059      * @param {Object} [scope] The execution scope (`this`) of the callback function
3060      */
3061     each: function(object, fn, scope) {
3062         for (var property in object) {
3063             if (object.hasOwnProperty(property)) {
3064                 if (fn.call(scope || object, property, object[property], object) === false) {
3065                     return;
3066                 }
3067             }
3068         }
3069     },
3070
3071     /**
3072      * Merges any number of objects recursively without referencing them or their children.
3073      *
3074      *     var extjs = {
3075      *         companyName: 'Ext JS',
3076      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
3077      *         isSuperCool: true
3078      *         office: {
3079      *             size: 2000,
3080      *             location: 'Palo Alto',
3081      *             isFun: true
3082      *         }
3083      *     };
3084      *
3085      *     var newStuff = {
3086      *         companyName: 'Sencha Inc.',
3087      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3088      *         office: {
3089      *             size: 40000,
3090      *             location: 'Redwood City'
3091      *         }
3092      *     };
3093      *
3094      *     var sencha = Ext.Object.merge(extjs, newStuff);
3095      *
3096      *     // extjs and sencha then equals to
3097      *     {
3098      *         companyName: 'Sencha Inc.',
3099      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3100      *         isSuperCool: true
3101      *         office: {
3102      *             size: 30000,
3103      *             location: 'Redwood City'
3104      *             isFun: true
3105      *         }
3106      *     }
3107      *
3108      * @param {Object...} object Any number of objects to merge.
3109      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3110      */
3111     merge: function(source, key, value) {
3112         if (typeof key === 'string') {
3113             if (value && value.constructor === Object) {
3114                 if (source[key] && source[key].constructor === Object) {
3115                     ExtObject.merge(source[key], value);
3116                 }
3117                 else {
3118                     source[key] = Ext.clone(value);
3119                 }
3120             }
3121             else {
3122                 source[key] = value;
3123             }
3124
3125             return source;
3126         }
3127
3128         var i = 1,
3129             ln = arguments.length,
3130             object, property;
3131
3132         for (; i < ln; i++) {
3133             object = arguments[i];
3134
3135             for (property in object) {
3136                 if (object.hasOwnProperty(property)) {
3137                     ExtObject.merge(source, property, object[property]);
3138                 }
3139             }
3140         }
3141
3142         return source;
3143     },
3144
3145     /**
3146      * Returns the first matching key corresponding to the given value.
3147      * If no matching value is found, null is returned.
3148      *
3149      *     var person = {
3150      *         name: 'Jacky',
3151      *         loves: 'food'
3152      *     };
3153      *
3154      *     alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
3155      *
3156      * @param {Object} object
3157      * @param {Object} value The value to find
3158      */
3159     getKey: function(object, value) {
3160         for (var property in object) {
3161             if (object.hasOwnProperty(property) && object[property] === value) {
3162                 return property;
3163             }
3164         }
3165
3166         return null;
3167     },
3168
3169     /**
3170      * Gets all values of the given object as an array.
3171      *
3172      *     var values = Ext.Object.getValues({
3173      *         name: 'Jacky',
3174      *         loves: 'food'
3175      *     }); // ['Jacky', 'food']
3176      *
3177      * @param {Object} object
3178      * @return {Array} An array of values from the object
3179      */
3180     getValues: function(object) {
3181         var values = [],
3182             property;
3183
3184         for (property in object) {
3185             if (object.hasOwnProperty(property)) {
3186                 values.push(object[property]);
3187             }
3188         }
3189
3190         return values;
3191     },
3192
3193     /**
3194      * Gets all keys of the given object as an array.
3195      *
3196      *     var values = Ext.Object.getKeys({
3197      *         name: 'Jacky',
3198      *         loves: 'food'
3199      *     }); // ['name', 'loves']
3200      *
3201      * @param {Object} object
3202      * @return {String[]} An array of keys from the object
3203      * @method
3204      */
3205     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3206         var keys = [],
3207             property;
3208
3209         for (property in object) {
3210             if (object.hasOwnProperty(property)) {
3211                 keys.push(property);
3212             }
3213         }
3214
3215         return keys;
3216     },
3217
3218     /**
3219      * Gets the total number of this object's own properties
3220      *
3221      *     var size = Ext.Object.getSize({
3222      *         name: 'Jacky',
3223      *         loves: 'food'
3224      *     }); // size equals 2
3225      *
3226      * @param {Object} object
3227      * @return {Number} size
3228      */
3229     getSize: function(object) {
3230         var size = 0,
3231             property;
3232
3233         for (property in object) {
3234             if (object.hasOwnProperty(property)) {
3235                 size++;
3236             }
3237         }
3238
3239         return size;
3240     }
3241 };
3242
3243
3244 /**
3245  * A convenient alias method for {@link Ext.Object#merge}.
3246  *
3247  * @member Ext
3248  * @method merge
3249  * @alias Ext.Object#merge
3250  */
3251 Ext.merge = Ext.Object.merge;
3252
3253 /**
3254  * Alias for {@link Ext.Object#toQueryString}.
3255  *
3256  * @member Ext
3257  * @method urlEncode
3258  * @alias Ext.Object#toQueryString
3259  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
3260  */
3261 Ext.urlEncode = function() {
3262     var args = Ext.Array.from(arguments),
3263         prefix = '';
3264
3265     // Support for the old `pre` argument
3266     if ((typeof args[1] === 'string')) {
3267         prefix = args[1] + '&';
3268         args[1] = false;
3269     }
3270
3271     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3272 };
3273
3274 /**
3275  * Alias for {@link Ext.Object#fromQueryString}.
3276  *
3277  * @member Ext
3278  * @method urlDecode
3279  * @alias Ext.Object#fromQueryString
3280  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
3281  */
3282 Ext.urlDecode = function() {
3283     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3284 };
3285
3286 })();
3287
3288 /**
3289  * @class Ext.Date
3290  * A set of useful static methods to deal with date
3291  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3292  * this object for convenience
3293  *
3294  * The date parsing and formatting syntax contains a subset of
3295  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3296  * supported will provide results equivalent to their PHP versions.
3297  *
3298  * The following is a list of all currently supported formats:
3299  * <pre class="">
3300 Format  Description                                                               Example returned values
3301 ------  -----------------------------------------------------------------------   -----------------------
3302   d     Day of the month, 2 digits with leading zeros                             01 to 31
3303   D     A short textual representation of the day of the week                     Mon to Sun
3304   j     Day of the month without leading zeros                                    1 to 31
3305   l     A full textual representation of the day of the week                      Sunday to Saturday
3306   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
3307   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
3308   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
3309   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
3310   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
3311   F     A full textual representation of a month, such as January or March        January to December
3312   m     Numeric representation of a month, with leading zeros                     01 to 12
3313   M     A short textual representation of a month                                 Jan to Dec
3314   n     Numeric representation of a month, without leading zeros                  1 to 12
3315   t     Number of days in the given month                                         28 to 31
3316   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
3317   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
3318         belongs to the previous or next year, that year is used instead)
3319   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
3320   y     A two digit representation of a year                                      Examples: 99 or 03
3321   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
3322   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
3323   g     12-hour format of an hour without leading zeros                           1 to 12
3324   G     24-hour format of an hour without leading zeros                           0 to 23
3325   h     12-hour format of an hour with leading zeros                              01 to 12
3326   H     24-hour format of an hour with leading zeros                              00 to 23
3327   i     Minutes, with leading zeros                                               00 to 59
3328   s     Seconds, with leading zeros                                               00 to 59
3329   u     Decimal fraction of a second                                              Examples:
3330         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
3331                                                                                   100 (i.e. 0.100s) or
3332                                                                                   999 (i.e. 0.999s) or
3333                                                                                   999876543210 (i.e. 0.999876543210s)
3334   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
3335   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
3336   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
3337   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
3338   c     ISO 8601 date
3339         Notes:                                                                    Examples:
3340         1) If unspecified, the month / day defaults to the current month / day,   1991 or
3341            the time defaults to midnight, while the timezone defaults to the      1992-10 or
3342            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3343            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
3344            are optional.                                                          1995-07-18T17:21:28-02:00 or
3345         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
3346            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
3347            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
3348         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
3349         date-time granularity which are supported, or see                         2000-02-13T21:25:33
3350         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
3351   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
3352   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3353                                                                                   \/Date(1238606590509+0800)\/
3354 </pre>
3355  *
3356  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3357  * <pre><code>
3358 // Sample date:
3359 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3360
3361 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3362 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
3363 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
3364 console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
3365 </code></pre>
3366  *
3367  * Here are some standard date/time patterns that you might find helpful.  They
3368  * are not part of the source of Ext.Date, but to use them you can simply copy this
3369  * block of code into any script that is included after Ext.Date and they will also become
3370  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
3371  * <pre><code>
3372 Ext.Date.patterns = {
3373     ISO8601Long:"Y-m-d H:i:s",
3374     ISO8601Short:"Y-m-d",
3375     ShortDate: "n/j/Y",
3376     LongDate: "l, F d, Y",
3377     FullDateTime: "l, F d, Y g:i:s A",
3378     MonthDay: "F d",
3379     ShortTime: "g:i A",
3380     LongTime: "g:i:s A",
3381     SortableDateTime: "Y-m-d\\TH:i:s",
3382     UniversalSortableDateTime: "Y-m-d H:i:sO",
3383     YearMonth: "F, Y"
3384 };
3385 </code></pre>
3386  *
3387  * Example usage:
3388  * <pre><code>
3389 var dt = new Date();
3390 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3391 </code></pre>
3392  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3393  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3394  * @singleton
3395  */
3396
3397 /*
3398  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3399  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3400  * They generate precompiled functions from format patterns instead of parsing and
3401  * processing each pattern every time a date is formatted. These functions are available
3402  * on every Date object.
3403  */
3404
3405 (function() {
3406
3407 // create private copy of Ext's Ext.util.Format.format() method
3408 // - to remove unnecessary dependency
3409 // - to resolve namespace conflict with MS-Ajax's implementation
3410 function xf(format) {
3411     var args = Array.prototype.slice.call(arguments, 1);
3412     return format.replace(/\{(\d+)\}/g, function(m, i) {
3413         return args[i];
3414     });
3415 }
3416
3417 Ext.Date = {
3418     /**
3419      * Returns the current timestamp
3420      * @return {Date} The current timestamp
3421      * @method
3422      */
3423     now: Date.now || function() {
3424         return +new Date();
3425     },
3426
3427     /**
3428      * @private
3429      * Private for now
3430      */
3431     toString: function(date) {
3432         var pad = Ext.String.leftPad;
3433
3434         return date.getFullYear() + "-"
3435             + pad(date.getMonth() + 1, 2, '0') + "-"
3436             + pad(date.getDate(), 2, '0') + "T"
3437             + pad(date.getHours(), 2, '0') + ":"
3438             + pad(date.getMinutes(), 2, '0') + ":"
3439             + pad(date.getSeconds(), 2, '0');
3440     },
3441
3442     /**
3443      * Returns the number of milliseconds between two dates
3444      * @param {Date} dateA The first date
3445      * @param {Date} dateB (optional) The second date, defaults to now
3446      * @return {Number} The difference in milliseconds
3447      */
3448     getElapsed: function(dateA, dateB) {
3449         return Math.abs(dateA - (dateB || new Date()));
3450     },
3451
3452     /**
3453      * Global flag which determines if strict date parsing should be used.
3454      * Strict date parsing will not roll-over invalid dates, which is the
3455      * default behaviour of javascript Date objects.
3456      * (see {@link #parse} for more information)
3457      * Defaults to <tt>false</tt>.
3458      * @type Boolean
3459     */
3460     useStrict: false,
3461
3462     // private
3463     formatCodeToRegex: function(character, currentGroup) {
3464         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3465         var p = utilDate.parseCodes[character];
3466
3467         if (p) {
3468           p = typeof p == 'function'? p() : p;
3469           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3470         }
3471
3472         return p ? Ext.applyIf({
3473           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3474         }, p) : {
3475             g: 0,
3476             c: null,
3477             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3478         };
3479     },
3480
3481     /**
3482      * <p>An object hash in which each property is a date parsing function. The property name is the
3483      * format string which that function parses.</p>
3484      * <p>This object is automatically populated with date parsing functions as
3485      * date formats are requested for Ext standard formatting strings.</p>
3486      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3487      * may be used as a format string to {@link #parse}.<p>
3488      * <p>Example:</p><pre><code>
3489 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3490 </code></pre>
3491      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3492      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3493      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3494      * (i.e. prevent javascript Date "rollover") (The default must be false).
3495      * Invalid date strings should return null when parsed.</div></li>
3496      * </ul></div></p>
3497      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3498      * formatting function must be placed into the {@link #formatFunctions} property.
3499      * @property parseFunctions
3500      * @type Object
3501      */
3502     parseFunctions: {
3503         "MS": function(input, strict) {
3504             // note: the timezone offset is ignored since the MS Ajax server sends
3505             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3506             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3507             var r = (input || '').match(re);
3508             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3509         }
3510     },
3511     parseRegexes: [],
3512
3513     /**
3514      * <p>An object hash in which each property is a date formatting function. The property name is the
3515      * format string which corresponds to the produced formatted date string.</p>
3516      * <p>This object is automatically populated with date formatting functions as
3517      * date formats are requested for Ext standard formatting strings.</p>
3518      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3519      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3520 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3521 </code></pre>
3522      * <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3523      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3524      * </ul></div></p>
3525      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3526      * parsing function must be placed into the {@link #parseFunctions} property.
3527      * @property formatFunctions
3528      * @type Object
3529      */
3530     formatFunctions: {
3531         "MS": function() {
3532             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3533             return '\\/Date(' + this.getTime() + ')\\/';
3534         }
3535     },
3536
3537     y2kYear : 50,
3538
3539     /**
3540      * Date interval constant
3541      * @type String
3542      */
3543     MILLI : "ms",
3544
3545     /**
3546      * Date interval constant
3547      * @type String
3548      */
3549     SECOND : "s",
3550
3551     /**
3552      * Date interval constant
3553      * @type String
3554      */
3555     MINUTE : "mi",
3556
3557     /** Date interval constant
3558      * @type String
3559      */
3560     HOUR : "h",
3561
3562     /**
3563      * Date interval constant
3564      * @type String
3565      */
3566     DAY : "d",
3567
3568     /**
3569      * Date interval constant
3570      * @type String
3571      */
3572     MONTH : "mo",
3573
3574     /**
3575      * Date interval constant
3576      * @type String
3577      */
3578     YEAR : "y",
3579
3580     /**
3581      * <p>An object hash containing default date values used during date parsing.</p>
3582      * <p>The following properties are available:<div class="mdetail-params"><ul>
3583      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3584      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3585      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3586      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3587      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3588      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3589      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3590      * </ul></div></p>
3591      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3592      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3593      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3594      * It is the responsiblity of the developer to account for this.</b></p>
3595      * Example Usage:
3596      * <pre><code>
3597 // set default day value to the first day of the month
3598 Ext.Date.defaults.d = 1;
3599
3600 // parse a February date string containing only year and month values.
3601 // setting the default day value to 1 prevents weird date rollover issues
3602 // when attempting to parse the following date string on, for example, March 31st 2009.
3603 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3604 </code></pre>
3605      * @property defaults
3606      * @type Object
3607      */
3608     defaults: {},
3609
3610     /**
3611      * @property {String[]} dayNames
3612      * An array of textual day names.
3613      * Override these values for international dates.
3614      * Example:
3615      * <pre><code>
3616 Ext.Date.dayNames = [
3617     'SundayInYourLang',
3618     'MondayInYourLang',
3619     ...
3620 ];
3621 </code></pre>
3622      */
3623     dayNames : [
3624         "Sunday",
3625         "Monday",
3626         "Tuesday",
3627         "Wednesday",
3628         "Thursday",
3629         "Friday",
3630         "Saturday"
3631     ],
3632
3633     /**
3634      * @property {String[]} monthNames
3635      * An array of textual month names.
3636      * Override these values for international dates.
3637      * Example:
3638      * <pre><code>
3639 Ext.Date.monthNames = [
3640     'JanInYourLang',
3641     'FebInYourLang',
3642     ...
3643 ];
3644 </code></pre>
3645      */
3646     monthNames : [
3647         "January",
3648         "February",
3649         "March",
3650         "April",
3651         "May",
3652         "June",
3653         "July",
3654         "August",
3655         "September",
3656         "October",
3657         "November",
3658         "December"
3659     ],
3660
3661     /**
3662      * @property {Object} monthNumbers
3663      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3664      * Override these values for international dates.
3665      * Example:
3666      * <pre><code>
3667 Ext.Date.monthNumbers = {
3668     'ShortJanNameInYourLang':0,
3669     'ShortFebNameInYourLang':1,
3670     ...
3671 };
3672 </code></pre>
3673      */
3674     monthNumbers : {
3675         Jan:0,
3676         Feb:1,
3677         Mar:2,
3678         Apr:3,
3679         May:4,
3680         Jun:5,
3681         Jul:6,
3682         Aug:7,
3683         Sep:8,
3684         Oct:9,
3685         Nov:10,
3686         Dec:11
3687     },
3688     /**
3689      * @property {String} defaultFormat
3690      * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3691      * and {@link Ext.util.Format#date} functions use.  See {@link Ext.Date} for details.</p>
3692      * <p>This may be overridden in a locale file.</p>
3693      */
3694     defaultFormat : "m/d/Y",
3695     /**
3696      * Get the short month name for the given month number.
3697      * Override this function for international dates.
3698      * @param {Number} month A zero-based javascript month number.
3699      * @return {String} The short month name.
3700      */
3701     getShortMonthName : function(month) {
3702         return utilDate.monthNames[month].substring(0, 3);
3703     },
3704
3705     /**
3706      * Get the short day name for the given day number.
3707      * Override this function for international dates.
3708      * @param {Number} day A zero-based javascript day number.
3709      * @return {String} The short day name.
3710      */
3711     getShortDayName : function(day) {
3712         return utilDate.dayNames[day].substring(0, 3);
3713     },
3714
3715     /**
3716      * Get the zero-based javascript month number for the given short/full month name.
3717      * Override this function for international dates.
3718      * @param {String} name The short/full month name.
3719      * @return {Number} The zero-based javascript month number.
3720      */
3721     getMonthNumber : function(name) {
3722         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3723         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3724     },
3725
3726     /**
3727      * Checks if the specified format contains hour information
3728      * @param {String} format The format to check
3729      * @return {Boolean} True if the format contains hour information
3730      * @method
3731      */
3732     formatContainsHourInfo : (function(){
3733         var stripEscapeRe = /(\\.)/g,
3734             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3735         return function(format){
3736             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3737         };
3738     })(),
3739
3740     /**
3741      * Checks if the specified format contains information about
3742      * anything other than the time.
3743      * @param {String} format The format to check
3744      * @return {Boolean} True if the format contains information about
3745      * date/day information.
3746      * @method
3747      */
3748     formatContainsDateInfo : (function(){
3749         var stripEscapeRe = /(\\.)/g,
3750             dateInfoRe = /([djzmnYycU]|MS)/;
3751
3752         return function(format){
3753             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3754         };
3755     })(),
3756
3757     /**
3758      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3759      * Formatting functions are strings (or functions which return strings) which
3760      * will return the appropriate value when evaluated in the context of the Date object
3761      * from which the {@link #format} method is called.
3762      * Add to / override these mappings for custom date formatting.
3763      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3764      * Example:
3765      * <pre><code>
3766 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3767 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3768 </code></pre>
3769      * @type Object
3770      */
3771     formatCodes : {
3772         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3773         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3774         j: "this.getDate()",
3775         l: "Ext.Date.dayNames[this.getDay()]",
3776         N: "(this.getDay() ? this.getDay() : 7)",
3777         S: "Ext.Date.getSuffix(this)",
3778         w: "this.getDay()",
3779         z: "Ext.Date.getDayOfYear(this)",
3780         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3781         F: "Ext.Date.monthNames[this.getMonth()]",
3782         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3783         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3784         n: "(this.getMonth() + 1)",
3785         t: "Ext.Date.getDaysInMonth(this)",
3786         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3787         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3788         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3789         y: "('' + this.getFullYear()).substring(2, 4)",
3790         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3791         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3792         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3793         G: "this.getHours()",
3794         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3795         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3796         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3797         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3798         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3799         O: "Ext.Date.getGMTOffset(this)",
3800         P: "Ext.Date.getGMTOffset(this, true)",
3801         T: "Ext.Date.getTimezone(this)",
3802         Z: "(this.getTimezoneOffset() * -60)",
3803
3804         c: function() { // ISO-8601 -- GMT format
3805             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3806                 var e = c.charAt(i);
3807                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3808             }
3809             return code.join(" + ");
3810         },
3811         /*
3812         c: function() { // ISO-8601 -- UTC format
3813             return [
3814               "this.getUTCFullYear()", "'-'",
3815               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3816               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3817               "'T'",
3818               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3819               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3820               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3821               "'Z'"
3822             ].join(" + ");
3823         },
3824         */
3825
3826         U: "Math.round(this.getTime() / 1000)"
3827     },
3828
3829     /**
3830      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3831      * @param {Number} year 4-digit year
3832      * @param {Number} month 1-based month-of-year
3833      * @param {Number} day Day of month
3834      * @param {Number} hour (optional) Hour
3835      * @param {Number} minute (optional) Minute
3836      * @param {Number} second (optional) Second
3837      * @param {Number} millisecond (optional) Millisecond
3838      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3839      */
3840     isValid : function(y, m, d, h, i, s, ms) {
3841         // setup defaults
3842         h = h || 0;
3843         i = i || 0;
3844         s = s || 0;
3845         ms = ms || 0;
3846
3847         // Special handling for year < 100
3848         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3849
3850         return y == dt.getFullYear() &&
3851             m == dt.getMonth() + 1 &&
3852             d == dt.getDate() &&
3853             h == dt.getHours() &&
3854             i == dt.getMinutes() &&
3855             s == dt.getSeconds() &&
3856             ms == dt.getMilliseconds();
3857     },
3858
3859     /**
3860      * Parses the passed string using the specified date format.
3861      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3862      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3863      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3864      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3865      * Keep in mind that the input date string must precisely match the specified format string
3866      * in order for the parse operation to be successful (failed parse operations return a null value).
3867      * <p>Example:</p><pre><code>
3868 //dt = Fri May 25 2007 (current date)
3869 var dt = new Date();
3870
3871 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3872 dt = Ext.Date.parse("2006", "Y");
3873
3874 //dt = Sun Jan 15 2006 (all date parts specified)
3875 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3876
3877 //dt = Sun Jan 15 2006 15:20:01
3878 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3879
3880 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3881 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3882 </code></pre>
3883      * @param {String} input The raw date string.
3884      * @param {String} format The expected date string format.
3885      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3886                         (defaults to false). Invalid date strings will return null when parsed.
3887      * @return {Date} The parsed Date.
3888      */
3889     parse : function(input, format, strict) {
3890         var p = utilDate.parseFunctions;
3891         if (p[format] == null) {
3892             utilDate.createParser(format);
3893         }
3894         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3895     },
3896
3897     // Backwards compat
3898     parseDate: function(input, format, strict){
3899         return utilDate.parse(input, format, strict);
3900     },
3901
3902
3903     // private
3904     getFormatCode : function(character) {
3905         var f = utilDate.formatCodes[character];
3906
3907         if (f) {
3908           f = typeof f == 'function'? f() : f;
3909           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3910         }
3911
3912         // note: unknown characters are treated as literals
3913         return f || ("'" + Ext.String.escape(character) + "'");
3914     },
3915
3916     // private
3917     createFormat : function(format) {
3918         var code = [],
3919             special = false,
3920             ch = '';
3921
3922         for (var i = 0; i < format.length; ++i) {
3923             ch = format.charAt(i);
3924             if (!special && ch == "\\") {
3925                 special = true;
3926             } else if (special) {
3927                 special = false;
3928                 code.push("'" + Ext.String.escape(ch) + "'");
3929             } else {
3930                 code.push(utilDate.getFormatCode(ch));
3931             }
3932         }
3933         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3934     },
3935
3936     // private
3937     createParser : (function() {
3938         var code = [
3939             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3940                 "def = Ext.Date.defaults,",
3941                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3942
3943             "if(results){",
3944                 "{1}",
3945
3946                 "if(u != null){", // i.e. unix time is defined
3947                     "v = new Date(u * 1000);", // give top priority to UNIX time
3948                 "}else{",
3949                     // create Date object representing midnight of the current day;
3950                     // this will provide us with our date defaults
3951                     // (note: clearTime() handles Daylight Saving Time automatically)
3952                     "dt = Ext.Date.clearTime(new Date);",
3953
3954                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3955                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3956                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3957                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3958
3959                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3960                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3961                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3962                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3963                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3964
3965                     "if(z >= 0 && y >= 0){",
3966                         // both the year and zero-based day of year are defined and >= 0.
3967                         // these 2 values alone provide sufficient info to create a full date object
3968
3969                         // create Date object representing January 1st for the given year
3970                         // handle years < 100 appropriately
3971                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3972
3973                         // then add day of year, checking for Date "rollover" if necessary
3974                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3975                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3976                         "v = null;", // invalid date, so return null
3977                     "}else{",
3978                         // plain old Date object
3979                         // handle years < 100 properly
3980                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3981                     "}",
3982                 "}",
3983             "}",
3984
3985             "if(v){",
3986                 // favour UTC offset over GMT offset
3987                 "if(zz != null){",
3988                     // reset to UTC, then add offset
3989                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3990                 "}else if(o){",
3991                     // reset to GMT, then add offset
3992                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3993                 "}",
3994             "}",
3995
3996             "return v;"
3997         ].join('\n');
3998
3999         return function(format) {
4000             var regexNum = utilDate.parseRegexes.length,
4001                 currentGroup = 1,
4002                 calc = [],
4003                 regex = [],
4004                 special = false,
4005                 ch = "";
4006
4007             for (var i = 0; i < format.length; ++i) {
4008                 ch = format.charAt(i);
4009                 if (!special && ch == "\\") {
4010                     special = true;
4011                 } else if (special) {
4012                     special = false;
4013                     regex.push(Ext.String.escape(ch));
4014                 } else {
4015                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
4016                     currentGroup += obj.g;
4017                     regex.push(obj.s);
4018                     if (obj.g && obj.c) {
4019                         calc.push(obj.c);
4020                     }
4021                 }
4022             }
4023
4024             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
4025             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
4026         };
4027     })(),
4028
4029     // private
4030     parseCodes : {
4031         /*
4032          * Notes:
4033          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
4034          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
4035          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
4036          */
4037         d: {
4038             g:1,
4039             c:"d = parseInt(results[{0}], 10);\n",
4040             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
4041         },
4042         j: {
4043             g:1,
4044             c:"d = parseInt(results[{0}], 10);\n",
4045             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
4046         },
4047         D: function() {
4048             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
4049             return {
4050                 g:0,
4051                 c:null,
4052                 s:"(?:" + a.join("|") +")"
4053             };
4054         },
4055         l: function() {
4056             return {
4057                 g:0,
4058                 c:null,
4059                 s:"(?:" + utilDate.dayNames.join("|") + ")"
4060             };
4061         },
4062         N: {
4063             g:0,
4064             c:null,
4065             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4066         },
4067         S: {
4068             g:0,
4069             c:null,
4070             s:"(?:st|nd|rd|th)"
4071         },
4072         w: {
4073             g:0,
4074             c:null,
4075             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4076         },
4077         z: {
4078             g:1,
4079             c:"z = parseInt(results[{0}], 10);\n",
4080             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4081         },
4082         W: {
4083             g:0,
4084             c:null,
4085             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4086         },
4087         F: function() {
4088             return {
4089                 g:1,
4090                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4091                 s:"(" + utilDate.monthNames.join("|") + ")"
4092             };
4093         },
4094         M: function() {
4095             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4096             return Ext.applyIf({
4097                 s:"(" + a.join("|") + ")"
4098             }, utilDate.formatCodeToRegex("F"));
4099         },
4100         m: {
4101             g:1,
4102             c:"m = parseInt(results[{0}], 10) - 1;\n",
4103             s:"(\\d{2})" // month number with leading zeros (01 - 12)
4104         },
4105         n: {
4106             g:1,
4107             c:"m = parseInt(results[{0}], 10) - 1;\n",
4108             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4109         },
4110         t: {
4111             g:0,
4112             c:null,
4113             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4114         },
4115         L: {
4116             g:0,
4117             c:null,
4118             s:"(?:1|0)"
4119         },
4120         o: function() {
4121             return utilDate.formatCodeToRegex("Y");
4122         },
4123         Y: {
4124             g:1,
4125             c:"y = parseInt(results[{0}], 10);\n",
4126             s:"(\\d{4})" // 4-digit year
4127         },
4128         y: {
4129             g:1,
4130             c:"var ty = parseInt(results[{0}], 10);\n"
4131                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4132             s:"(\\d{1,2})"
4133         },
4134         /*
4135          * In the am/pm parsing routines, we allow both upper and lower case
4136          * even though it doesn't exactly match the spec. It gives much more flexibility
4137          * in being able to specify case insensitive regexes.
4138          */
4139         a: {
4140             g:1,
4141             c:"if (/(am)/i.test(results[{0}])) {\n"
4142                 + "if (!h || h == 12) { h = 0; }\n"
4143                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4144             s:"(am|pm|AM|PM)"
4145         },
4146         A: {
4147             g:1,
4148             c:"if (/(am)/i.test(results[{0}])) {\n"
4149                 + "if (!h || h == 12) { h = 0; }\n"
4150                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4151             s:"(AM|PM|am|pm)"
4152         },
4153         g: function() {
4154             return utilDate.formatCodeToRegex("G");
4155         },
4156         G: {
4157             g:1,
4158             c:"h = parseInt(results[{0}], 10);\n",
4159             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4160         },
4161         h: function() {
4162             return utilDate.formatCodeToRegex("H");
4163         },
4164         H: {
4165             g:1,
4166             c:"h = parseInt(results[{0}], 10);\n",
4167             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
4168         },
4169         i: {
4170             g:1,
4171             c:"i = parseInt(results[{0}], 10);\n",
4172             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4173         },
4174         s: {
4175             g:1,
4176             c:"s = parseInt(results[{0}], 10);\n",
4177             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4178         },
4179         u: {
4180             g:1,
4181             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4182             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4183         },
4184         O: {
4185             g:1,
4186             c:[
4187                 "o = results[{0}];",
4188                 "var sn = o.substring(0,1),", // get + / - sign
4189                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4190                     "mn = o.substring(3,5) % 60;", // get minutes
4191                 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
4192             ].join("\n"),
4193             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4194         },
4195         P: {
4196             g:1,
4197             c:[
4198                 "o = results[{0}];",
4199                 "var sn = o.substring(0,1),", // get + / - sign
4200                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4201                     "mn = o.substring(4,6) % 60;", // get minutes
4202                 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
4203             ].join("\n"),
4204             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4205         },
4206         T: {
4207             g:0,
4208             c:null,
4209             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4210         },
4211         Z: {
4212             g:1,
4213             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4214                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4215             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4216         },
4217         c: function() {
4218             var calc = [],
4219                 arr = [
4220                     utilDate.formatCodeToRegex("Y", 1), // year
4221                     utilDate.formatCodeToRegex("m", 2), // month
4222                     utilDate.formatCodeToRegex("d", 3), // day
4223                     utilDate.formatCodeToRegex("h", 4), // hour
4224                     utilDate.formatCodeToRegex("i", 5), // minute
4225                     utilDate.formatCodeToRegex("s", 6), // second
4226                     {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4227                     {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
4228                         "if(results[8]) {", // timezone specified
4229                             "if(results[8] == 'Z'){",
4230                                 "zz = 0;", // UTC
4231                             "}else if (results[8].indexOf(':') > -1){",
4232                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4233                             "}else{",
4234                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4235                             "}",
4236                         "}"
4237                     ].join('\n')}
4238                 ];
4239
4240             for (var i = 0, l = arr.length; i < l; ++i) {
4241                 calc.push(arr[i].c);
4242             }
4243
4244             return {
4245                 g:1,
4246                 c:calc.join(""),
4247                 s:[
4248                     arr[0].s, // year (required)
4249                     "(?:", "-", arr[1].s, // month (optional)
4250                         "(?:", "-", arr[2].s, // day (optional)
4251                             "(?:",
4252                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4253                                 arr[3].s, ":", arr[4].s,  // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
4254                                 "(?::", arr[5].s, ")?", // seconds (optional)
4255                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4256                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4257                             ")?",
4258                         ")?",
4259                     ")?"
4260                 ].join("")
4261             };
4262         },
4263         U: {
4264             g:1,
4265             c:"u = parseInt(results[{0}], 10);\n",
4266             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4267         }
4268     },
4269
4270     //Old Ext.Date prototype methods.
4271     // private
4272     dateFormat: function(date, format) {
4273         return utilDate.format(date, format);
4274     },
4275
4276     /**
4277      * Formats a date given the supplied format string.
4278      * @param {Date} date The date to format
4279      * @param {String} format The format string
4280      * @return {String} The formatted date
4281      */
4282     format: function(date, format) {
4283         if (utilDate.formatFunctions[format] == null) {
4284             utilDate.createFormat(format);
4285         }
4286         var result = utilDate.formatFunctions[format].call(date);
4287         return result + '';
4288     },
4289
4290     /**
4291      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4292      *
4293      * Note: The date string returned by the javascript Date object's toString() method varies
4294      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4295      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4296      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4297      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4298      * from the GMT offset portion of the date string.
4299      * @param {Date} date The date
4300      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4301      */
4302     getTimezone : function(date) {
4303         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4304         //
4305         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4306         // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
4307         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4308         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4309         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4310         //
4311         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4312         // step 1: (?:\((.*)\) -- find timezone in parentheses
4313         // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
4314         // step 3: remove all non uppercase characters found in step 1 and 2
4315         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4316     },
4317
4318     /**
4319      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4320      * @param {Date} date The date
4321      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4322      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4323      */
4324     getGMTOffset : function(date, colon) {
4325         var offset = date.getTimezoneOffset();
4326         return (offset > 0 ? "-" : "+")
4327             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4328             + (colon ? ":" : "")
4329             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4330     },
4331
4332     /**
4333      * Get the numeric day number of the year, adjusted for leap year.
4334      * @param {Date} date The date
4335      * @return {Number} 0 to 364 (365 in leap years).
4336      */
4337     getDayOfYear: function(date) {
4338         var num = 0,
4339             d = Ext.Date.clone(date),
4340             m = date.getMonth(),
4341             i;
4342
4343         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4344             num += utilDate.getDaysInMonth(d);
4345         }
4346         return num + date.getDate() - 1;
4347     },
4348
4349     /**
4350      * Get the numeric ISO-8601 week number of the year.
4351      * (equivalent to the format specifier 'W', but without a leading zero).
4352      * @param {Date} date The date
4353      * @return {Number} 1 to 53
4354      * @method
4355      */
4356     getWeekOfYear : (function() {
4357         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4358         var ms1d = 864e5, // milliseconds in a day
4359             ms7d = 7 * ms1d; // milliseconds in a week
4360
4361         return function(date) { // return a closure so constants get calculated only once
4362             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4363                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4364                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4365
4366             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4367         };
4368     })(),
4369
4370     /**
4371      * Checks if the current date falls within a leap year.
4372      * @param {Date} date The date
4373      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4374      */
4375     isLeapYear : function(date) {
4376         var year = date.getFullYear();
4377         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4378     },
4379
4380     /**
4381      * Get the first day of the current month, adjusted for leap year.  The returned value
4382      * is the numeric day index within the week (0-6) which can be used in conjunction with
4383      * the {@link #monthNames} array to retrieve the textual day name.
4384      * Example:
4385      * <pre><code>
4386 var dt = new Date('1/10/2007'),
4387     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4388 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4389      * </code></pre>
4390      * @param {Date} date The date
4391      * @return {Number} The day number (0-6).
4392      */
4393     getFirstDayOfMonth : function(date) {
4394         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4395         return (day < 0) ? (day + 7) : day;
4396     },
4397
4398     /**
4399      * Get the last day of the current month, adjusted for leap year.  The returned value
4400      * is the numeric day index within the week (0-6) which can be used in conjunction with
4401      * the {@link #monthNames} array to retrieve the textual day name.
4402      * Example:
4403      * <pre><code>
4404 var dt = new Date('1/10/2007'),
4405     lastDay = Ext.Date.getLastDayOfMonth(dt);
4406 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4407      * </code></pre>
4408      * @param {Date} date The date
4409      * @return {Number} The day number (0-6).
4410      */
4411     getLastDayOfMonth : function(date) {
4412         return utilDate.getLastDateOfMonth(date).getDay();
4413     },
4414
4415
4416     /**
4417      * Get the date of the first day of the month in which this date resides.
4418      * @param {Date} date The date
4419      * @return {Date}
4420      */
4421     getFirstDateOfMonth : function(date) {
4422         return new Date(date.getFullYear(), date.getMonth(), 1);
4423     },
4424
4425     /**
4426      * Get the date of the last day of the month in which this date resides.
4427      * @param {Date} date The date
4428      * @return {Date}
4429      */
4430     getLastDateOfMonth : function(date) {
4431         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4432     },
4433
4434     /**
4435      * Get the number of days in the current month, adjusted for leap year.
4436      * @param {Date} date The date
4437      * @return {Number} The number of days in the month.
4438      * @method
4439      */
4440     getDaysInMonth: (function() {
4441         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4442
4443         return function(date) { // return a closure for efficiency
4444             var m = date.getMonth();
4445
4446             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4447         };
4448     })(),
4449
4450     /**
4451      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4452      * @param {Date} date The date
4453      * @return {String} 'st, 'nd', 'rd' or 'th'.
4454      */
4455     getSuffix : function(date) {
4456         switch (date.getDate()) {
4457             case 1:
4458             case 21:
4459             case 31:
4460                 return "st";
4461             case 2:
4462             case 22:
4463                 return "nd";
4464             case 3:
4465             case 23:
4466                 return "rd";
4467             default:
4468                 return "th";
4469         }
4470     },
4471
4472     /**
4473      * Creates and returns a new Date instance with the exact same date value as the called instance.
4474      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4475      * variable will also be changed.  When the intention is to create a new variable that will not
4476      * modify the original instance, you should create a clone.
4477      *
4478      * Example of correctly cloning a date:
4479      * <pre><code>
4480 //wrong way:
4481 var orig = new Date('10/1/2006');
4482 var copy = orig;
4483 copy.setDate(5);
4484 console.log(orig);  //returns 'Thu Oct 05 2006'!
4485
4486 //correct way:
4487 var orig = new Date('10/1/2006'),
4488     copy = Ext.Date.clone(orig);
4489 copy.setDate(5);
4490 console.log(orig);  //returns 'Thu Oct 01 2006'
4491      * </code></pre>
4492      * @param {Date} date The date
4493      * @return {Date} The new Date instance.
4494      */
4495     clone : function(date) {
4496         return new Date(date.getTime());
4497     },
4498
4499     /**
4500      * Checks if the current date is affected by Daylight Saving Time (DST).
4501      * @param {Date} date The date
4502      * @return {Boolean} True if the current date is affected by DST.
4503      */
4504     isDST : function(date) {
4505         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4506         // courtesy of @geoffrey.mcgill
4507         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4508     },
4509
4510     /**
4511      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4512      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4513      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4514      * @param {Date} date The date
4515      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4516      * @return {Date} this or the clone.
4517      */
4518     clearTime : function(date, clone) {
4519         if (clone) {
4520             return Ext.Date.clearTime(Ext.Date.clone(date));
4521         }
4522
4523         // get current date before clearing time
4524         var d = date.getDate();
4525
4526         // clear time
4527         date.setHours(0);
4528         date.setMinutes(0);
4529         date.setSeconds(0);
4530         date.setMilliseconds(0);
4531
4532         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4533             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4534             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4535
4536             // increment hour until cloned date == current date
4537             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4538
4539             date.setDate(d);
4540             date.setHours(c.getHours());
4541         }
4542
4543         return date;
4544     },
4545
4546     /**
4547      * Provides a convenient method for performing basic date arithmetic. This method
4548      * does not modify the Date instance being called - it creates and returns
4549      * a new Date instance containing the resulting date value.
4550      *
4551      * Examples:
4552      * <pre><code>
4553 // Basic usage:
4554 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4555 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4556
4557 // Negative values will be subtracted:
4558 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4559 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4560
4561      * </code></pre>
4562      *
4563      * @param {Date} date The date to modify
4564      * @param {String} interval A valid date interval enum value.
4565      * @param {Number} value The amount to add to the current date.
4566      * @return {Date} The new Date instance.
4567      */
4568     add : function(date, interval, value) {
4569         var d = Ext.Date.clone(date),
4570             Date = Ext.Date;
4571         if (!interval || value === 0) return d;
4572
4573         switch(interval.toLowerCase()) {
4574             case Ext.Date.MILLI:
4575                 d.setMilliseconds(d.getMilliseconds() + value);
4576                 break;
4577             case Ext.Date.SECOND:
4578                 d.setSeconds(d.getSeconds() + value);
4579                 break;
4580             case Ext.Date.MINUTE:
4581                 d.setMinutes(d.getMinutes() + value);
4582                 break;
4583             case Ext.Date.HOUR:
4584                 d.setHours(d.getHours() + value);
4585                 break;
4586             case Ext.Date.DAY:
4587                 d.setDate(d.getDate() + value);
4588                 break;
4589             case Ext.Date.MONTH:
4590                 var day = date.getDate();
4591                 if (day > 28) {
4592                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4593                 }
4594                 d.setDate(day);
4595                 d.setMonth(date.getMonth() + value);
4596                 break;
4597             case Ext.Date.YEAR:
4598                 d.setFullYear(date.getFullYear() + value);
4599                 break;
4600         }
4601         return d;
4602     },
4603
4604     /**
4605      * Checks if a date falls on or between the given start and end dates.
4606      * @param {Date} date The date to check
4607      * @param {Date} start Start date
4608      * @param {Date} end End date
4609      * @return {Boolean} true if this date falls on or between the given start and end dates.
4610      */
4611     between : function(date, start, end) {
4612         var t = date.getTime();
4613         return start.getTime() <= t && t <= end.getTime();
4614     },
4615
4616     //Maintains compatibility with old static and prototype window.Date methods.
4617     compat: function() {
4618         var nativeDate = window.Date,
4619             p, u,
4620             statics = ['useStrict', 'formatCodeToRegex', 'parseFunctions', 'parseRegexes', 'formatFunctions', 'y2kYear', 'MILLI', 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'MONTH', 'YEAR', 'defaults', 'dayNames', 'monthNames', 'monthNumbers', 'getShortMonthName', 'getShortDayName', 'getMonthNumber', 'formatCodes', 'isValid', 'parseDate', 'getFormatCode', 'createFormat', 'createParser', 'parseCodes'],
4621             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4622
4623         //Append statics
4624         Ext.Array.forEach(statics, function(s) {
4625             nativeDate[s] = utilDate[s];
4626         });
4627
4628         //Append to prototype
4629         Ext.Array.forEach(proto, function(s) {
4630             nativeDate.prototype[s] = function() {
4631                 var args = Array.prototype.slice.call(arguments);
4632                 args.unshift(this);
4633                 return utilDate[s].apply(utilDate, args);
4634             };
4635         });
4636     }
4637 };
4638
4639 var utilDate = Ext.Date;
4640
4641 })();
4642
4643 /**
4644  * @author Jacky Nguyen <jacky@sencha.com>
4645  * @docauthor Jacky Nguyen <jacky@sencha.com>
4646  * @class Ext.Base
4647  *
4648  * The root of all classes created with {@link Ext#define}.
4649  *
4650  * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
4651  * All prototype and static members of this class are inherited by all other classes.
4652  */
4653 (function(flexSetter) {
4654
4655 var Base = Ext.Base = function() {};
4656     Base.prototype = {
4657         $className: 'Ext.Base',
4658
4659         $class: Base,
4660
4661         /**
4662          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4663          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4664          * for a detailed comparison
4665          *
4666          *     Ext.define('My.Cat', {
4667          *         statics: {
4668          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4669          *         },
4670          *
4671          *         constructor: function() {
4672          *             alert(this.self.speciesName); / dependent on 'this'
4673          *
4674          *             return this;
4675          *         },
4676          *
4677          *         clone: function() {
4678          *             return new this.self();
4679          *         }
4680          *     });
4681          *
4682          *
4683          *     Ext.define('My.SnowLeopard', {
4684          *         extend: 'My.Cat',
4685          *         statics: {
4686          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4687          *         }
4688          *     });
4689          *
4690          *     var cat = new My.Cat();                     // alerts 'Cat'
4691          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4692          *
4693          *     var clone = snowLeopard.clone();
4694          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4695          *
4696          * @type Ext.Class
4697          * @protected
4698          */
4699         self: Base,
4700
4701         // Default constructor, simply returns `this`
4702         constructor: function() {
4703             return this;
4704         },
4705
4706         //<feature classSystem.config>
4707         /**
4708          * Initialize configuration for this class. a typical example:
4709          *
4710          *     Ext.define('My.awesome.Class', {
4711          *         // The default config
4712          *         config: {
4713          *             name: 'Awesome',
4714          *             isAwesome: true
4715          *         },
4716          *
4717          *         constructor: function(config) {
4718          *             this.initConfig(config);
4719          *
4720          *             return this;
4721          *         }
4722          *     });
4723          *
4724          *     var awesome = new My.awesome.Class({
4725          *         name: 'Super Awesome'
4726          *     });
4727          *
4728          *     alert(awesome.getName()); // 'Super Awesome'
4729          *
4730          * @protected
4731          * @param {Object} config
4732          * @return {Object} mixins The mixin prototypes as key - value pairs
4733          */
4734         initConfig: function(config) {
4735             if (!this.$configInited) {
4736                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4737
4738                 this.applyConfig(this.config);
4739
4740                 this.$configInited = true;
4741             }
4742
4743             return this;
4744         },
4745
4746         /**
4747          * @private
4748          */
4749         setConfig: function(config) {
4750             this.applyConfig(config || {});
4751
4752             return this;
4753         },
4754
4755         /**
4756          * @private
4757          */
4758         applyConfig: flexSetter(function(name, value) {
4759             var setter = 'set' + Ext.String.capitalize(name);
4760
4761             if (typeof this[setter] === 'function') {
4762                 this[setter].call(this, value);
4763             }
4764
4765             return this;
4766         }),
4767         //</feature>
4768
4769         /**
4770          * Call the parent's overridden method. For example:
4771          *
4772          *     Ext.define('My.own.A', {
4773          *         constructor: function(test) {
4774          *             alert(test);
4775          *         }
4776          *     });
4777          *
4778          *     Ext.define('My.own.B', {
4779          *         extend: 'My.own.A',
4780          *
4781          *         constructor: function(test) {
4782          *             alert(test);
4783          *
4784          *             this.callParent([test + 1]);
4785          *         }
4786          *     });
4787          *
4788          *     Ext.define('My.own.C', {
4789          *         extend: 'My.own.B',
4790          *
4791          *         constructor: function() {
4792          *             alert("Going to call parent's overriden constructor...");
4793          *
4794          *             this.callParent(arguments);
4795          *         }
4796          *     });
4797          *
4798          *     var a = new My.own.A(1); // alerts '1'
4799          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
4800          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4801          *                              // alerts '2', then alerts '3'
4802          *
4803          * @protected
4804          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4805          * from the current method, for example: `this.callParent(arguments)`
4806          * @return {Object} Returns the result from the superclass' method
4807          */
4808         callParent: function(args) {
4809             var method = this.callParent.caller,
4810                 parentClass, methodName;
4811
4812             if (!method.$owner) {
4813
4814                 method = method.caller;
4815             }
4816
4817             parentClass = method.$owner.superclass;
4818             methodName = method.$name;
4819
4820
4821             return parentClass[methodName].apply(this, args || []);
4822         },
4823
4824
4825         /**
4826          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4827          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4828          * `this` points to during run-time
4829          *
4830          *     Ext.define('My.Cat', {
4831          *         statics: {
4832          *             totalCreated: 0,
4833          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4834          *         },
4835          *
4836          *         constructor: function() {
4837          *             var statics = this.statics();
4838          *
4839          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4840          *                                             // equivalent to: My.Cat.speciesName
4841          *
4842          *             alert(this.self.speciesName);   // dependent on 'this'
4843          *
4844          *             statics.totalCreated++;
4845          *
4846          *             return this;
4847          *         },
4848          *
4849          *         clone: function() {
4850          *             var cloned = new this.self;                      // dependent on 'this'
4851          *
4852          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4853          *
4854          *             return cloned;
4855          *         }
4856          *     });
4857          *
4858          *
4859          *     Ext.define('My.SnowLeopard', {
4860          *         extend: 'My.Cat',
4861          *
4862          *         statics: {
4863          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4864          *         },
4865          *
4866          *         constructor: function() {
4867          *             this.callParent();
4868          *         }
4869          *     });
4870          *
4871          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4872          *
4873          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4874          *
4875          *     var clone = snowLeopard.clone();
4876          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4877          *     alert(clone.groupName);                 // alerts 'Cat'
4878          *
4879          *     alert(My.Cat.totalCreated);             // alerts 3
4880          *
4881          * @protected
4882          * @return {Ext.Class}
4883          */
4884         statics: function() {
4885             var method = this.statics.caller,
4886                 self = this.self;
4887
4888             if (!method) {
4889                 return self;
4890             }
4891
4892             return method.$owner;
4893         },
4894
4895         /**
4896          * Call the original method that was previously overridden with {@link Ext.Base#override}
4897          *
4898          *     Ext.define('My.Cat', {
4899          *         constructor: function() {
4900          *             alert("I'm a cat!");
4901          *
4902          *             return this;
4903          *         }
4904          *     });
4905          *
4906          *     My.Cat.override({
4907          *         constructor: function() {
4908          *             alert("I'm going to be a cat!");
4909          *
4910          *             var instance = this.callOverridden();
4911          *
4912          *             alert("Meeeeoooowwww");
4913          *
4914          *             return instance;
4915          *         }
4916          *     });
4917          *
4918          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4919          *                               // alerts "I'm a cat!"
4920          *                               // alerts "Meeeeoooowwww"
4921          *
4922          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4923          * @return {Object} Returns the result after calling the overridden method
4924          * @protected
4925          */
4926         callOverridden: function(args) {
4927             var method = this.callOverridden.caller;
4928
4929
4930             return method.$previous.apply(this, args || []);
4931         },
4932
4933         destroy: function() {}
4934     };
4935
4936     // These static properties will be copied to every newly created class with {@link Ext#define}
4937     Ext.apply(Ext.Base, {
4938         /**
4939          * Create a new instance of this Class.
4940          *
4941          *     Ext.define('My.cool.Class', {
4942          *         ...
4943          *     });
4944          *
4945          *     My.cool.Class.create({
4946          *         someConfig: true
4947          *     });
4948          *
4949          * All parameters are passed to the constructor of the class.
4950          *
4951          * @return {Object} the created instance.
4952          * @static
4953          * @inheritable
4954          */
4955         create: function() {
4956             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4957         },
4958
4959         /**
4960          * @private
4961          * @inheritable
4962          */
4963         own: function(name, value) {
4964             if (typeof value == 'function') {
4965                 this.ownMethod(name, value);
4966             }
4967             else {
4968                 this.prototype[name] = value;
4969             }
4970         },
4971
4972         /**
4973          * @private
4974          * @inheritable
4975          */
4976         ownMethod: function(name, fn) {
4977             var originalFn;
4978
4979             if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
4980                 originalFn = fn;
4981
4982                 fn = function() {
4983                     return originalFn.apply(this, arguments);
4984                 };
4985             }
4986
4987             fn.$owner = this;
4988             fn.$name = name;
4989
4990             this.prototype[name] = fn;
4991         },
4992
4993         /**
4994          * Add / override static properties of this class.
4995          *
4996          *     Ext.define('My.cool.Class', {
4997          *         ...
4998          *     });
4999          *
5000          *     My.cool.Class.addStatics({
5001          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
5002          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
5003          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
5004          *     });
5005          *
5006          * @param {Object} members
5007          * @return {Ext.Base} this
5008          * @static
5009          * @inheritable
5010          */
5011         addStatics: function(members) {
5012             for (var name in members) {
5013                 if (members.hasOwnProperty(name)) {
5014                     this[name] = members[name];
5015                 }
5016             }
5017
5018             return this;
5019         },
5020
5021         /**
5022          * @private
5023          * @param {Object} members
5024          */
5025         addInheritableStatics: function(members) {
5026             var inheritableStatics,
5027                 hasInheritableStatics,
5028                 prototype = this.prototype,
5029                 name, member;
5030
5031             inheritableStatics = prototype.$inheritableStatics;
5032             hasInheritableStatics = prototype.$hasInheritableStatics;
5033
5034             if (!inheritableStatics) {
5035                 inheritableStatics = prototype.$inheritableStatics = [];
5036                 hasInheritableStatics = prototype.$hasInheritableStatics = {};
5037             }
5038
5039
5040             for (name in members) {
5041                 if (members.hasOwnProperty(name)) {
5042                     member = members[name];
5043                     this[name] = member;
5044
5045                     if (!hasInheritableStatics[name]) {
5046                         hasInheritableStatics[name] = true;
5047                         inheritableStatics.push(name);
5048                     }
5049                 }
5050             }
5051
5052             return this;
5053         },
5054
5055         /**
5056          * Add methods / properties to the prototype of this class.
5057          *
5058          *     Ext.define('My.awesome.Cat', {
5059          *         constructor: function() {
5060          *             ...
5061          *         }
5062          *     });
5063          *
5064          *      My.awesome.Cat.implement({
5065          *          meow: function() {
5066          *             alert('Meowww...');
5067          *          }
5068          *      });
5069          *
5070          *      var kitty = new My.awesome.Cat;
5071          *      kitty.meow();
5072          *
5073          * @param {Object} members
5074          * @static
5075          * @inheritable
5076          */
5077         implement: function(members) {
5078             var prototype = this.prototype,
5079                 enumerables = Ext.enumerables,
5080                 name, i, member;
5081             for (name in members) {
5082                 if (members.hasOwnProperty(name)) {
5083                     member = members[name];
5084
5085                     if (typeof member === 'function') {
5086                         member.$owner = this;
5087                         member.$name = name;
5088                     }
5089
5090                     prototype[name] = member;
5091                 }
5092             }
5093
5094             if (enumerables) {
5095                 for (i = enumerables.length; i--;) {
5096                     name = enumerables[i];
5097
5098                     if (members.hasOwnProperty(name)) {
5099                         member = members[name];
5100                         member.$owner = this;
5101                         member.$name = name;
5102                         prototype[name] = member;
5103                     }
5104                 }
5105             }
5106         },
5107
5108         /**
5109          * Borrow another class' members to the prototype of this class.
5110          *
5111          *     Ext.define('Bank', {
5112          *         money: '$$$',
5113          *         printMoney: function() {
5114          *             alert('$$$$$$$');
5115          *         }
5116          *     });
5117          *
5118          *     Ext.define('Thief', {
5119          *         ...
5120          *     });
5121          *
5122          *     Thief.borrow(Bank, ['money', 'printMoney']);
5123          *
5124          *     var steve = new Thief();
5125          *
5126          *     alert(steve.money); // alerts '$$$'
5127          *     steve.printMoney(); // alerts '$$$$$$$'
5128          *
5129          * @param {Ext.Base} fromClass The class to borrow members from
5130          * @param {String/String[]} members The names of the members to borrow
5131          * @return {Ext.Base} this
5132          * @static
5133          * @inheritable
5134          */
5135         borrow: function(fromClass, members) {
5136             var fromPrototype = fromClass.prototype,
5137                 i, ln, member;
5138
5139             members = Ext.Array.from(members);
5140
5141             for (i = 0, ln = members.length; i < ln; i++) {
5142                 member = members[i];
5143
5144                 this.own(member, fromPrototype[member]);
5145             }
5146
5147             return this;
5148         },
5149
5150         /**
5151          * Override prototype members of this class. Overridden methods can be invoked via
5152          * {@link Ext.Base#callOverridden}
5153          *
5154          *     Ext.define('My.Cat', {
5155          *         constructor: function() {
5156          *             alert("I'm a cat!");
5157          *
5158          *             return this;
5159          *         }
5160          *     });
5161          *
5162          *     My.Cat.override({
5163          *         constructor: function() {
5164          *             alert("I'm going to be a cat!");
5165          *
5166          *             var instance = this.callOverridden();
5167          *
5168          *             alert("Meeeeoooowwww");
5169          *
5170          *             return instance;
5171          *         }
5172          *     });
5173          *
5174          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5175          *                               // alerts "I'm a cat!"
5176          *                               // alerts "Meeeeoooowwww"
5177          *
5178          * @param {Object} members
5179          * @return {Ext.Base} this
5180          * @static
5181          * @inheritable
5182          */
5183         override: function(members) {
5184             var prototype = this.prototype,
5185                 enumerables = Ext.enumerables,
5186                 name, i, member, previous;
5187
5188             if (arguments.length === 2) {
5189                 name = members;
5190                 member = arguments[1];
5191
5192                 if (typeof member == 'function') {
5193                     if (typeof prototype[name] == 'function') {
5194                         previous = prototype[name];
5195                         member.$previous = previous;
5196                     }
5197
5198                     this.ownMethod(name, member);
5199                 }
5200                 else {
5201                     prototype[name] = member;
5202                 }
5203
5204                 return this;
5205             }
5206
5207             for (name in members) {
5208                 if (members.hasOwnProperty(name)) {
5209                     member = members[name];
5210
5211                     if (typeof member === 'function') {
5212                         if (typeof prototype[name] === 'function') {
5213                             previous = prototype[name];
5214                             member.$previous = previous;
5215                         }
5216
5217                         this.ownMethod(name, member);
5218                     }
5219                     else {
5220                         prototype[name] = member;
5221                     }
5222                 }
5223             }
5224
5225             if (enumerables) {
5226                 for (i = enumerables.length; i--;) {
5227                     name = enumerables[i];
5228
5229                     if (members.hasOwnProperty(name)) {
5230                         if (typeof prototype[name] !== 'undefined') {
5231                             previous = prototype[name];
5232                             members[name].$previous = previous;
5233                         }
5234
5235                         this.ownMethod(name, members[name]);
5236                     }
5237                 }
5238             }
5239
5240             return this;
5241         },
5242
5243         //<feature classSystem.mixins>
5244         /**
5245          * Used internally by the mixins pre-processor
5246          * @private
5247          * @inheritable
5248          */
5249         mixin: function(name, cls) {
5250             var mixin = cls.prototype,
5251                 my = this.prototype,
5252                 key, fn;
5253
5254             for (key in mixin) {
5255                 if (mixin.hasOwnProperty(key)) {
5256                     if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
5257                         if (typeof mixin[key] === 'function') {
5258                             fn = mixin[key];
5259
5260                             if (typeof fn.$owner === 'undefined') {
5261                                 this.ownMethod(key, fn);
5262                             }
5263                             else {
5264                                 my[key] = fn;
5265                             }
5266                         }
5267                         else {
5268                             my[key] = mixin[key];
5269                         }
5270                     }
5271                     //<feature classSystem.config>
5272                     else if (key === 'config' && my.config && mixin.config) {
5273                         Ext.Object.merge(my.config, mixin.config);
5274                     }
5275                     //</feature>
5276                 }
5277             }
5278
5279             if (typeof mixin.onClassMixedIn !== 'undefined') {
5280                 mixin.onClassMixedIn.call(cls, this);
5281             }
5282
5283             if (!my.hasOwnProperty('mixins')) {
5284                 if ('mixins' in my) {
5285                     my.mixins = Ext.Object.merge({}, my.mixins);
5286                 }
5287                 else {
5288                     my.mixins = {};
5289                 }
5290             }
5291
5292             my.mixins[name] = mixin;
5293         },
5294         //</feature>
5295
5296         /**
5297          * Get the current class' name in string format.
5298          *
5299          *     Ext.define('My.cool.Class', {
5300          *         constructor: function() {
5301          *             alert(this.self.getName()); // alerts 'My.cool.Class'
5302          *         }
5303          *     });
5304          *
5305          *     My.cool.Class.getName(); // 'My.cool.Class'
5306          *
5307          * @return {String} className
5308          * @static
5309          * @inheritable
5310          */
5311         getName: function() {
5312             return Ext.getClassName(this);
5313         },
5314
5315         /**
5316          * Create aliases for existing prototype methods. Example:
5317          *
5318          *     Ext.define('My.cool.Class', {
5319          *         method1: function() { ... },
5320          *         method2: function() { ... }
5321          *     });
5322          *
5323          *     var test = new My.cool.Class();
5324          *
5325          *     My.cool.Class.createAlias({
5326          *         method3: 'method1',
5327          *         method4: 'method2'
5328          *     });
5329          *
5330          *     test.method3(); // test.method1()
5331          *
5332          *     My.cool.Class.createAlias('method5', 'method3');
5333          *
5334          *     test.method5(); // test.method3() -> test.method1()
5335          *
5336          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5337          * {@link Ext.Function#flexSetter flexSetter}
5338          * @param {String/Object} origin The original method name
5339          * @static
5340          * @inheritable
5341          * @method
5342          */
5343         createAlias: flexSetter(function(alias, origin) {
5344             this.prototype[alias] = function() {
5345                 return this[origin].apply(this, arguments);
5346             }
5347         })
5348     });
5349
5350 })(Ext.Function.flexSetter);
5351
5352 /**
5353  * @author Jacky Nguyen <jacky@sencha.com>
5354  * @docauthor Jacky Nguyen <jacky@sencha.com>
5355  * @class Ext.Class
5356  *
5357  * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
5358  * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
5359  * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
5360  *
5361  * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
5362  * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
5363  *
5364  * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
5365  * from, see {@link Ext.Base}.
5366  */
5367 (function() {
5368
5369     var Class,
5370         Base = Ext.Base,
5371         baseStaticProperties = [],
5372         baseStaticProperty;
5373
5374     for (baseStaticProperty in Base) {
5375         if (Base.hasOwnProperty(baseStaticProperty)) {
5376             baseStaticProperties.push(baseStaticProperty);
5377         }
5378     }
5379
5380     /**
5381      * @method constructor
5382      * Creates new class.
5383      * @param {Object} classData An object represent the properties of this class
5384      * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
5385      * Note that the creation process can be asynchronous depending on the pre-processors used.
5386      * @return {Ext.Base} The newly created class
5387      */
5388     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5389         if (typeof newClass != 'function') {
5390             onClassCreated = classData;
5391             classData = newClass;
5392             newClass = function() {
5393                 return this.constructor.apply(this, arguments);
5394             };
5395         }
5396
5397         if (!classData) {
5398             classData = {};
5399         }
5400
5401         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5402             registeredPreprocessors = Class.getPreprocessors(),
5403             index = 0,
5404             preprocessors = [],
5405             preprocessor, staticPropertyName, process, i, j, ln;
5406
5407         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5408             staticPropertyName = baseStaticProperties[i];
5409             newClass[staticPropertyName] = Base[staticPropertyName];
5410         }
5411
5412         delete classData.preprocessors;
5413
5414         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5415             preprocessor = preprocessorStack[j];
5416
5417             if (typeof preprocessor == 'string') {
5418                 preprocessor = registeredPreprocessors[preprocessor];
5419
5420                 if (!preprocessor.always) {
5421                     if (classData.hasOwnProperty(preprocessor.name)) {
5422                         preprocessors.push(preprocessor.fn);
5423                     }
5424                 }
5425                 else {
5426                     preprocessors.push(preprocessor.fn);
5427                 }
5428             }
5429             else {
5430                 preprocessors.push(preprocessor);
5431             }
5432         }
5433
5434         classData.onClassCreated = onClassCreated || Ext.emptyFn;
5435
5436         classData.onBeforeClassCreated = function(cls, data) {
5437             onClassCreated = data.onClassCreated;
5438
5439             delete data.onBeforeClassCreated;
5440             delete data.onClassCreated;
5441
5442             cls.implement(data);
5443
5444             onClassCreated.call(cls, cls);
5445         };
5446
5447         process = function(cls, data) {
5448             preprocessor = preprocessors[index++];
5449
5450             if (!preprocessor) {
5451                 data.onBeforeClassCreated.apply(this, arguments);
5452                 return;
5453             }
5454
5455             if (preprocessor.call(this, cls, data, process) !== false) {
5456                 process.apply(this, arguments);
5457             }
5458         };
5459
5460         process.call(Class, newClass, classData);
5461
5462         return newClass;
5463     };
5464
5465     Ext.apply(Class, {
5466
5467         /** @private */
5468         preprocessors: {},
5469
5470         /**
5471          * Register a new pre-processor to be used during the class creation process
5472          *
5473          * @member Ext.Class
5474          * @param {String} name The pre-processor's name
5475          * @param {Function} fn The callback function to be executed. Typical format:
5476          *
5477          *     function(cls, data, fn) {
5478          *         // Your code here
5479          *
5480          *         // Execute this when the processing is finished.
5481          *         // Asynchronous processing is perfectly ok
5482          *         if (fn) {
5483          *             fn.call(this, cls, data);
5484          *         }
5485          *     });
5486          *
5487          * @param {Function} fn.cls The created class
5488          * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
5489          * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
5490          * regardless of whether the processing is synchronous or aynchronous
5491          *
5492          * @return {Ext.Class} this
5493          * @static
5494          */
5495         registerPreprocessor: function(name, fn, always) {
5496             this.preprocessors[name] = {
5497                 name: name,
5498                 always: always ||  false,
5499                 fn: fn
5500             };
5501
5502             return this;
5503         },
5504
5505         /**
5506          * Retrieve a pre-processor callback function by its name, which has been registered before
5507          *
5508          * @param {String} name
5509          * @return {Function} preprocessor
5510          * @static
5511          */
5512         getPreprocessor: function(name) {
5513             return this.preprocessors[name];
5514         },
5515
5516         getPreprocessors: function() {
5517             return this.preprocessors;
5518         },
5519
5520         /**
5521          * Retrieve the array stack of default pre-processors
5522          *
5523          * @return {Function[]} defaultPreprocessors
5524          * @static
5525          */
5526         getDefaultPreprocessors: function() {
5527             return this.defaultPreprocessors || [];
5528         },
5529
5530         /**
5531          * Set the default array stack of default pre-processors
5532          *
5533          * @param {Function/Function[]} preprocessors
5534          * @return {Ext.Class} this
5535          * @static
5536          */
5537         setDefaultPreprocessors: function(preprocessors) {
5538             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5539
5540             return this;
5541         },
5542
5543         /**
5544          * Inserts this pre-processor at a specific position in the stack, optionally relative to
5545          * any existing pre-processor. For example:
5546          *
5547          *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5548          *         // Your code here
5549          *
5550          *         if (fn) {
5551          *             fn.call(this, cls, data);
5552          *         }
5553          *     }).setDefaultPreprocessorPosition('debug', 'last');
5554          *
5555          * @param {String} name The pre-processor name. Note that it needs to be registered with
5556          * {@link #registerPreprocessor registerPreprocessor} before this
5557          * @param {String} offset The insertion position. Four possible values are:
5558          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5559          * @param {String} relativeName
5560          * @return {Ext.Class} this
5561          * @static
5562          */
5563         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5564             var defaultPreprocessors = this.defaultPreprocessors,
5565                 index;
5566
5567             if (typeof offset == 'string') {
5568                 if (offset === 'first') {
5569                     defaultPreprocessors.unshift(name);
5570
5571                     return this;
5572                 }
5573                 else if (offset === 'last') {
5574                     defaultPreprocessors.push(name);
5575
5576                     return this;
5577                 }
5578
5579                 offset = (offset === 'after') ? 1 : -1;
5580             }
5581
5582             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5583
5584             if (index !== -1) {
5585                 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5586             }
5587
5588             return this;
5589         }
5590     });
5591
5592     /**
5593      * @cfg {String} extend
5594      * The parent class that this class extends. For example:
5595      *
5596      *     Ext.define('Person', {
5597      *         say: function(text) { alert(text); }
5598      *     });
5599      *
5600      *     Ext.define('Developer', {
5601      *         extend: 'Person',
5602      *         say: function(text) { this.callParent(["print "+text]); }
5603      *     });
5604      */
5605     Class.registerPreprocessor('extend', function(cls, data) {
5606         var extend = data.extend,
5607             base = Ext.Base,
5608             basePrototype = base.prototype,
5609             prototype = function() {},
5610             parent, i, k, ln, staticName, parentStatics,
5611             parentPrototype, clsPrototype;
5612
5613         if (extend && extend !== Object) {
5614             parent = extend;
5615         }
5616         else {
5617             parent = base;
5618         }
5619
5620         parentPrototype = parent.prototype;
5621
5622         prototype.prototype = parentPrototype;
5623         clsPrototype = cls.prototype = new prototype();
5624
5625         if (!('$class' in parent)) {
5626             for (i in basePrototype) {
5627                 if (!parentPrototype[i]) {
5628                     parentPrototype[i] = basePrototype[i];
5629                 }
5630             }
5631         }
5632
5633         clsPrototype.self = cls;
5634
5635         cls.superclass = clsPrototype.superclass = parentPrototype;
5636
5637         delete data.extend;
5638
5639         //<feature classSystem.inheritableStatics>
5640         // Statics inheritance
5641         parentStatics = parentPrototype.$inheritableStatics;
5642
5643         if (parentStatics) {
5644             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5645                 staticName = parentStatics[k];
5646
5647                 if (!cls.hasOwnProperty(staticName)) {
5648                     cls[staticName] = parent[staticName];
5649                 }
5650             }
5651         }
5652         //</feature>
5653
5654         //<feature classSystem.config>
5655         // Merge the parent class' config object without referencing it
5656         if (parentPrototype.config) {
5657             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5658         }
5659         else {
5660             clsPrototype.config = {};
5661         }
5662         //</feature>
5663
5664         //<feature classSystem.onClassExtended>
5665         if (clsPrototype.$onExtended) {
5666             clsPrototype.$onExtended.call(cls, cls, data);
5667         }
5668
5669         if (data.onClassExtended) {
5670             clsPrototype.$onExtended = data.onClassExtended;
5671             delete data.onClassExtended;
5672         }
5673         //</feature>
5674
5675     }, true);
5676
5677     //<feature classSystem.statics>
5678     /**
5679      * @cfg {Object} statics
5680      * List of static methods for this class. For example:
5681      *
5682      *     Ext.define('Computer', {
5683      *          statics: {
5684      *              factory: function(brand) {
5685      *                  // 'this' in static methods refer to the class itself
5686      *                  return new this(brand);
5687      *              }
5688      *          },
5689      *
5690      *          constructor: function() { ... }
5691      *     });
5692      *
5693      *     var dellComputer = Computer.factory('Dell');
5694      */
5695     Class.registerPreprocessor('statics', function(cls, data) {
5696         cls.addStatics(data.statics);
5697
5698         delete data.statics;
5699     });
5700     //</feature>
5701
5702     //<feature classSystem.inheritableStatics>
5703     /**
5704      * @cfg {Object} inheritableStatics
5705      * List of inheritable static methods for this class.
5706      * Otherwise just like {@link #statics} but subclasses inherit these methods.
5707      */
5708     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5709         cls.addInheritableStatics(data.inheritableStatics);
5710
5711         delete data.inheritableStatics;
5712     });
5713     //</feature>
5714
5715     //<feature classSystem.config>
5716     /**
5717      * @cfg {Object} config
5718      * List of configuration options with their default values, for which automatically
5719      * accessor methods are generated.  For example:
5720      *
5721      *     Ext.define('SmartPhone', {
5722      *          config: {
5723      *              hasTouchScreen: false,
5724      *              operatingSystem: 'Other',
5725      *              price: 500
5726      *          },
5727      *          constructor: function(cfg) {
5728      *              this.initConfig(cfg);
5729      *          }
5730      *     });
5731      *
5732      *     var iPhone = new SmartPhone({
5733      *          hasTouchScreen: true,
5734      *          operatingSystem: 'iOS'
5735      *     });
5736      *
5737      *     iPhone.getPrice(); // 500;
5738      *     iPhone.getOperatingSystem(); // 'iOS'
5739      *     iPhone.getHasTouchScreen(); // true;
5740      *     iPhone.hasTouchScreen(); // true
5741      */
5742     Class.registerPreprocessor('config', function(cls, data) {
5743         var prototype = cls.prototype;
5744
5745         Ext.Object.each(data.config, function(name) {
5746             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5747                 pName = name,
5748                 apply = 'apply' + cName,
5749                 setter = 'set' + cName,
5750                 getter = 'get' + cName;
5751
5752             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5753                 data[apply] = function(val) {
5754                     return val;
5755                 };
5756             }
5757
5758             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5759                 data[setter] = function(val) {
5760                     var ret = this[apply].call(this, val, this[pName]);
5761
5762                     if (typeof ret != 'undefined') {
5763                         this[pName] = ret;
5764                     }
5765
5766                     return this;
5767                 };
5768             }
5769
5770             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5771                 data[getter] = function() {
5772                     return this[pName];
5773                 };
5774             }
5775         });
5776
5777         Ext.Object.merge(prototype.config, data.config);
5778         delete data.config;
5779     });
5780     //</feature>
5781
5782     //<feature classSystem.mixins>
5783     /**
5784      * @cfg {Object} mixins
5785      * List of classes to mix into this class. For example:
5786      *
5787      *     Ext.define('CanSing', {
5788      *          sing: function() {
5789      *              alert("I'm on the highway to hell...")
5790      *          }
5791      *     });
5792      *
5793      *     Ext.define('Musician', {
5794      *          extend: 'Person',
5795      *
5796      *          mixins: {
5797      *              canSing: 'CanSing'
5798      *          }
5799      *     })
5800      */
5801     Class.registerPreprocessor('mixins', function(cls, data) {
5802         var mixins = data.mixins,
5803             name, mixin, i, ln;
5804
5805         delete data.mixins;
5806
5807         Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
5808             if (mixins instanceof Array) {
5809                 for (i = 0,ln = mixins.length; i < ln; i++) {
5810                     mixin = mixins[i];
5811                     name = mixin.prototype.mixinId || mixin.$className;
5812
5813                     cls.mixin(name, mixin);
5814                 }
5815             }
5816             else {
5817                 for (name in mixins) {
5818                     if (mixins.hasOwnProperty(name)) {
5819                         cls.mixin(name, mixins[name]);
5820                     }
5821                 }
5822             }
5823         });
5824     });
5825
5826     //</feature>
5827
5828     Class.setDefaultPreprocessors([
5829         'extend'
5830         //<feature classSystem.statics>
5831         ,'statics'
5832         //</feature>
5833         //<feature classSystem.inheritableStatics>
5834         ,'inheritableStatics'
5835         //</feature>
5836         //<feature classSystem.config>
5837         ,'config'
5838         //</feature>
5839         //<feature classSystem.mixins>
5840         ,'mixins'
5841         //</feature>
5842     ]);
5843
5844     //<feature classSystem.backwardsCompatible>
5845     // Backwards compatible
5846     Ext.extend = function(subclass, superclass, members) {
5847         if (arguments.length === 2 && Ext.isObject(superclass)) {
5848             members = superclass;
5849             superclass = subclass;
5850             subclass = null;
5851         }
5852
5853         var cls;
5854
5855         if (!superclass) {
5856             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5857         }
5858
5859         members.extend = superclass;
5860         members.preprocessors = [
5861             'extend'
5862             //<feature classSystem.statics>
5863             ,'statics'
5864             //</feature>
5865             //<feature classSystem.inheritableStatics>
5866             ,'inheritableStatics'
5867             //</feature>
5868             //<feature classSystem.mixins>
5869             ,'mixins'
5870             //</feature>
5871             //<feature classSystem.config>
5872             ,'config'
5873             //</feature>
5874         ];
5875
5876         if (subclass) {
5877             cls = new Class(subclass, members);
5878         }
5879         else {
5880             cls = new Class(members);
5881         }
5882
5883         cls.prototype.override = function(o) {
5884             for (var m in o) {
5885                 if (o.hasOwnProperty(m)) {
5886                     this[m] = o[m];
5887                 }
5888             }
5889         };
5890
5891         return cls;
5892     };
5893     //</feature>
5894
5895 })();
5896
5897 /**
5898  * @author Jacky Nguyen <jacky@sencha.com>
5899  * @docauthor Jacky Nguyen <jacky@sencha.com>
5900  * @class Ext.ClassManager
5901  *
5902  * Ext.ClassManager manages all classes and handles mapping from string class name to
5903  * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5904  * these convenient shorthands:
5905  *
5906  * - {@link Ext#define Ext.define}
5907  * - {@link Ext#create Ext.create}
5908  * - {@link Ext#widget Ext.widget}
5909  * - {@link Ext#getClass Ext.getClass}
5910  * - {@link Ext#getClassName Ext.getClassName}
5911  *
5912  * # Basic syntax:
5913  *
5914  *     Ext.define(className, properties);
5915  *
5916  * in which `properties` is an object represent a collection of properties that apply to the class. See
5917  * {@link Ext.ClassManager#create} for more detailed instructions.
5918  *
5919  *     Ext.define('Person', {
5920  *          name: 'Unknown',
5921  *
5922  *          constructor: function(name) {
5923  *              if (name) {
5924  *                  this.name = name;
5925  *              }
5926  *
5927  *              return this;
5928  *          },
5929  *
5930  *          eat: function(foodType) {
5931  *              alert("I'm eating: " + foodType);
5932  *
5933  *              return this;
5934  *          }
5935  *     });
5936  *
5937  *     var aaron = new Person("Aaron");
5938  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5939  *
5940  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5941  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5942  *
5943  * # Inheritance:
5944  *
5945  *     Ext.define('Developer', {
5946  *          extend: 'Person',
5947  *
5948  *          constructor: function(name, isGeek) {
5949  *              this.isGeek = isGeek;
5950  *
5951  *              // Apply a method from the parent class' prototype
5952  *              this.callParent([name]);
5953  *
5954  *              return this;
5955  *
5956  *          },
5957  *
5958  *          code: function(language) {
5959  *              alert("I'm coding in: " + language);
5960  *
5961  *              this.eat("Bugs");
5962  *
5963  *              return this;
5964  *          }
5965  *     });
5966  *
5967  *     var jacky = new Developer("Jacky", true);
5968  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5969  *                               // alert("I'm eating: Bugs");
5970  *
5971  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5972  *
5973  * # Mixins:
5974  *
5975  *     Ext.define('CanPlayGuitar', {
5976  *          playGuitar: function() {
5977  *             alert("F#...G...D...A");
5978  *          }
5979  *     });
5980  *
5981  *     Ext.define('CanComposeSongs', {
5982  *          composeSongs: function() { ... }
5983  *     });
5984  *
5985  *     Ext.define('CanSing', {
5986  *          sing: function() {
5987  *              alert("I'm on the highway to hell...")
5988  *          }
5989  *     });
5990  *
5991  *     Ext.define('Musician', {
5992  *          extend: 'Person',
5993  *
5994  *          mixins: {
5995  *              canPlayGuitar: 'CanPlayGuitar',
5996  *              canComposeSongs: 'CanComposeSongs',
5997  *              canSing: 'CanSing'
5998  *          }
5999  *     })
6000  *
6001  *     Ext.define('CoolPerson', {
6002  *          extend: 'Person',
6003  *
6004  *          mixins: {
6005  *              canPlayGuitar: 'CanPlayGuitar',
6006  *              canSing: 'CanSing'
6007  *          },
6008  *
6009  *          sing: function() {
6010  *              alert("Ahem....");
6011  *
6012  *              this.mixins.canSing.sing.call(this);
6013  *
6014  *              alert("[Playing guitar at the same time...]");
6015  *
6016  *              this.playGuitar();
6017  *          }
6018  *     });
6019  *
6020  *     var me = new CoolPerson("Jacky");
6021  *
6022  *     me.sing(); // alert("Ahem...");
6023  *                // alert("I'm on the highway to hell...");
6024  *                // alert("[Playing guitar at the same time...]");
6025  *                // alert("F#...G...D...A");
6026  *
6027  * # Config:
6028  *
6029  *     Ext.define('SmartPhone', {
6030  *          config: {
6031  *              hasTouchScreen: false,
6032  *              operatingSystem: 'Other',
6033  *              price: 500
6034  *          },
6035  *
6036  *          isExpensive: false,
6037  *
6038  *          constructor: function(config) {
6039  *              this.initConfig(config);
6040  *
6041  *              return this;
6042  *          },
6043  *
6044  *          applyPrice: function(price) {
6045  *              this.isExpensive = (price > 500);
6046  *
6047  *              return price;
6048  *          },
6049  *
6050  *          applyOperatingSystem: function(operatingSystem) {
6051  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
6052  *                  return 'Other';
6053  *              }
6054  *
6055  *              return operatingSystem;
6056  *          }
6057  *     });
6058  *
6059  *     var iPhone = new SmartPhone({
6060  *          hasTouchScreen: true,
6061  *          operatingSystem: 'iOS'
6062  *     });
6063  *
6064  *     iPhone.getPrice(); // 500;
6065  *     iPhone.getOperatingSystem(); // 'iOS'
6066  *     iPhone.getHasTouchScreen(); // true;
6067  *     iPhone.hasTouchScreen(); // true
6068  *
6069  *     iPhone.isExpensive; // false;
6070  *     iPhone.setPrice(600);
6071  *     iPhone.getPrice(); // 600
6072  *     iPhone.isExpensive; // true;
6073  *
6074  *     iPhone.setOperatingSystem('AlienOS');
6075  *     iPhone.getOperatingSystem(); // 'Other'
6076  *
6077  * # Statics:
6078  *
6079  *     Ext.define('Computer', {
6080  *          statics: {
6081  *              factory: function(brand) {
6082  *                 // 'this' in static methods refer to the class itself
6083  *                  return new this(brand);
6084  *              }
6085  *          },
6086  *
6087  *          constructor: function() { ... }
6088  *     });
6089  *
6090  *     var dellComputer = Computer.factory('Dell');
6091  *
6092  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
6093  * static properties within class methods
6094  *
6095  * @singleton
6096  */
6097 (function(Class, alias) {
6098
6099     var slice = Array.prototype.slice;
6100
6101     var Manager = Ext.ClassManager = {
6102
6103         /**
6104          * @property {Object} classes
6105          * All classes which were defined through the ClassManager. Keys are the
6106          * name of the classes and the values are references to the classes.
6107          * @private
6108          */
6109         classes: {},
6110
6111         /**
6112          * @private
6113          */
6114         existCache: {},
6115
6116         /**
6117          * @private
6118          */
6119         namespaceRewrites: [{
6120             from: 'Ext.',
6121             to: Ext
6122         }],
6123
6124         /**
6125          * @private
6126          */
6127         maps: {
6128             alternateToName: {},
6129             aliasToName: {},
6130             nameToAliases: {}
6131         },
6132
6133         /** @private */
6134         enableNamespaceParseCache: true,
6135
6136         /** @private */
6137         namespaceParseCache: {},
6138
6139         /** @private */
6140         instantiators: [],
6141
6142
6143         /**
6144          * Checks if a class has already been created.
6145          *
6146          * @param {String} className
6147          * @return {Boolean} exist
6148          */
6149         isCreated: function(className) {
6150             var i, ln, part, root, parts;
6151
6152
6153             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6154                 return true;
6155             }
6156
6157             root = Ext.global;
6158             parts = this.parseNamespace(className);
6159
6160             for (i = 0, ln = parts.length; i < ln; i++) {
6161                 part = parts[i];
6162
6163                 if (typeof part !== 'string') {
6164                     root = part;
6165                 } else {
6166                     if (!root || !root[part]) {
6167                         return false;
6168                     }
6169
6170                     root = root[part];
6171                 }
6172             }
6173
6174             Ext.Loader.historyPush(className);
6175
6176             this.existCache[className] = true;
6177
6178             return true;
6179         },
6180
6181         /**
6182          * Supports namespace rewriting
6183          * @private
6184          */
6185         parseNamespace: function(namespace) {
6186
6187             var cache = this.namespaceParseCache;
6188
6189             if (this.enableNamespaceParseCache) {
6190                 if (cache.hasOwnProperty(namespace)) {
6191                     return cache[namespace];
6192                 }
6193             }
6194
6195             var parts = [],
6196                 rewrites = this.namespaceRewrites,
6197                 rewrite, from, to, i, ln, root = Ext.global;
6198
6199             for (i = 0, ln = rewrites.length; i < ln; i++) {
6200                 rewrite = rewrites[i];
6201                 from = rewrite.from;
6202                 to = rewrite.to;
6203
6204                 if (namespace === from || namespace.substring(0, from.length) === from) {
6205                     namespace = namespace.substring(from.length);
6206
6207                     if (typeof to !== 'string') {
6208                         root = to;
6209                     } else {
6210                         parts = parts.concat(to.split('.'));
6211                     }
6212
6213                     break;
6214                 }
6215             }
6216
6217             parts.push(root);
6218
6219             parts = parts.concat(namespace.split('.'));
6220
6221             if (this.enableNamespaceParseCache) {
6222                 cache[namespace] = parts;
6223             }
6224
6225             return parts;
6226         },
6227
6228         /**
6229          * Creates a namespace and assign the `value` to the created object
6230          *
6231          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6232          *
6233          *     alert(MyCompany.pkg.Example === someObject); // alerts true
6234          *
6235          * @param {String} name
6236          * @param {Object} value
6237          */
6238         setNamespace: function(name, value) {
6239             var root = Ext.global,
6240                 parts = this.parseNamespace(name),
6241                 ln = parts.length - 1,
6242                 leaf = parts[ln],
6243                 i, part;
6244
6245             for (i = 0; i < ln; i++) {
6246                 part = parts[i];
6247
6248                 if (typeof part !== 'string') {
6249                     root = part;
6250                 } else {
6251                     if (!root[part]) {
6252                         root[part] = {};
6253                     }
6254
6255                     root = root[part];
6256                 }
6257             }
6258
6259             root[leaf] = value;
6260
6261             return root[leaf];
6262         },
6263
6264         /**
6265          * The new Ext.ns, supports namespace rewriting
6266          * @private
6267          */
6268         createNamespaces: function() {
6269             var root = Ext.global,
6270                 parts, part, i, j, ln, subLn;
6271
6272             for (i = 0, ln = arguments.length; i < ln; i++) {
6273                 parts = this.parseNamespace(arguments[i]);
6274
6275                 for (j = 0, subLn = parts.length; j < subLn; j++) {
6276                     part = parts[j];
6277
6278                     if (typeof part !== 'string') {
6279                         root = part;
6280                     } else {
6281                         if (!root[part]) {
6282                             root[part] = {};
6283                         }
6284
6285                         root = root[part];
6286                     }
6287                 }
6288             }
6289
6290             return root;
6291         },
6292
6293         /**
6294          * Sets a name reference to a class.
6295          *
6296          * @param {String} name
6297          * @param {Object} value
6298          * @return {Ext.ClassManager} this
6299          */
6300         set: function(name, value) {
6301             var targetName = this.getName(value);
6302
6303             this.classes[name] = this.setNamespace(name, value);
6304
6305             if (targetName && targetName !== name) {
6306                 this.maps.alternateToName[name] = targetName;
6307             }
6308
6309             return this;
6310         },
6311
6312         /**
6313          * Retrieve a class by its name.
6314          *
6315          * @param {String} name
6316          * @return {Ext.Class} class
6317          */
6318         get: function(name) {
6319             if (this.classes.hasOwnProperty(name)) {
6320                 return this.classes[name];
6321             }
6322
6323             var root = Ext.global,
6324                 parts = this.parseNamespace(name),
6325                 part, i, ln;
6326
6327             for (i = 0, ln = parts.length; i < ln; i++) {
6328                 part = parts[i];
6329
6330                 if (typeof part !== 'string') {
6331                     root = part;
6332                 } else {
6333                     if (!root || !root[part]) {
6334                         return null;
6335                     }
6336
6337                     root = root[part];
6338                 }
6339             }
6340
6341             return root;
6342         },
6343
6344         /**
6345          * Register the alias for a class.
6346          *
6347          * @param {Ext.Class/String} cls a reference to a class or a className
6348          * @param {String} alias Alias to use when referring to this class
6349          */
6350         setAlias: function(cls, alias) {
6351             var aliasToNameMap = this.maps.aliasToName,
6352                 nameToAliasesMap = this.maps.nameToAliases,
6353                 className;
6354
6355             if (typeof cls === 'string') {
6356                 className = cls;
6357             } else {
6358                 className = this.getName(cls);
6359             }
6360
6361             if (alias && aliasToNameMap[alias] !== className) {
6362
6363                 aliasToNameMap[alias] = className;
6364             }
6365
6366             if (!nameToAliasesMap[className]) {
6367                 nameToAliasesMap[className] = [];
6368             }
6369
6370             if (alias) {
6371                 Ext.Array.include(nameToAliasesMap[className], alias);
6372             }
6373
6374             return this;
6375         },
6376
6377         /**
6378          * Get a reference to the class by its alias.
6379          *
6380          * @param {String} alias
6381          * @return {Ext.Class} class
6382          */
6383         getByAlias: function(alias) {
6384             return this.get(this.getNameByAlias(alias));
6385         },
6386
6387         /**
6388          * Get the name of a class by its alias.
6389          *
6390          * @param {String} alias
6391          * @return {String} className
6392          */
6393         getNameByAlias: function(alias) {
6394             return this.maps.aliasToName[alias] || '';
6395         },
6396
6397         /**
6398          * Get the name of a class by its alternate name.
6399          *
6400          * @param {String} alternate
6401          * @return {String} className
6402          */
6403         getNameByAlternate: function(alternate) {
6404             return this.maps.alternateToName[alternate] || '';
6405         },
6406
6407         /**
6408          * Get the aliases of a class by the class name
6409          *
6410          * @param {String} name
6411          * @return {String[]} aliases
6412          */
6413         getAliasesByName: function(name) {
6414             return this.maps.nameToAliases[name] || [];
6415         },
6416
6417         /**
6418          * Get the name of the class by its reference or its instance.
6419          *
6420          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6421          *
6422          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6423          *
6424          * @param {Ext.Class/Object} object
6425          * @return {String} className
6426          */
6427         getName: function(object) {
6428             return object && object.$className || '';
6429         },
6430
6431         /**
6432          * Get the class of the provided object; returns null if it's not an instance
6433          * of any class created with Ext.define.
6434          *
6435          *     var component = new Ext.Component();
6436          *
6437          *     Ext.ClassManager.getClass(component); // returns Ext.Component
6438          *
6439          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6440          *
6441          * @param {Object} object
6442          * @return {Ext.Class} class
6443          */
6444         getClass: function(object) {
6445             return object && object.self || null;
6446         },
6447
6448         /**
6449          * Defines a class.
6450          *
6451          * {@link Ext#define Ext.define} and {@link Ext.ClassManager#create Ext.ClassManager.create} are almost aliases
6452          * of each other, with the only exception that Ext.define allows definition of {@link Ext.Class#override overrides}.
6453          * To avoid trouble, always use Ext.define.
6454          *
6455          *     Ext.define('My.awesome.Class', {
6456          *         someProperty: 'something',
6457          *         someMethod: function() { ... }
6458          *         ...
6459          *
6460          *     }, function() {
6461          *         alert('Created!');
6462          *         alert(this === My.awesome.Class); // alerts true
6463          *
6464          *         var myInstance = new this();
6465          *     });
6466          *
6467          * @param {String} className The class name to create in string dot-namespaced format, for example:
6468          * `My.very.awesome.Class`, `FeedViewer.plugin.CoolPager`. It is highly recommended to follow this simple convention:
6469          *
6470          * - The root and the class name are 'CamelCased'
6471          * - Everything else is lower-cased
6472          *
6473          * @param {Object} data The key-value pairs of properties to apply to this class. Property names can be of any valid
6474          * strings, except those in the reserved list below:
6475          *
6476          * - {@link Ext.Base#self self}
6477          * - {@link Ext.Class#alias alias}
6478          * - {@link Ext.Class#alternateClassName alternateClassName}
6479          * - {@link Ext.Class#config config}
6480          * - {@link Ext.Class#extend extend}
6481          * - {@link Ext.Class#inheritableStatics inheritableStatics}
6482          * - {@link Ext.Class#mixins mixins}
6483          * - {@link Ext.Class#override override} (only when using {@link Ext#define Ext.define})
6484          * - {@link Ext.Class#requires requires}
6485          * - {@link Ext.Class#singleton singleton}
6486          * - {@link Ext.Class#statics statics}
6487          * - {@link Ext.Class#uses uses}
6488          *
6489          * @param {Function} [createdFn] callback to execute after the class is created, the execution scope of which
6490          * (`this`) will be the newly created class itself.
6491          *
6492          * @return {Ext.Base}
6493          */
6494         create: function(className, data, createdFn) {
6495             var manager = this;
6496
6497
6498             data.$className = className;
6499
6500             return new Class(data, function() {
6501                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6502                     registeredPostprocessors = manager.postprocessors,
6503                     index = 0,
6504                     postprocessors = [],
6505                     postprocessor, process, i, ln;
6506
6507                 delete data.postprocessors;
6508
6509                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6510                     postprocessor = postprocessorStack[i];
6511
6512                     if (typeof postprocessor === 'string') {
6513                         postprocessor = registeredPostprocessors[postprocessor];
6514
6515                         if (!postprocessor.always) {
6516                             if (data[postprocessor.name] !== undefined) {
6517                                 postprocessors.push(postprocessor.fn);
6518                             }
6519                         }
6520                         else {
6521                             postprocessors.push(postprocessor.fn);
6522                         }
6523                     }
6524                     else {
6525                         postprocessors.push(postprocessor);
6526                     }
6527                 }
6528
6529                 process = function(clsName, cls, clsData) {
6530                     postprocessor = postprocessors[index++];
6531
6532                     if (!postprocessor) {
6533                         manager.set(className, cls);
6534
6535                         Ext.Loader.historyPush(className);
6536
6537                         if (createdFn) {
6538                             createdFn.call(cls, cls);
6539                         }
6540
6541                         return;
6542                     }
6543
6544                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6545                         process.apply(this, arguments);
6546                     }
6547                 };
6548
6549                 process.call(manager, className, this, data);
6550             });
6551         },
6552
6553         /**
6554          * Instantiate a class by its alias.
6555          *
6556          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6557          * attempt to load the class via synchronous loading.
6558          *
6559          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6560          *
6561          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6562          *
6563          * @param {String} alias
6564          * @param {Object...} args Additional arguments after the alias will be passed to the
6565          * class constructor.
6566          * @return {Object} instance
6567          */
6568         instantiateByAlias: function() {
6569             var alias = arguments[0],
6570                 args = slice.call(arguments),
6571                 className = this.getNameByAlias(alias);
6572
6573             if (!className) {
6574                 className = this.maps.aliasToName[alias];
6575
6576
6577
6578                 Ext.syncRequire(className);
6579             }
6580
6581             args[0] = className;
6582
6583             return this.instantiate.apply(this, args);
6584         },
6585
6586         /**
6587          * Instantiate a class by either full name, alias or alternate name.
6588          *
6589          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6590          * attempt to load the class via synchronous loading.
6591          *
6592          * For example, all these three lines return the same result:
6593          *
6594          *     // alias
6595          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6596          *
6597          *     // alternate name
6598          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6599          *
6600          *     // full class name
6601          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6602          *
6603          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6604          *
6605          * @param {String} name
6606          * @param {Object...} args Additional arguments after the name will be passed to the class' constructor.
6607          * @return {Object} instance
6608          */
6609         instantiate: function() {
6610             var name = arguments[0],
6611                 args = slice.call(arguments, 1),
6612                 alias = name,
6613                 possibleName, cls;
6614
6615             if (typeof name !== 'function') {
6616
6617                 cls = this.get(name);
6618             }
6619             else {
6620                 cls = name;
6621             }
6622
6623             // No record of this class name, it's possibly an alias, so look it up
6624             if (!cls) {
6625                 possibleName = this.getNameByAlias(name);
6626
6627                 if (possibleName) {
6628                     name = possibleName;
6629
6630                     cls = this.get(name);
6631                 }
6632             }
6633
6634             // Still no record of this class name, it's possibly an alternate name, so look it up
6635             if (!cls) {
6636                 possibleName = this.getNameByAlternate(name);
6637
6638                 if (possibleName) {
6639                     name = possibleName;
6640
6641                     cls = this.get(name);
6642                 }
6643             }
6644
6645             // Still not existing at this point, try to load it via synchronous mode as the last resort
6646             if (!cls) {
6647
6648                 Ext.syncRequire(name);
6649
6650                 cls = this.get(name);
6651             }
6652
6653
6654
6655             return this.getInstantiator(args.length)(cls, args);
6656         },
6657
6658         /**
6659          * @private
6660          * @param name
6661          * @param args
6662          */
6663         dynInstantiate: function(name, args) {
6664             args = Ext.Array.from(args, true);
6665             args.unshift(name);
6666
6667             return this.instantiate.apply(this, args);
6668         },
6669
6670         /**
6671          * @private
6672          * @param length
6673          */
6674         getInstantiator: function(length) {
6675             if (!this.instantiators[length]) {
6676                 var i = length,
6677                     args = [];
6678
6679                 for (i = 0; i < length; i++) {
6680                     args.push('a['+i+']');
6681                 }
6682
6683                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6684             }
6685
6686             return this.instantiators[length];
6687         },
6688
6689         /**
6690          * @private
6691          */
6692         postprocessors: {},
6693
6694         /**
6695          * @private
6696          */
6697         defaultPostprocessors: [],
6698
6699         /**
6700          * Register a post-processor function.
6701          *
6702          * @param {String} name
6703          * @param {Function} postprocessor
6704          */
6705         registerPostprocessor: function(name, fn, always) {
6706             this.postprocessors[name] = {
6707                 name: name,
6708                 always: always ||  false,
6709                 fn: fn
6710             };
6711
6712             return this;
6713         },
6714
6715         /**
6716          * Set the default post processors array stack which are applied to every class.
6717          *
6718          * @param {String/String[]} The name of a registered post processor or an array of registered names.
6719          * @return {Ext.ClassManager} this
6720          */
6721         setDefaultPostprocessors: function(postprocessors) {
6722             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6723
6724             return this;
6725         },
6726
6727         /**
6728          * Insert this post-processor at a specific position in the stack, optionally relative to
6729          * any existing post-processor
6730          *
6731          * @param {String} name The post-processor name. Note that it needs to be registered with
6732          * {@link Ext.ClassManager#registerPostprocessor} before this
6733          * @param {String} offset The insertion position. Four possible values are:
6734          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6735          * @param {String} relativeName
6736          * @return {Ext.ClassManager} this
6737          */
6738         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6739             var defaultPostprocessors = this.defaultPostprocessors,
6740                 index;
6741
6742             if (typeof offset === 'string') {
6743                 if (offset === 'first') {
6744                     defaultPostprocessors.unshift(name);
6745
6746                     return this;
6747                 }
6748                 else if (offset === 'last') {
6749                     defaultPostprocessors.push(name);
6750
6751                     return this;
6752                 }
6753
6754                 offset = (offset === 'after') ? 1 : -1;
6755             }
6756
6757             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6758
6759             if (index !== -1) {
6760                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6761             }
6762
6763             return this;
6764         },
6765
6766         /**
6767          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6768          * or class names. Expressions support wildcards:
6769          *
6770          *     // returns ['Ext.window.Window']
6771          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6772          *
6773          *     // returns ['widget.panel', 'widget.window', ...]
6774          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6775          *
6776          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6777          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6778          *
6779          * @param {String} expression
6780          * @return {String[]} classNames
6781          */
6782         getNamesByExpression: function(expression) {
6783             var nameToAliasesMap = this.maps.nameToAliases,
6784                 names = [],
6785                 name, alias, aliases, possibleName, regex, i, ln;
6786
6787
6788             if (expression.indexOf('*') !== -1) {
6789                 expression = expression.replace(/\*/g, '(.*?)');
6790                 regex = new RegExp('^' + expression + '$');
6791
6792                 for (name in nameToAliasesMap) {
6793                     if (nameToAliasesMap.hasOwnProperty(name)) {
6794                         aliases = nameToAliasesMap[name];
6795
6796                         if (name.search(regex) !== -1) {
6797                             names.push(name);
6798                         }
6799                         else {
6800                             for (i = 0, ln = aliases.length; i < ln; i++) {
6801                                 alias = aliases[i];
6802
6803                                 if (alias.search(regex) !== -1) {
6804                                     names.push(name);
6805                                     break;
6806                                 }
6807                             }
6808                         }
6809                     }
6810                 }
6811
6812             } else {
6813                 possibleName = this.getNameByAlias(expression);
6814
6815                 if (possibleName) {
6816                     names.push(possibleName);
6817                 } else {
6818                     possibleName = this.getNameByAlternate(expression);
6819
6820                     if (possibleName) {
6821                         names.push(possibleName);
6822                     } else {
6823                         names.push(expression);
6824                     }
6825                 }
6826             }
6827
6828             return names;
6829         }
6830     };
6831
6832     var defaultPostprocessors = Manager.defaultPostprocessors;
6833     //<feature classSystem.alias>
6834
6835     /**
6836      * @cfg {String[]} alias
6837      * @member Ext.Class
6838      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
6839      *
6840      *     Ext.define('MyApp.CoolPanel', {
6841      *         extend: 'Ext.panel.Panel',
6842      *         alias: ['widget.coolpanel'],
6843      *         title: 'Yeah!'
6844      *     });
6845      *
6846      *     // Using Ext.create
6847      *     Ext.widget('widget.coolpanel');
6848      *     // Using the shorthand for widgets and in xtypes
6849      *     Ext.widget('panel', {
6850      *         items: [
6851      *             {xtype: 'coolpanel', html: 'Foo'},
6852      *             {xtype: 'coolpanel', html: 'Bar'}
6853      *         ]
6854      *     });
6855      */
6856     Manager.registerPostprocessor('alias', function(name, cls, data) {
6857         var aliases = data.alias,
6858             i, ln;
6859
6860         delete data.alias;
6861
6862         for (i = 0, ln = aliases.length; i < ln; i++) {
6863             alias = aliases[i];
6864
6865             this.setAlias(cls, alias);
6866         }
6867     });
6868
6869     /**
6870      * @cfg {Boolean} singleton
6871      * @member Ext.Class
6872      * When set to true, the class will be instantiated as singleton.  For example:
6873      *
6874      *     Ext.define('Logger', {
6875      *         singleton: true,
6876      *         log: function(msg) {
6877      *             console.log(msg);
6878      *         }
6879      *     });
6880      *
6881      *     Logger.log('Hello');
6882      */
6883     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6884         fn.call(this, name, new cls(), data);
6885         return false;
6886     });
6887
6888     /**
6889      * @cfg {String/String[]} alternateClassName
6890      * @member Ext.Class
6891      * Defines alternate names for this class.  For example:
6892      *
6893      *     Ext.define('Developer', {
6894      *         alternateClassName: ['Coder', 'Hacker'],
6895      *         code: function(msg) {
6896      *             alert('Typing... ' + msg);
6897      *         }
6898      *     });
6899      *
6900      *     var joe = Ext.create('Developer');
6901      *     joe.code('stackoverflow');
6902      *
6903      *     var rms = Ext.create('Hacker');
6904      *     rms.code('hack hack');
6905      */
6906     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6907         var alternates = data.alternateClassName,
6908             i, ln, alternate;
6909
6910         if (!(alternates instanceof Array)) {
6911             alternates = [alternates];
6912         }
6913
6914         for (i = 0, ln = alternates.length; i < ln; i++) {
6915             alternate = alternates[i];
6916
6917
6918             this.set(alternate, cls);
6919         }
6920     });
6921
6922     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6923
6924     Ext.apply(Ext, {
6925         /**
6926          * @method
6927          * @member Ext
6928          * @alias Ext.ClassManager#instantiate
6929          */
6930         create: alias(Manager, 'instantiate'),
6931
6932         /**
6933          * @private
6934          * API to be stablized
6935          *
6936          * @param {Object} item
6937          * @param {String} namespace
6938          */
6939         factory: function(item, namespace) {
6940             if (item instanceof Array) {
6941                 var i, ln;
6942
6943                 for (i = 0, ln = item.length; i < ln; i++) {
6944                     item[i] = Ext.factory(item[i], namespace);
6945                 }
6946
6947                 return item;
6948             }
6949
6950             var isString = (typeof item === 'string');
6951
6952             if (isString || (item instanceof Object && item.constructor === Object)) {
6953                 var name, config = {};
6954
6955                 if (isString) {
6956                     name = item;
6957                 }
6958                 else {
6959                     name = item.className;
6960                     config = item;
6961                     delete config.className;
6962                 }
6963
6964                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6965                     name = namespace + '.' + Ext.String.capitalize(name);
6966                 }
6967
6968                 return Ext.create(name, config);
6969             }
6970
6971             if (typeof item === 'function') {
6972                 return Ext.create(item);
6973             }
6974
6975             return item;
6976         },
6977
6978         /**
6979          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6980          *
6981          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6982          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6983          *
6984          * @method
6985          * @member Ext
6986          * @param {String} name  xtype of the widget to create.
6987          * @param {Object...} args  arguments for the widget constructor.
6988          * @return {Object} widget instance
6989          */
6990         widget: function(name) {
6991             var args = slice.call(arguments);
6992             args[0] = 'widget.' + name;
6993
6994             return Manager.instantiateByAlias.apply(Manager, args);
6995         },
6996
6997         /**
6998          * @method
6999          * @member Ext
7000          * @alias Ext.ClassManager#instantiateByAlias
7001          */
7002         createByAlias: alias(Manager, 'instantiateByAlias'),
7003
7004         /**
7005          * @cfg {String} override
7006          * @member Ext.Class
7007          * 
7008          * Defines an override applied to a class. Note that **overrides can only be created using
7009          * {@link Ext#define}.** {@link Ext.ClassManager#create} only creates classes.
7010          * 
7011          * To define an override, include the override property. The content of an override is
7012          * aggregated with the specified class in order to extend or modify that class. This can be
7013          * as simple as setting default property values or it can extend and/or replace methods.
7014          * This can also extend the statics of the class.
7015          *
7016          * One use for an override is to break a large class into manageable pieces.
7017          *
7018          *      // File: /src/app/Panel.js
7019          *
7020          *      Ext.define('My.app.Panel', {
7021          *          extend: 'Ext.panel.Panel',
7022          *          requires: [
7023          *              'My.app.PanelPart2',
7024          *              'My.app.PanelPart3'
7025          *          ]
7026          *
7027          *          constructor: function (config) {
7028          *              this.callSuper(arguments); // calls Ext.panel.Panel's constructor
7029          *              //...
7030          *          },
7031          *
7032          *          statics: {
7033          *              method: function () {
7034          *                  return 'abc';
7035          *              }
7036          *          }
7037          *      });
7038          *
7039          *      // File: /src/app/PanelPart2.js
7040          *      Ext.define('My.app.PanelPart2', {
7041          *          override: 'My.app.Panel',
7042          *
7043          *          constructor: function (config) {
7044          *              this.callSuper(arguments); // calls My.app.Panel's constructor
7045          *              //...
7046          *          }
7047          *      });
7048          *
7049          * Another use of overrides is to provide optional parts of classes that can be
7050          * independently required. In this case, the class may even be unaware of the
7051          * override altogether.
7052          *
7053          *      Ext.define('My.ux.CoolTip', {
7054          *          override: 'Ext.tip.ToolTip',
7055          *
7056          *          constructor: function (config) {
7057          *              this.callSuper(arguments); // calls Ext.tip.ToolTip's constructor
7058          *              //...
7059          *          }
7060          *      });
7061          *
7062          * The above override can now be required as normal.
7063          *
7064          *      Ext.define('My.app.App', {
7065          *          requires: [
7066          *              'My.ux.CoolTip'
7067          *          ]
7068          *      });
7069          *
7070          * Overrides can also contain statics:
7071          *
7072          *      Ext.define('My.app.BarMod', {
7073          *          override: 'Ext.foo.Bar',
7074          *
7075          *          statics: {
7076          *              method: function (x) {
7077          *                  return this.callSuper([x * 2]); // call Ext.foo.Bar.method
7078          *              }
7079          *          }
7080          *      });
7081          *
7082          * IMPORTANT: An override is only included in a build if the class it overrides is
7083          * required. Otherwise, the override, like the target class, is not included.
7084          */
7085         
7086         /**
7087          * @method
7088          *
7089          * @member Ext
7090          * @alias Ext.ClassManager#create
7091          */
7092         define: function (className, data, createdFn) {
7093             if (!data.override) {
7094                 return Manager.create.apply(Manager, arguments);
7095             }
7096
7097             var requires = data.requires,
7098                 uses = data.uses,
7099                 overrideName = className;
7100
7101             className = data.override;
7102
7103             // hoist any 'requires' or 'uses' from the body onto the faux class:
7104             data = Ext.apply({}, data);
7105             delete data.requires;
7106             delete data.uses;
7107             delete data.override;
7108
7109             // make sure className is in the requires list:
7110             if (typeof requires == 'string') {
7111                 requires = [ className, requires ];
7112             } else if (requires) {
7113                 requires = requires.slice(0);
7114                 requires.unshift(className);
7115             } else {
7116                 requires = [ className ];
7117             }
7118
7119 // TODO - we need to rework this to allow the override to not require the target class
7120 //  and rather 'wait' for it in such a way that if the target class is not in the build,
7121 //  neither are any of its overrides.
7122 //
7123 //  Also, this should process the overrides for a class ASAP (ideally before any derived
7124 //  classes) if the target class 'requires' the overrides. Without some special handling, the
7125 //  overrides so required will be processed before the class and have to be bufferred even
7126 //  in a build.
7127 //
7128 // TODO - we should probably support the "config" processor on an override (to config new
7129 //  functionaliy like Aria) and maybe inheritableStatics (although static is now supported
7130 //  by callSuper). If inheritableStatics causes those statics to be included on derived class
7131 //  constructors, that probably means "no" to this since an override can come after other
7132 //  classes extend the target.
7133             return Manager.create(overrideName, {
7134                     requires: requires,
7135                     uses: uses,
7136                     isPartial: true,
7137                     constructor: function () {
7138                     }
7139                 }, function () {
7140                     var cls = Manager.get(className);
7141                     if (cls.override) { // if (normal class)
7142                         cls.override(data);
7143                     } else { // else (singleton)
7144                         cls.self.override(data);
7145                     }
7146
7147                     if (createdFn) {
7148                         // called once the override is applied and with the context of the
7149                         // overridden class (the override itself is a meaningless, name-only
7150                         // thing).
7151                         createdFn.call(cls);
7152                     }
7153                 });
7154         },
7155
7156         /**
7157          * @method
7158          * @member Ext
7159          * @alias Ext.ClassManager#getName
7160          */
7161         getClassName: alias(Manager, 'getName'),
7162
7163         /**
7164          * Returns the displayName property or className or object.
7165          * When all else fails, returns "Anonymous".
7166          * @param {Object} object
7167          * @return {String}
7168          */
7169         getDisplayName: function(object) {
7170             if (object.displayName) {
7171                 return object.displayName;
7172             }
7173
7174             if (object.$name && object.$class) {
7175                 return Ext.getClassName(object.$class) + '#' + object.$name;
7176             }
7177
7178             if (object.$className) {
7179                 return object.$className;
7180             }
7181
7182             return 'Anonymous';
7183         },
7184
7185         /**
7186          * @method
7187          * @member Ext
7188          * @alias Ext.ClassManager#getClass
7189          */
7190         getClass: alias(Manager, 'getClass'),
7191
7192         /**
7193          * Creates namespaces to be used for scoping variables and classes so that they are not global.
7194          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
7195          *
7196          *     Ext.namespace('Company', 'Company.data');
7197          *
7198          *     // equivalent and preferable to the above syntax
7199          *     Ext.namespace('Company.data');
7200          *
7201          *     Company.Widget = function() { ... };
7202          *
7203          *     Company.data.CustomStore = function(config) { ... };
7204          *
7205          * @method
7206          * @member Ext
7207          * @param {String} namespace1
7208          * @param {String} namespace2
7209          * @param {String} etc
7210          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7211          */
7212         namespace: alias(Manager, 'createNamespaces')
7213     });
7214
7215     /**
7216      * Old name for {@link Ext#widget}.
7217      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7218      * @method
7219      * @member Ext
7220      * @alias Ext#widget
7221      */
7222     Ext.createWidget = Ext.widget;
7223
7224     /**
7225      * Convenient alias for {@link Ext#namespace Ext.namespace}
7226      * @method
7227      * @member Ext
7228      * @alias Ext#namespace
7229      */
7230     Ext.ns = Ext.namespace;
7231
7232     Class.registerPreprocessor('className', function(cls, data) {
7233         if (data.$className) {
7234             cls.$className = data.$className;
7235         }
7236     }, true);
7237
7238     Class.setDefaultPreprocessorPosition('className', 'first');
7239
7240     Class.registerPreprocessor('xtype', function(cls, data) {
7241         var xtypes = Ext.Array.from(data.xtype),
7242             widgetPrefix = 'widget.',
7243             aliases = Ext.Array.from(data.alias),
7244             i, ln, xtype;
7245
7246         data.xtype = xtypes[0];
7247         data.xtypes = xtypes;
7248
7249         aliases = data.alias = Ext.Array.from(data.alias);
7250
7251         for (i = 0,ln = xtypes.length; i < ln; i++) {
7252             xtype = xtypes[i];
7253
7254
7255             aliases.push(widgetPrefix + xtype);
7256         }
7257
7258         data.alias = aliases;
7259     });
7260
7261     Class.setDefaultPreprocessorPosition('xtype', 'last');
7262
7263     Class.registerPreprocessor('alias', function(cls, data) {
7264         var aliases = Ext.Array.from(data.alias),
7265             xtypes = Ext.Array.from(data.xtypes),
7266             widgetPrefix = 'widget.',
7267             widgetPrefixLength = widgetPrefix.length,
7268             i, ln, alias, xtype;
7269
7270         for (i = 0, ln = aliases.length; i < ln; i++) {
7271             alias = aliases[i];
7272
7273
7274             if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
7275                 xtype = alias.substring(widgetPrefixLength);
7276                 Ext.Array.include(xtypes, xtype);
7277
7278                 if (!cls.xtype) {
7279                     cls.xtype = data.xtype = xtype;
7280                 }
7281             }
7282         }
7283
7284         data.alias = aliases;
7285         data.xtypes = xtypes;
7286     });
7287
7288     Class.setDefaultPreprocessorPosition('alias', 'last');
7289
7290 })(Ext.Class, Ext.Function.alias);
7291
7292 /**
7293  * @class Ext.Loader
7294  * @singleton
7295  * @author Jacky Nguyen <jacky@sencha.com>
7296  * @docauthor Jacky Nguyen <jacky@sencha.com>
7297  *
7298  * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7299  * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7300  * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7301  * of each approach:
7302  *
7303  * # Asynchronous Loading
7304  *
7305  * - Advantages:
7306  *       + Cross-domain
7307  *       + No web server needed: you can run the application via the file system protocol
7308  *     (i.e: `file://path/to/your/index.html`)
7309  *       + Best possible debugging experience: error messages come with the exact file name and line number
7310  *
7311  * - Disadvantages:
7312  *       + Dependencies need to be specified before-hand
7313  *
7314  * ### Method 1: Explicitly include what you need:
7315  *
7316  *     // Syntax
7317  *     Ext.require({String/Array} expressions);
7318  *
7319  *     // Example: Single alias
7320  *     Ext.require('widget.window');
7321  *
7322  *     // Example: Single class name
7323  *     Ext.require('Ext.window.Window');
7324  *
7325  *     // Example: Multiple aliases / class names mix
7326  *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7327  *
7328  *     // Wildcards
7329  *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7330  *
7331  * ### Method 2: Explicitly exclude what you don't need:
7332  *
7333  *     // Syntax: Note that it must be in this chaining format.
7334  *     Ext.exclude({String/Array} expressions)
7335  *        .require({String/Array} expressions);
7336  *
7337  *     // Include everything except Ext.data.*
7338  *     Ext.exclude('Ext.data.*').require('*'); 
7339  *
7340  *     // Include all widgets except widget.checkbox*,
7341  *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7342  *     Ext.exclude('widget.checkbox*').require('widget.*');
7343  *
7344  * # Synchronous Loading on Demand
7345  *
7346  * - Advantages:
7347  *       + There's no need to specify dependencies before-hand, which is always the convenience of including
7348  *     ext-all.js before
7349  *
7350  * - Disadvantages:
7351  *       + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7352  *       + Must be from the same domain due to XHR restriction
7353  *       + Need a web server, same reason as above
7354  *
7355  * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7356  *
7357  *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7358  *
7359  *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7360  *
7361  *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7362  *
7363  * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7364  * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7365  * the given class and all its dependencies.
7366  *
7367  * # Hybrid Loading - The Best of Both Worlds
7368  *
7369  * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7370  *
7371  * ### Step 1: Start writing your application using synchronous approach.
7372  *
7373  * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7374  *
7375  *     Ext.onReady(function(){
7376  *         var window = Ext.createWidget('window', {
7377  *             width: 500,
7378  *             height: 300,
7379  *             layout: {
7380  *                 type: 'border',
7381  *                 padding: 5
7382  *             },
7383  *             title: 'Hello Dialog',
7384  *             items: [{
7385  *                 title: 'Navigation',
7386  *                 collapsible: true,
7387  *                 region: 'west',
7388  *                 width: 200,
7389  *                 html: 'Hello',
7390  *                 split: true
7391  *             }, {
7392  *                 title: 'TabPanel',
7393  *                 region: 'center'
7394  *             }]
7395  *         });
7396  *
7397  *         window.show();
7398  *     })
7399  *
7400  * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7401  *
7402  *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7403  *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7404  *
7405  * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7406  *
7407  *     Ext.require('Ext.window.Window');
7408  *     Ext.require('Ext.layout.container.Border');
7409  *
7410  *     Ext.onReady(...);
7411  *
7412  * Everything should now load via asynchronous mode.
7413  *
7414  * # Deployment
7415  *
7416  * It's important to note that dynamic loading should only be used during development on your local machines.
7417  * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7418  * the whole process of transitioning from / to between development / maintenance and production as easy as
7419  * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7420  * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7421  * array into one, then include it on top of your application.
7422  *
7423  * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7424  */
7425 (function(Manager, Class, flexSetter, alias) {
7426
7427     var
7428         dependencyProperties = ['extend', 'mixins', 'requires'],
7429         Loader;
7430
7431     Loader = Ext.Loader = {
7432         /**
7433          * @private
7434          */
7435         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7436
7437         /**
7438          * Flag indicating whether there are still files being loaded
7439          * @private
7440          */
7441         isLoading: false,
7442
7443         /**
7444          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7445          * {
7446          *      requires: [...], // The required classes for this queue item
7447          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
7448          * }
7449          * @private
7450          */
7451         queue: [],
7452
7453         /**
7454          * Maintain the list of files that have already been handled so that they never get double-loaded
7455          * @private
7456          */
7457         isFileLoaded: {},
7458
7459         /**
7460          * Maintain the list of listeners to execute when all required scripts are fully loaded
7461          * @private
7462          */
7463         readyListeners: [],
7464
7465         /**
7466          * Contains optional dependencies to be loaded last
7467          * @private
7468          */
7469         optionalRequires: [],
7470
7471         /**
7472          * Map of fully qualified class names to an array of dependent classes.
7473          * @private
7474          */
7475         requiresMap: {},
7476
7477         /**
7478          * @private
7479          */
7480         numPendingFiles: 0,
7481
7482         /**
7483          * @private
7484          */
7485         numLoadedFiles: 0,
7486
7487         /** @private */
7488         hasFileLoadError: false,
7489
7490         /**
7491          * @private
7492          */
7493         classNameToFilePathMap: {},
7494
7495         /**
7496          * @property {String[]} history
7497          * An array of class names to keep track of the dependency loading order.
7498          * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7499          */
7500         history: [],
7501
7502         /**
7503          * Configuration
7504          * @private
7505          */
7506         config: {
7507             /**
7508              * @cfg {Boolean} enabled
7509              * Whether or not to enable the dynamic dependency loading feature.
7510              */
7511             enabled: false,
7512
7513             /**
7514              * @cfg {Boolean} disableCaching
7515              * Appends current timestamp to script files to prevent caching.
7516              */
7517             disableCaching: true,
7518
7519             /**
7520              * @cfg {String} disableCachingParam
7521              * The get parameter name for the cache buster's timestamp.
7522              */
7523             disableCachingParam: '_dc',
7524
7525             /**
7526              * @cfg {Object} paths
7527              * The mapping from namespaces to file paths
7528              *
7529              *     {
7530              *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7531              *                     // loaded from ./layout/Container.js
7532              *
7533              *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
7534              *                                     // ./src/my_own_folder/layout/Container.js
7535              *     }
7536              *
7537              * Note that all relative paths are relative to the current HTML document.
7538              * If not being specified, for example, `Other.awesome.Class`
7539              * will simply be loaded from `./Other/awesome/Class.js`
7540              */
7541             paths: {
7542                 'Ext': '.'
7543             }
7544         },
7545
7546         /**
7547          * Set the configuration for the loader. This should be called right after ext-core.js
7548          * (or ext-core-debug.js) is included in the page, e.g.:
7549          *
7550          *     <script type="text/javascript" src="ext-core-debug.js"></script>
7551          *     <script type="text/javascript">
7552          *       Ext.Loader.setConfig({
7553          *           enabled: true,
7554          *           paths: {
7555          *               'My': 'my_own_path'
7556          *           }
7557          *       });
7558          *     <script>
7559          *     <script type="text/javascript">
7560          *       Ext.require(...);
7561          *
7562          *       Ext.onReady(function() {
7563          *           // application code here
7564          *       });
7565          *     </script>
7566          *
7567          * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7568          *
7569          * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
7570          * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
7571          * @return {Ext.Loader} this
7572          */
7573         setConfig: function(name, value) {
7574             if (Ext.isObject(name) && arguments.length === 1) {
7575                 Ext.Object.merge(this.config, name);
7576             }
7577             else {
7578                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7579             }
7580
7581             return this;
7582         },
7583
7584         /**
7585          * Get the config value corresponding to the specified name.
7586          * If no name is given, will return the config object.
7587          * @param {String} name The config property name
7588          * @return {Object}
7589          */
7590         getConfig: function(name) {
7591             if (name) {
7592                 return this.config[name];
7593             }
7594
7595             return this.config;
7596         },
7597
7598         /**
7599          * Sets the path of a namespace. For Example:
7600          *
7601          *     Ext.Loader.setPath('Ext', '.');
7602          *
7603          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7604          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7605          * @return {Ext.Loader} this
7606          * @method
7607          */
7608         setPath: flexSetter(function(name, path) {
7609             this.config.paths[name] = path;
7610
7611             return this;
7612         }),
7613
7614         /**
7615          * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7616          * For example:
7617          *
7618          *     Ext.Loader.setPath('My', '/path/to/My');
7619          *
7620          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7621          *
7622          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7623          *
7624          *     Ext.Loader.setPath({
7625          *         'My': '/path/to/lib',
7626          *         'My.awesome': '/other/path/for/awesome/stuff',
7627          *         'My.awesome.more': '/more/awesome/path'
7628          *     });
7629          *
7630          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7631          *
7632          *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7633          *
7634          *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7635          *
7636          *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7637          *
7638          * @param {String} className
7639          * @return {String} path
7640          */
7641         getPath: function(className) {
7642             var path = '',
7643                 paths = this.config.paths,
7644                 prefix = this.getPrefix(className);
7645
7646             if (prefix.length > 0) {
7647                 if (prefix === className) {
7648                     return paths[prefix];
7649                 }
7650
7651                 path = paths[prefix];
7652                 className = className.substring(prefix.length + 1);
7653             }
7654
7655             if (path.length > 0) {
7656                 path += '/';
7657             }
7658
7659             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7660         },
7661
7662         /**
7663          * @private
7664          * @param {String} className
7665          */
7666         getPrefix: function(className) {
7667             var paths = this.config.paths,
7668                 prefix, deepestPrefix = '';
7669
7670             if (paths.hasOwnProperty(className)) {
7671                 return className;
7672             }
7673
7674             for (prefix in paths) {
7675                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7676                     if (prefix.length > deepestPrefix.length) {
7677                         deepestPrefix = prefix;
7678                     }
7679                 }
7680             }
7681
7682             return deepestPrefix;
7683         },
7684
7685         /**
7686          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7687          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7688          * empty
7689          * @private
7690          */
7691         refreshQueue: function() {
7692             var ln = this.queue.length,
7693                 i, item, j, requires;
7694
7695             if (ln === 0) {
7696                 this.triggerReady();
7697                 return;
7698             }
7699
7700             for (i = 0; i < ln; i++) {
7701                 item = this.queue[i];
7702
7703                 if (item) {
7704                     requires = item.requires;
7705
7706                     // Don't bother checking when the number of files loaded
7707                     // is still less than the array length
7708                     if (requires.length > this.numLoadedFiles) {
7709                         continue;
7710                     }
7711
7712                     j = 0;
7713
7714                     do {
7715                         if (Manager.isCreated(requires[j])) {
7716                             // Take out from the queue
7717                             Ext.Array.erase(requires, j, 1);
7718                         }
7719                         else {
7720                             j++;
7721                         }
7722                     } while (j < requires.length);
7723
7724                     if (item.requires.length === 0) {
7725                         Ext.Array.erase(this.queue, i, 1);
7726                         item.callback.call(item.scope);
7727                         this.refreshQueue();
7728                         break;
7729                     }
7730                 }
7731             }
7732
7733             return this;
7734         },
7735
7736         /**
7737          * Inject a script element to document's head, call onLoad and onError accordingly
7738          * @private
7739          */
7740         injectScriptElement: function(url, onLoad, onError, scope) {
7741             var script = document.createElement('script'),
7742                 me = this,
7743                 onLoadFn = function() {
7744                     me.cleanupScriptElement(script);
7745                     onLoad.call(scope);
7746                 },
7747                 onErrorFn = function() {
7748                     me.cleanupScriptElement(script);
7749                     onError.call(scope);
7750                 };
7751
7752             script.type = 'text/javascript';
7753             script.src = url;
7754             script.onload = onLoadFn;
7755             script.onerror = onErrorFn;
7756             script.onreadystatechange = function() {
7757                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7758                     onLoadFn();
7759                 }
7760             };
7761
7762             this.documentHead.appendChild(script);
7763
7764             return script;
7765         },
7766
7767         /**
7768          * @private
7769          */
7770         cleanupScriptElement: function(script) {
7771             script.onload = null;
7772             script.onreadystatechange = null;
7773             script.onerror = null;
7774
7775             return this;
7776         },
7777
7778         /**
7779          * Load a script file, supports both asynchronous and synchronous approaches
7780          *
7781          * @param {String} url
7782          * @param {Function} onLoad
7783          * @param {Object} scope
7784          * @param {Boolean} synchronous
7785          * @private
7786          */
7787         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7788             var me = this,
7789                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7790                 fileName = url.split('/').pop(),
7791                 isCrossOriginRestricted = false,
7792                 xhr, status, onScriptError;
7793
7794             scope = scope || this;
7795
7796             this.isLoading = true;
7797
7798             if (!synchronous) {
7799                 onScriptError = function() {
7800                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7801                 };
7802
7803                 if (!Ext.isReady && Ext.onDocumentReady) {
7804                     Ext.onDocumentReady(function() {
7805                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7806                     });
7807                 }
7808                 else {
7809                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7810                 }
7811             }
7812             else {
7813                 if (typeof XMLHttpRequest !== 'undefined') {
7814                     xhr = new XMLHttpRequest();
7815                 } else {
7816                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7817                 }
7818
7819                 try {
7820                     xhr.open('GET', noCacheUrl, false);
7821                     xhr.send(null);
7822                 } catch (e) {
7823                     isCrossOriginRestricted = true;
7824                 }
7825
7826                 status = (xhr.status === 1223) ? 204 : xhr.status;
7827
7828                 if (!isCrossOriginRestricted) {
7829                     isCrossOriginRestricted = (status === 0);
7830                 }
7831
7832                 if (isCrossOriginRestricted
7833                 ) {
7834                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7835                                        "being loaded from a different domain or from the local file system whereby cross origin " +
7836                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
7837                                        "Ext.require instead.", synchronous);
7838                 }
7839                 else if (status >= 200 && status < 300
7840                 ) {
7841                     // Firebug friendly, file names are still shown even though they're eval'ed code
7842                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7843
7844                     onLoad.call(scope);
7845                 }
7846                 else {
7847                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7848                                        "verify that the file exists. " +
7849                                        "XHR status code: " + status, synchronous);
7850                 }
7851
7852                 // Prevent potential IE memory leak
7853                 xhr = null;
7854             }
7855         },
7856
7857         /**
7858          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7859          * Can be chained with more `require` and `exclude` methods, e.g.:
7860          *
7861          *     Ext.exclude('Ext.data.*').require('*');
7862          *
7863          *     Ext.exclude('widget.button*').require('widget.*');
7864          *
7865          * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
7866          *
7867          * @param {String/String[]} excludes
7868          * @return {Object} object contains `require` method for chaining
7869          */
7870         exclude: function(excludes) {
7871             var me = this;
7872
7873             return {
7874                 require: function(expressions, fn, scope) {
7875                     return me.require(expressions, fn, scope, excludes);
7876                 },
7877
7878                 syncRequire: function(expressions, fn, scope) {
7879                     return me.syncRequire(expressions, fn, scope, excludes);
7880                 }
7881             };
7882         },
7883
7884         /**
7885          * Synchronously loads all classes by the given names and all their direct dependencies;
7886          * optionally executes the given callback function when finishes, within the optional scope.
7887          *
7888          * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
7889          *
7890          * @param {String/String[]} expressions Can either be a string or an array of string
7891          * @param {Function} fn (Optional) The callback function
7892          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7893          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7894          */
7895         syncRequire: function() {
7896             this.syncModeEnabled = true;
7897             this.require.apply(this, arguments);
7898             this.refreshQueue();
7899             this.syncModeEnabled = false;
7900         },
7901
7902         /**
7903          * Loads all classes by the given names and all their direct dependencies;
7904          * optionally executes the given callback function when finishes, within the optional scope.
7905          *
7906          * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
7907          *
7908          * @param {String/String[]} expressions Can either be a string or an array of string
7909          * @param {Function} fn (Optional) The callback function
7910          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7911          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7912          */
7913         require: function(expressions, fn, scope, excludes) {
7914             var filePath, expression, exclude, className, excluded = {},
7915                 excludedClassNames = [],
7916                 possibleClassNames = [],
7917                 possibleClassName, classNames = [],
7918                 i, j, ln, subLn;
7919
7920             expressions = Ext.Array.from(expressions);
7921             excludes = Ext.Array.from(excludes);
7922
7923             fn = fn || Ext.emptyFn;
7924
7925             scope = scope || Ext.global;
7926
7927             for (i = 0, ln = excludes.length; i < ln; i++) {
7928                 exclude = excludes[i];
7929
7930                 if (typeof exclude === 'string' && exclude.length > 0) {
7931                     excludedClassNames = Manager.getNamesByExpression(exclude);
7932
7933                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7934                         excluded[excludedClassNames[j]] = true;
7935                     }
7936                 }
7937             }
7938
7939             for (i = 0, ln = expressions.length; i < ln; i++) {
7940                 expression = expressions[i];
7941
7942                 if (typeof expression === 'string' && expression.length > 0) {
7943                     possibleClassNames = Manager.getNamesByExpression(expression);
7944
7945                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7946                         possibleClassName = possibleClassNames[j];
7947
7948                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7949                             Ext.Array.include(classNames, possibleClassName);
7950                         }
7951                     }
7952                 }
7953             }
7954
7955             // If the dynamic dependency feature is not being used, throw an error
7956             // if the dependencies are not defined
7957             if (!this.config.enabled) {
7958                 if (classNames.length > 0) {
7959                     Ext.Error.raise({
7960                         sourceClass: "Ext.Loader",
7961                         sourceMethod: "require",
7962                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7963                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7964                     });
7965                 }
7966             }
7967
7968             if (classNames.length === 0) {
7969                 fn.call(scope);
7970                 return this;
7971             }
7972
7973             this.queue.push({
7974                 requires: classNames,
7975                 callback: fn,
7976                 scope: scope
7977             });
7978
7979             classNames = classNames.slice();
7980
7981             for (i = 0, ln = classNames.length; i < ln; i++) {
7982                 className = classNames[i];
7983
7984                 if (!this.isFileLoaded.hasOwnProperty(className)) {
7985                     this.isFileLoaded[className] = false;
7986
7987                     filePath = this.getPath(className);
7988
7989                     this.classNameToFilePathMap[className] = filePath;
7990
7991                     this.numPendingFiles++;
7992
7993                     this.loadScriptFile(
7994                         filePath,
7995                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7996                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7997                         this,
7998                         this.syncModeEnabled
7999                     );
8000                 }
8001             }
8002
8003             return this;
8004         },
8005
8006         /**
8007          * @private
8008          * @param {String} className
8009          * @param {String} filePath
8010          */
8011         onFileLoaded: function(className, filePath) {
8012             this.numLoadedFiles++;
8013
8014             this.isFileLoaded[className] = true;
8015
8016             this.numPendingFiles--;
8017
8018             if (this.numPendingFiles === 0) {
8019                 this.refreshQueue();
8020             }
8021
8022
8023         },
8024
8025         /**
8026          * @private
8027          */
8028         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
8029             this.numPendingFiles--;
8030             this.hasFileLoadError = true;
8031
8032         },
8033
8034         /**
8035          * @private
8036          */
8037         addOptionalRequires: function(requires) {
8038             var optionalRequires = this.optionalRequires,
8039                 i, ln, require;
8040
8041             requires = Ext.Array.from(requires);
8042
8043             for (i = 0, ln = requires.length; i < ln; i++) {
8044                 require = requires[i];
8045
8046                 Ext.Array.include(optionalRequires, require);
8047             }
8048
8049             return this;
8050         },
8051
8052         /**
8053          * @private
8054          */
8055         triggerReady: function(force) {
8056             var readyListeners = this.readyListeners,
8057                 optionalRequires, listener;
8058
8059             if (this.isLoading || force) {
8060                 this.isLoading = false;
8061
8062                 if (this.optionalRequires.length) {
8063                     // Clone then empty the array to eliminate potential recursive loop issue
8064                     optionalRequires = Ext.Array.clone(this.optionalRequires);
8065
8066                     // Empty the original array
8067                     this.optionalRequires.length = 0;
8068
8069                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
8070                     return this;
8071                 }
8072
8073                 while (readyListeners.length) {
8074                     listener = readyListeners.shift();
8075                     listener.fn.call(listener.scope);
8076
8077                     if (this.isLoading) {
8078                         return this;
8079                     }
8080                 }
8081             }
8082
8083             return this;
8084         },
8085
8086         /**
8087          * Adds new listener to be executed when all required scripts are fully loaded.
8088          *
8089          * @param {Function} fn The function callback to be executed
8090          * @param {Object} scope The execution scope (`this`) of the callback function
8091          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
8092          */
8093         onReady: function(fn, scope, withDomReady, options) {
8094             var oldFn;
8095
8096             if (withDomReady !== false && Ext.onDocumentReady) {
8097                 oldFn = fn;
8098
8099                 fn = function() {
8100                     Ext.onDocumentReady(oldFn, scope, options);
8101                 };
8102             }
8103
8104             if (!this.isLoading) {
8105                 fn.call(scope);
8106             }
8107             else {
8108                 this.readyListeners.push({
8109                     fn: fn,
8110                     scope: scope
8111                 });
8112             }
8113         },
8114
8115         /**
8116          * @private
8117          * @param {String} className
8118          */
8119         historyPush: function(className) {
8120             if (className && this.isFileLoaded.hasOwnProperty(className)) {
8121                 Ext.Array.include(this.history, className);
8122             }
8123
8124             return this;
8125         }
8126     };
8127
8128     /**
8129      * @member Ext
8130      * @method require
8131      * @alias Ext.Loader#require
8132      */
8133     Ext.require = alias(Loader, 'require');
8134
8135     /**
8136      * @member Ext
8137      * @method syncRequire
8138      * @alias Ext.Loader#syncRequire
8139      */
8140     Ext.syncRequire = alias(Loader, 'syncRequire');
8141
8142     /**
8143      * @member Ext
8144      * @method exclude
8145      * @alias Ext.Loader#exclude
8146      */
8147     Ext.exclude = alias(Loader, 'exclude');
8148
8149     /**
8150      * @member Ext
8151      * @method onReady
8152      * @alias Ext.Loader#onReady
8153      */
8154     Ext.onReady = function(fn, scope, options) {
8155         Loader.onReady(fn, scope, true, options);
8156     };
8157
8158     /**
8159      * @cfg {String[]} requires
8160      * @member Ext.Class
8161      * List of classes that have to be loaded before instantiating this class.
8162      * For example:
8163      *
8164      *     Ext.define('Mother', {
8165      *         requires: ['Child'],
8166      *         giveBirth: function() {
8167      *             // we can be sure that child class is available.
8168      *             return new Child();
8169      *         }
8170      *     });
8171      */
8172     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
8173         var me = this,
8174             dependencies = [],
8175             className = Manager.getName(cls),
8176             i, j, ln, subLn, value, propertyName, propertyValue;
8177
8178         /*
8179         Basically loop through the dependencyProperties, look for string class names and push
8180         them into a stack, regardless of whether the property's value is a string, array or object. For example:
8181         {
8182               extend: 'Ext.MyClass',
8183               requires: ['Ext.some.OtherClass'],
8184               mixins: {
8185                   observable: 'Ext.util.Observable';
8186               }
8187         }
8188         which will later be transformed into:
8189         {
8190               extend: Ext.MyClass,
8191               requires: [Ext.some.OtherClass],
8192               mixins: {
8193                   observable: Ext.util.Observable;
8194               }
8195         }
8196         */
8197
8198         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8199             propertyName = dependencyProperties[i];
8200
8201             if (data.hasOwnProperty(propertyName)) {
8202                 propertyValue = data[propertyName];
8203
8204                 if (typeof propertyValue === 'string') {
8205                     dependencies.push(propertyValue);
8206                 }
8207                 else if (propertyValue instanceof Array) {
8208                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8209                         value = propertyValue[j];
8210
8211                         if (typeof value === 'string') {
8212                             dependencies.push(value);
8213                         }
8214                     }
8215                 }
8216                 else if (typeof propertyValue != 'function') {
8217                     for (j in propertyValue) {
8218                         if (propertyValue.hasOwnProperty(j)) {
8219                             value = propertyValue[j];
8220
8221                             if (typeof value === 'string') {
8222                                 dependencies.push(value);
8223                             }
8224                         }
8225                     }
8226                 }
8227             }
8228         }
8229
8230         if (dependencies.length === 0) {
8231 //            Loader.historyPush(className);
8232             return;
8233         }
8234
8235
8236         Loader.require(dependencies, function() {
8237             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8238                 propertyName = dependencyProperties[i];
8239
8240                 if (data.hasOwnProperty(propertyName)) {
8241                     propertyValue = data[propertyName];
8242
8243                     if (typeof propertyValue === 'string') {
8244                         data[propertyName] = Manager.get(propertyValue);
8245                     }
8246                     else if (propertyValue instanceof Array) {
8247                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8248                             value = propertyValue[j];
8249
8250                             if (typeof value === 'string') {
8251                                 data[propertyName][j] = Manager.get(value);
8252                             }
8253                         }
8254                     }
8255                     else if (typeof propertyValue != 'function') {
8256                         for (var k in propertyValue) {
8257                             if (propertyValue.hasOwnProperty(k)) {
8258                                 value = propertyValue[k];
8259
8260                                 if (typeof value === 'string') {
8261                                     data[propertyName][k] = Manager.get(value);
8262                                 }
8263                             }
8264                         }
8265                     }
8266                 }
8267             }
8268
8269             continueFn.call(me, cls, data);
8270         });
8271
8272         return false;
8273     }, true);
8274
8275     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8276
8277     /**
8278      * @cfg {String[]} uses
8279      * @member Ext.Class
8280      * List of classes to load together with this class.  These aren't neccessarily loaded before
8281      * this class is instantiated. For example:
8282      *
8283      *     Ext.define('Mother', {
8284      *         uses: ['Child'],
8285      *         giveBirth: function() {
8286      *             // This code might, or might not work:
8287      *             // return new Child();
8288      *
8289      *             // Instead use Ext.create() to load the class at the spot if not loaded already:
8290      *             return Ext.create('Child');
8291      *         }
8292      *     });
8293      */
8294     Manager.registerPostprocessor('uses', function(name, cls, data) {
8295         var uses = Ext.Array.from(data.uses),
8296             items = [],
8297             i, ln, item;
8298
8299         for (i = 0, ln = uses.length; i < ln; i++) {
8300             item = uses[i];
8301
8302             if (typeof item === 'string') {
8303                 items.push(item);
8304             }
8305         }
8306
8307         Loader.addOptionalRequires(items);
8308     });
8309
8310     Manager.setDefaultPostprocessorPosition('uses', 'last');
8311
8312 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8313
8314 /**
8315  * @author Brian Moeskau <brian@sencha.com>
8316  * @docauthor Brian Moeskau <brian@sencha.com>
8317  *
8318  * A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8319  * errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8320  * uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8321  * the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8322  * with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8323  * execution will halt.
8324  *
8325  * Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8326  * handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8327  * although in a real application it's usually a better idea to override the handling function and perform
8328  * logging or some other method of reporting the errors in a way that is meaningful to the application.
8329  *
8330  * At its simplest you can simply raise an error as a simple string from within any code:
8331  *
8332  * Example usage:
8333  *
8334  *     Ext.Error.raise('Something bad happened!');
8335  *
8336  * If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8337  * displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8338  * additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
8339  * In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8340  * added to the error object and, if the console is available, logged to the console for inspection.
8341  *
8342  * Example usage:
8343  *
8344  *     Ext.define('Ext.Foo', {
8345  *         doSomething: function(option){
8346  *             if (someCondition === false) {
8347  *                 Ext.Error.raise({
8348  *                     msg: 'You cannot do that!',
8349  *                     option: option,   // whatever was passed into the method
8350  *                     'error code': 100 // other arbitrary info
8351  *                 });
8352  *             }
8353  *         }
8354  *     });
8355  *
8356  * If a console is available (that supports the `console.dir` function) you'll see console output like:
8357  *
8358  *     An error was raised with the following data:
8359  *     option:         Object { foo: "bar"}
8360  *         foo:        "bar"
8361  *     error code:     100
8362  *     msg:            "You cannot do that!"
8363  *     sourceClass:   "Ext.Foo"
8364  *     sourceMethod:  "doSomething"
8365  *
8366  *     uncaught exception: You cannot do that!
8367  *
8368  * As you can see, the error will report exactly where it was raised and will include as much information as the
8369  * raising code can usefully provide.
8370  *
8371  * If you want to handle all application errors globally you can simply override the static {@link #handle} method
8372  * and provide whatever handling logic you need. If the method returns true then the error is considered handled
8373  * and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8374  *
8375  * Example usage:
8376  *
8377  *     Ext.Error.handle = function(err) {
8378  *         if (err.someProperty == 'NotReallyAnError') {
8379  *             // maybe log something to the application here if applicable
8380  *             return true;
8381  *         }
8382  *         // any non-true return value (including none) will cause the error to be thrown
8383  *     }
8384  *
8385  */
8386 Ext.Error = Ext.extend(Error, {
8387     statics: {
8388         /**
8389          * @property {Boolean} ignore
8390          * Static flag that can be used to globally disable error reporting to the browser if set to true
8391          * (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8392          * and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8393          * be preferable to supply a custom error {@link #handle handling} function instead.
8394          *
8395          * Example usage:
8396          *
8397          *     Ext.Error.ignore = true;
8398          *
8399          * @static
8400          */
8401         ignore: false,
8402
8403         /**
8404          * @property {Boolean} notify
8405          * Static flag that can be used to globally control error notification to the user. Unlike
8406          * Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8407          * set to false to disable the alert notification (default is true for IE6 and IE7).
8408          *
8409          * Only the first error will generate an alert. Internally this flag is set to false when the
8410          * first error occurs prior to displaying the alert.
8411          *
8412          * This flag is not used in a release build.
8413          *
8414          * Example usage:
8415          *
8416          *     Ext.Error.notify = false;
8417          *
8418          * @static
8419          */
8420         //notify: Ext.isIE6 || Ext.isIE7,
8421
8422         /**
8423          * Raise an error that can include additional data and supports automatic console logging if available.
8424          * You can pass a string error message or an object with the `msg` attribute which will be used as the
8425          * error message. The object can contain any other name-value attributes (or objects) to be logged
8426          * along with the error.
8427          *
8428          * Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8429          * execution will halt.
8430          *
8431          * Example usage:
8432          *
8433          *     Ext.Error.raise('A simple string error message');
8434          *
8435          *     // or...
8436          *
8437          *     Ext.define('Ext.Foo', {
8438          *         doSomething: function(option){
8439          *             if (someCondition === false) {
8440          *                 Ext.Error.raise({
8441          *                     msg: 'You cannot do that!',
8442          *                     option: option,   // whatever was passed into the method
8443          *                     'error code': 100 // other arbitrary info
8444          *                 });
8445          *             }
8446          *         }
8447          *     });
8448          *
8449          * @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
8450          * used as the error message. Any other data included in the object will also be logged to the browser console,
8451          * if available.
8452          * @static
8453          */
8454         raise: function(err){
8455             err = err || {};
8456             if (Ext.isString(err)) {
8457                 err = { msg: err };
8458             }
8459
8460             var method = this.raise.caller;
8461
8462             if (method) {
8463                 if (method.$name) {
8464                     err.sourceMethod = method.$name;
8465                 }
8466                 if (method.$owner) {
8467                     err.sourceClass = method.$owner.$className;
8468                 }
8469             }
8470
8471             if (Ext.Error.handle(err) !== true) {
8472                 var msg = Ext.Error.prototype.toString.call(err);
8473
8474                 Ext.log({
8475                     msg: msg,
8476                     level: 'error',
8477                     dump: err,
8478                     stack: true
8479                 });
8480
8481                 throw new Ext.Error(err);
8482             }
8483         },
8484
8485         /**
8486          * Globally handle any Ext errors that may be raised, optionally providing custom logic to
8487          * handle different errors individually. Return true from the function to bypass throwing the
8488          * error to the browser, otherwise the error will be thrown and execution will halt.
8489          *
8490          * Example usage:
8491          *
8492          *     Ext.Error.handle = function(err) {
8493          *         if (err.someProperty == 'NotReallyAnError') {
8494          *             // maybe log something to the application here if applicable
8495          *             return true;
8496          *         }
8497          *         // any non-true return value (including none) will cause the error to be thrown
8498          *     }
8499          *
8500          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
8501          * raised with it, plus properties about the method and class from which the error originated (if raised from a
8502          * class that uses the Ext 4 class system).
8503          * @static
8504          */
8505         handle: function(){
8506             return Ext.Error.ignore;
8507         }
8508     },
8509
8510     // This is the standard property that is the name of the constructor.
8511     name: 'Ext.Error',
8512
8513     /**
8514      * Creates new Error object.
8515      * @param {String/Object} config The error message string, or an object containing the
8516      * attribute "msg" that will be used as the error message. Any other data included in
8517      * the object will be applied to the error instance and logged to the browser console, if available.
8518      */
8519     constructor: function(config){
8520         if (Ext.isString(config)) {
8521             config = { msg: config };
8522         }
8523
8524         var me = this;
8525
8526         Ext.apply(me, config);
8527
8528         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8529         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8530     },
8531
8532     /**
8533      * Provides a custom string representation of the error object. This is an override of the base JavaScript
8534      * `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8535      * be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8536      *
8537      * The default implementation will include the error message along with the raising class and method, if available,
8538      * but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8539      * a particular error instance, if you want to provide a custom description that will show up in the console.
8540      * @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
8541      * include the raising class and method names, if available.
8542      */
8543     toString: function(){
8544         var me = this,
8545             className = me.className ? me.className  : '',
8546             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8547             msg = me.msg || '(No description provided)';
8548
8549         return className + methodName + msg;
8550     }
8551 });
8552
8553 /*
8554  * This mechanism is used to notify the user of the first error encountered on the page. This
8555  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8556  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8557  * where exceptions are handled in a try/catch.
8558  */
8559
8560
8561
8562 /*
8563
8564 This file is part of Ext JS 4
8565
8566 Copyright (c) 2011 Sencha Inc
8567
8568 Contact:  http://www.sencha.com/contact
8569
8570 GNU General Public License Usage
8571 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
8572
8573 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
8574
8575 */
8576 /**
8577  * @class Ext.JSON
8578  * Modified version of Douglas Crockford's JSON.js that doesn't
8579  * mess with the Object prototype
8580  * http://www.json.org/js.html
8581  * @singleton
8582  */
8583 Ext.JSON = new(function() {
8584     var useHasOwn = !! {}.hasOwnProperty,
8585     isNative = function() {
8586         var useNative = null;
8587
8588         return function() {
8589             if (useNative === null) {
8590                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
8591             }
8592
8593             return useNative;
8594         };
8595     }(),
8596     pad = function(n) {
8597         return n < 10 ? "0" + n : n;
8598     },
8599     doDecode = function(json) {
8600         return eval("(" + json + ')');
8601     },
8602     doEncode = function(o) {
8603         if (!Ext.isDefined(o) || o === null) {
8604             return "null";
8605         } else if (Ext.isArray(o)) {
8606             return encodeArray(o);
8607         } else if (Ext.isDate(o)) {
8608             return Ext.JSON.encodeDate(o);
8609         } else if (Ext.isString(o)) {
8610             return encodeString(o);
8611         } else if (typeof o == "number") {
8612             //don't use isNumber here, since finite checks happen inside isNumber
8613             return isFinite(o) ? String(o) : "null";
8614         } else if (Ext.isBoolean(o)) {
8615             return String(o);
8616         } else if (Ext.isObject(o)) {
8617             return encodeObject(o);
8618         } else if (typeof o === "function") {
8619             return "null";
8620         }
8621         return 'undefined';
8622     },
8623     m = {
8624         "\b": '\\b',
8625         "\t": '\\t',
8626         "\n": '\\n',
8627         "\f": '\\f',
8628         "\r": '\\r',
8629         '"': '\\"',
8630         "\\": '\\\\',
8631         '\x0b': '\\u000b' //ie doesn't handle \v
8632     },
8633     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8634     encodeString = function(s) {
8635         return '"' + s.replace(charToReplace, function(a) {
8636             var c = m[a];
8637             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8638         }) + '"';
8639     },
8640     encodeArray = function(o) {
8641         var a = ["[", ""],
8642         // Note empty string in case there are no serializable members.
8643         len = o.length,
8644         i;
8645         for (i = 0; i < len; i += 1) {
8646             a.push(doEncode(o[i]), ',');
8647         }
8648         // Overwrite trailing comma (or empty string)
8649         a[a.length - 1] = ']';
8650         return a.join("");
8651     },
8652     encodeObject = function(o) {
8653         var a = ["{", ""],
8654         // Note empty string in case there are no serializable members.
8655         i;
8656         for (i in o) {
8657             if (!useHasOwn || o.hasOwnProperty(i)) {
8658                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8659             }
8660         }
8661         // Overwrite trailing comma (or empty string)
8662         a[a.length - 1] = '}';
8663         return a.join("");
8664     };
8665
8666     /**
8667      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8668      * <b>The returned value includes enclosing double quotation marks.</b></p>
8669      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8670      * <p>To override this:</p><pre><code>
8671 Ext.JSON.encodeDate = function(d) {
8672     return Ext.Date.format(d, '"Y-m-d"');
8673 };
8674      </code></pre>
8675      * @param {Date} d The Date to encode
8676      * @return {String} The string literal to use in a JSON string.
8677      */
8678     this.encodeDate = function(o) {
8679         return '"' + o.getFullYear() + "-"
8680         + pad(o.getMonth() + 1) + "-"
8681         + pad(o.getDate()) + "T"
8682         + pad(o.getHours()) + ":"
8683         + pad(o.getMinutes()) + ":"
8684         + pad(o.getSeconds()) + '"';
8685     };
8686
8687     /**
8688      * Encodes an Object, Array or other value
8689      * @param {Object} o The variable to encode
8690      * @return {String} The JSON string
8691      */
8692     this.encode = function() {
8693         var ec;
8694         return function(o) {
8695             if (!ec) {
8696                 // setup encoding function on first access
8697                 ec = isNative() ? JSON.stringify : doEncode;
8698             }
8699             return ec(o);
8700         };
8701     }();
8702
8703
8704     /**
8705      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
8706      * @param {String} json The JSON string
8707      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8708      * @return {Object} The resulting object
8709      */
8710     this.decode = function() {
8711         var dc;
8712         return function(json, safe) {
8713             if (!dc) {
8714                 // setup decoding function on first access
8715                 dc = isNative() ? JSON.parse : doDecode;
8716             }
8717             try {
8718                 return dc(json);
8719             } catch (e) {
8720                 if (safe === true) {
8721                     return null;
8722                 }
8723                 Ext.Error.raise({
8724                     sourceClass: "Ext.JSON",
8725                     sourceMethod: "decode",
8726                     msg: "You're trying to decode an invalid JSON String: " + json
8727                 });
8728             }
8729         };
8730     }();
8731
8732 })();
8733 /**
8734  * Shorthand for {@link Ext.JSON#encode}
8735  * @member Ext
8736  * @method encode
8737  * @alias Ext.JSON#encode
8738  */
8739 Ext.encode = Ext.JSON.encode;
8740 /**
8741  * Shorthand for {@link Ext.JSON#decode}
8742  * @member Ext
8743  * @method decode
8744  * @alias Ext.JSON#decode
8745  */
8746 Ext.decode = Ext.JSON.decode;
8747
8748
8749 /**
8750  * @class Ext
8751
8752  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
8753  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
8754  as direct properties of the Ext namespace.
8755
8756  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
8757  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
8758
8759  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
8760  This ensures all scripts have been loaded, preventing dependency issues. For example
8761
8762      Ext.onReady(function(){
8763          new Ext.Component({
8764              renderTo: document.body,
8765              html: 'DOM ready!'
8766          });
8767      });
8768
8769 For more information about how to use the Ext classes, see
8770
8771 - <a href="http://www.sencha.com/learn/">The Learning Center</a>
8772 - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
8773 - <a href="http://www.sencha.com/forum/">The forums</a>
8774
8775  * @singleton
8776  * @markdown
8777  */
8778 Ext.apply(Ext, {
8779     userAgent: navigator.userAgent.toLowerCase(),
8780     cache: {},
8781     idSeed: 1000,
8782     windowId: 'ext-window',
8783     documentId: 'ext-document',
8784
8785     /**
8786      * True when the document is fully initialized and ready for action
8787      * @type Boolean
8788      */
8789     isReady: false,
8790
8791     /**
8792      * True to automatically uncache orphaned Ext.Elements periodically
8793      * @type Boolean
8794      */
8795     enableGarbageCollector: true,
8796
8797     /**
8798      * True to automatically purge event listeners during garbageCollection.
8799      * @type Boolean
8800      */
8801     enableListenerCollection: true,
8802
8803     /**
8804      * Generates unique ids. If the element already has an id, it is unchanged
8805      * @param {HTMLElement/Ext.Element} el (optional) The element to generate an id for
8806      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
8807      * @return {String} The generated Id.
8808      */
8809     id: function(el, prefix) {
8810         var me = this,
8811             sandboxPrefix = '';
8812         el = Ext.getDom(el, true) || {};
8813         if (el === document) {
8814             el.id = me.documentId;
8815         }
8816         else if (el === window) {
8817             el.id = me.windowId;
8818         }
8819         if (!el.id) {
8820             if (me.isSandboxed) {
8821                 if (!me.uniqueGlobalNamespace) {
8822                     me.getUniqueGlobalNamespace();
8823                 }
8824                 sandboxPrefix = me.uniqueGlobalNamespace + '-';
8825             }
8826             el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed);
8827         }
8828         return el.id;
8829     },
8830
8831     /**
8832      * Returns the current document body as an {@link Ext.Element}.
8833      * @return Ext.Element The document body
8834      */
8835     getBody: function() {
8836         return Ext.get(document.body || false);
8837     },
8838
8839     /**
8840      * Returns the current document head as an {@link Ext.Element}.
8841      * @return Ext.Element The document head
8842      * @method
8843      */
8844     getHead: function() {
8845         var head;
8846
8847         return function() {
8848             if (head == undefined) {
8849                 head = Ext.get(document.getElementsByTagName("head")[0]);
8850             }
8851
8852             return head;
8853         };
8854     }(),
8855
8856     /**
8857      * Returns the current HTML document object as an {@link Ext.Element}.
8858      * @return Ext.Element The document
8859      */
8860     getDoc: function() {
8861         return Ext.get(document);
8862     },
8863
8864     /**
8865      * This is shorthand reference to {@link Ext.ComponentManager#get}.
8866      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
8867      * @param {String} id The component {@link Ext.Component#id id}
8868      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
8869      * Class was found.
8870     */
8871     getCmp: function(id) {
8872         return Ext.ComponentManager.get(id);
8873     },
8874
8875     /**
8876      * Returns the current orientation of the mobile device
8877      * @return {String} Either 'portrait' or 'landscape'
8878      */
8879     getOrientation: function() {
8880         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
8881     },
8882
8883     /**
8884      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
8885      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
8886      * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of
8887      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
8888      * passed into this function in a single call as separate arguments.
8889      * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} arg1
8890      * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
8891      */
8892     destroy: function() {
8893         var ln = arguments.length,
8894         i, arg;
8895
8896         for (i = 0; i < ln; i++) {
8897             arg = arguments[i];
8898             if (arg) {
8899                 if (Ext.isArray(arg)) {
8900                     this.destroy.apply(this, arg);
8901                 }
8902                 else if (Ext.isFunction(arg.destroy)) {
8903                     arg.destroy();
8904                 }
8905                 else if (arg.dom) {
8906                     arg.remove();
8907                 }
8908             }
8909         }
8910     },
8911
8912     /**
8913      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
8914      *
8915      * For example, these lines are equivalent:
8916      *
8917      *     Ext.callback(myFunc, this, [arg1, arg2]);
8918      *     Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]);
8919      *
8920      * @param {Function} callback The callback to execute
8921      * @param {Object} scope (optional) The scope to execute in
8922      * @param {Array} args (optional) The arguments to pass to the function
8923      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
8924      */
8925     callback: function(callback, scope, args, delay){
8926         if(Ext.isFunction(callback)){
8927             args = args || [];
8928             scope = scope || window;
8929             if (delay) {
8930                 Ext.defer(callback, delay, scope, args);
8931             } else {
8932                 callback.apply(scope, args);
8933             }
8934         }
8935     },
8936
8937     /**
8938      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
8939      * @param {String} value The string to encode
8940      * @return {String} The encoded text
8941      */
8942     htmlEncode : function(value) {
8943         return Ext.String.htmlEncode(value);
8944     },
8945
8946     /**
8947      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
8948      * @param {String} value The string to decode
8949      * @return {String} The decoded text
8950      */
8951     htmlDecode : function(value) {
8952          return Ext.String.htmlDecode(value);
8953     },
8954
8955     /**
8956      * Appends content to the query string of a URL, handling logic for whether to place
8957      * a question mark or ampersand.
8958      * @param {String} url The URL to append to.
8959      * @param {String} s The content to append to the URL.
8960      * @return (String) The resulting URL
8961      */
8962     urlAppend : function(url, s) {
8963         if (!Ext.isEmpty(s)) {
8964             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
8965         }
8966         return url;
8967     }
8968 });
8969
8970
8971 Ext.ns = Ext.namespace;
8972
8973 // for old browsers
8974 window.undefined = window.undefined;
8975
8976 /**
8977  * @class Ext
8978  * Ext core utilities and functions.
8979  * @singleton
8980  */
8981 (function(){
8982 /*
8983 FF 3.6      - Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17
8984 FF 4.0.1    - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
8985 FF 5.0      - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0
8986
8987 IE6         - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)
8988 IE7         - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;)
8989 IE8         - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
8990 IE9         - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
8991
8992 Chrome 11   - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24
8993
8994 Safari 5    - Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1
8995
8996 Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11
8997 */
8998     var check = function(regex){
8999             return regex.test(Ext.userAgent);
9000         },
9001         isStrict = document.compatMode == "CSS1Compat",
9002         version = function (is, regex) {
9003             var m;
9004             return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0;
9005         },
9006         docMode = document.documentMode,
9007         isOpera = check(/opera/),
9008         isOpera10_5 = isOpera && check(/version\/10\.5/),
9009         isChrome = check(/\bchrome\b/),
9010         isWebKit = check(/webkit/),
9011         isSafari = !isChrome && check(/safari/),
9012         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
9013         isSafari3 = isSafari && check(/version\/3/),
9014         isSafari4 = isSafari && check(/version\/4/),
9015         isSafari5 = isSafari && check(/version\/5/),
9016         isIE = !isOpera && check(/msie/),
9017         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
9018         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
9019         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
9020         isIE6 = isIE && check(/msie 6/),
9021         isGecko = !isWebKit && check(/gecko/),
9022         isGecko3 = isGecko && check(/rv:1\.9/),
9023         isGecko4 = isGecko && check(/rv:2\.0/),
9024         isGecko5 = isGecko && check(/rv:5\./),
9025         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
9026         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
9027         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
9028         isWindows = check(/windows|win32/),
9029         isMac = check(/macintosh|mac os x/),
9030         isLinux = check(/linux/),
9031         scrollbarSize = null,
9032         chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/),
9033         firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/),
9034         ieVersion = version(isIE, /msie (\d+\.\d+)/),
9035         operaVersion = version(isOpera, /version\/(\d+\.\d+)/),
9036         safariVersion = version(isSafari, /version\/(\d+\.\d+)/),
9037         webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/),
9038         isSecure = /^https/i.test(window.location.protocol);
9039
9040     // remove css image flicker
9041     try {
9042         document.execCommand("BackgroundImageCache", false, true);
9043     } catch(e) {}
9044
9045
9046     Ext.setVersion('extjs', '4.0.7');
9047     Ext.apply(Ext, {
9048         /**
9049          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
9050          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
9051          * @type String
9052          */
9053         SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
9054
9055         /**
9056          * True if the {@link Ext.fx.Anim} Class is available
9057          * @type Boolean
9058          * @property enableFx
9059          */
9060
9061         /**
9062          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
9063          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
9064          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
9065          * @type Boolean
9066          */
9067         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
9068
9069         /**
9070          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
9071          * Currently not optimized for performance.
9072          * @type Boolean
9073          */
9074         enableNestedListenerRemoval : false,
9075
9076         /**
9077          * Indicates whether to use native browser parsing for JSON methods.
9078          * This option is ignored if the browser does not support native JSON methods.
9079          * <b>Note: Native JSON methods will not work with objects that have functions.
9080          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
9081          * @type Boolean
9082          */
9083         USE_NATIVE_JSON : false,
9084
9085         /**
9086          * Return the dom node for the passed String (id), dom node, or Ext.Element.
9087          * Optional 'strict' flag is needed for IE since it can return 'name' and
9088          * 'id' elements by using getElementById.
9089          * Here are some examples:
9090          * <pre><code>
9091 // gets dom node based on id
9092 var elDom = Ext.getDom('elId');
9093 // gets dom node based on the dom node
9094 var elDom1 = Ext.getDom(elDom);
9095
9096 // If we don&#39;t know if we are working with an
9097 // Ext.Element or a dom node use Ext.getDom
9098 function(el){
9099     var dom = Ext.getDom(el);
9100     // do something with the dom node
9101 }
9102          * </code></pre>
9103          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
9104          * when this method is called to be successful.
9105          * @param {String/HTMLElement/Ext.Element} el
9106          * @return HTMLElement
9107          */
9108         getDom : function(el, strict) {
9109             if (!el || !document) {
9110                 return null;
9111             }
9112             if (el.dom) {
9113                 return el.dom;
9114             } else {
9115                 if (typeof el == 'string') {
9116                     var e = document.getElementById(el);
9117                     // IE returns elements with the 'name' and 'id' attribute.
9118                     // we do a strict check to return the element with only the id attribute
9119                     if (e && isIE && strict) {
9120                         if (el == e.getAttribute('id')) {
9121                             return e;
9122                         } else {
9123                             return null;
9124                         }
9125                     }
9126                     return e;
9127                 } else {
9128                     return el;
9129                 }
9130             }
9131         },
9132
9133         /**
9134          * Removes a DOM node from the document.
9135          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
9136          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
9137          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
9138          * will be ignored if passed in.</p>
9139          * @param {HTMLElement} node The node to remove
9140          * @method
9141          */
9142         removeNode : isIE6 || isIE7 ? function() {
9143             var d;
9144             return function(n){
9145                 if(n && n.tagName != 'BODY'){
9146                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9147                     d = d || document.createElement('div');
9148                     d.appendChild(n);
9149                     d.innerHTML = '';
9150                     delete Ext.cache[n.id];
9151                 }
9152             };
9153         }() : function(n) {
9154             if (n && n.parentNode && n.tagName != 'BODY') {
9155                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9156                 n.parentNode.removeChild(n);
9157                 delete Ext.cache[n.id];
9158             }
9159         },
9160
9161         isStrict: isStrict,
9162
9163         isIEQuirks: isIE && !isStrict,
9164
9165         /**
9166          * True if the detected browser is Opera.
9167          * @type Boolean
9168          */
9169         isOpera : isOpera,
9170
9171         /**
9172          * True if the detected browser is Opera 10.5x.
9173          * @type Boolean
9174          */
9175         isOpera10_5 : isOpera10_5,
9176
9177         /**
9178          * True if the detected browser uses WebKit.
9179          * @type Boolean
9180          */
9181         isWebKit : isWebKit,
9182
9183         /**
9184          * True if the detected browser is Chrome.
9185          * @type Boolean
9186          */
9187         isChrome : isChrome,
9188
9189         /**
9190          * True if the detected browser is Safari.
9191          * @type Boolean
9192          */
9193         isSafari : isSafari,
9194
9195         /**
9196          * True if the detected browser is Safari 3.x.
9197          * @type Boolean
9198          */
9199         isSafari3 : isSafari3,
9200
9201         /**
9202          * True if the detected browser is Safari 4.x.
9203          * @type Boolean
9204          */
9205         isSafari4 : isSafari4,
9206
9207         /**
9208          * True if the detected browser is Safari 5.x.
9209          * @type Boolean
9210          */
9211         isSafari5 : isSafari5,
9212
9213         /**
9214          * True if the detected browser is Safari 2.x.
9215          * @type Boolean
9216          */
9217         isSafari2 : isSafari2,
9218
9219         /**
9220          * True if the detected browser is Internet Explorer.
9221          * @type Boolean
9222          */
9223         isIE : isIE,
9224
9225         /**
9226          * True if the detected browser is Internet Explorer 6.x.
9227          * @type Boolean
9228          */
9229         isIE6 : isIE6,
9230
9231         /**
9232          * True if the detected browser is Internet Explorer 7.x.
9233          * @type Boolean
9234          */
9235         isIE7 : isIE7,
9236
9237         /**
9238          * True if the detected browser is Internet Explorer 8.x.
9239          * @type Boolean
9240          */
9241         isIE8 : isIE8,
9242
9243         /**
9244          * True if the detected browser is Internet Explorer 9.x.
9245          * @type Boolean
9246          */
9247         isIE9 : isIE9,
9248
9249         /**
9250          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
9251          * @type Boolean
9252          */
9253         isGecko : isGecko,
9254
9255         /**
9256          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
9257          * @type Boolean
9258          */
9259         isGecko3 : isGecko3,
9260
9261         /**
9262          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
9263          * @type Boolean
9264          */
9265         isGecko4 : isGecko4,
9266
9267         /**
9268          * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
9269          * @type Boolean
9270          */
9271         isGecko5 : isGecko5,
9272
9273         /**
9274          * True if the detected browser uses FireFox 3.0
9275          * @type Boolean
9276          */
9277         isFF3_0 : isFF3_0,
9278
9279         /**
9280          * True if the detected browser uses FireFox 3.5
9281          * @type Boolean
9282          */
9283         isFF3_5 : isFF3_5,
9284
9285         /**
9286          * True if the detected browser uses FireFox 3.6
9287          * @type Boolean
9288          */
9289         isFF3_6 : isFF3_6,
9290
9291         /**
9292          * True if the detected browser uses FireFox 4
9293          * @type Boolean
9294          */
9295         isFF4 : 4 <= firefoxVersion && firefoxVersion < 5,
9296
9297         /**
9298          * True if the detected browser uses FireFox 5
9299          * @type Boolean
9300          */
9301         isFF5 : 5 <= firefoxVersion && firefoxVersion < 6,
9302
9303         /**
9304          * True if the detected platform is Linux.
9305          * @type Boolean
9306          */
9307         isLinux : isLinux,
9308
9309         /**
9310          * True if the detected platform is Windows.
9311          * @type Boolean
9312          */
9313         isWindows : isWindows,
9314
9315         /**
9316          * True if the detected platform is Mac OS.
9317          * @type Boolean
9318          */
9319         isMac : isMac,
9320
9321         /**
9322          * The current version of Chrome (0 if the browser is not Chrome).
9323          * @type Number
9324          */
9325         chromeVersion: chromeVersion,
9326
9327         /**
9328          * The current version of Firefox (0 if the browser is not Firefox).
9329          * @type Number
9330          */
9331         firefoxVersion: firefoxVersion,
9332
9333         /**
9334          * The current version of IE (0 if the browser is not IE). This does not account
9335          * for the documentMode of the current page, which is factored into {@link #isIE7},
9336          * {@link #isIE8} and {@link #isIE9}. Thus this is not always true:
9337          *
9338          *      Ext.isIE8 == (Ext.ieVersion == 8)
9339          *
9340          * @type Number
9341          * @markdown
9342          */
9343         ieVersion: ieVersion,
9344
9345         /**
9346          * The current version of Opera (0 if the browser is not Opera).
9347          * @type Number
9348          */
9349         operaVersion: operaVersion,
9350
9351         /**
9352          * The current version of Safari (0 if the browser is not Safari).
9353          * @type Number
9354          */
9355         safariVersion: safariVersion,
9356
9357         /**
9358          * The current version of WebKit (0 if the browser does not use WebKit).
9359          * @type Number
9360          */
9361         webKitVersion: webKitVersion,
9362
9363         /**
9364          * True if the page is running over SSL
9365          * @type Boolean
9366          */
9367         isSecure: isSecure,
9368
9369         /**
9370          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
9371          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
9372          * For other browsers it uses an inline data URL.
9373          * @type String
9374          */
9375         BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
9376
9377         /**
9378          * <p>Utility method for returning a default value if the passed value is empty.</p>
9379          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
9380          * <li>null</li>
9381          * <li>undefined</li>
9382          * <li>an empty array</li>
9383          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
9384          * </ul></div>
9385          * @param {Object} value The value to test
9386          * @param {Object} defaultValue The value to return if the original value is empty
9387          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
9388          * @return {Object} value, if non-empty, else defaultValue
9389          * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead
9390          */
9391         value : function(v, defaultValue, allowBlank){
9392             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
9393         },
9394
9395         /**
9396          * Escapes the passed string for use in a regular expression
9397          * @param {String} str
9398          * @return {String}
9399          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
9400          */
9401         escapeRe : function(s) {
9402             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
9403         },
9404
9405         /**
9406          * Applies event listeners to elements by selectors when the document is ready.
9407          * The event name is specified with an <tt>&#64;</tt> suffix.
9408          * <pre><code>
9409 Ext.addBehaviors({
9410     // add a listener for click on all anchors in element with id foo
9411     '#foo a&#64;click' : function(e, t){
9412         // do something
9413     },
9414
9415     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
9416     '#foo a, #bar span.some-class&#64;mouseover' : function(){
9417         // do something
9418     }
9419 });
9420          * </code></pre>
9421          * @param {Object} obj The list of behaviors to apply
9422          */
9423         addBehaviors : function(o){
9424             if(!Ext.isReady){
9425                 Ext.onReady(function(){
9426                     Ext.addBehaviors(o);
9427                 });
9428             } else {
9429                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
9430                     parts,
9431                     b,
9432                     s;
9433                 for (b in o) {
9434                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
9435                         s = parts[0];
9436                         if(!cache[s]){
9437                             cache[s] = Ext.select(s);
9438                         }
9439                         cache[s].on(parts[1], o[b]);
9440                     }
9441                 }
9442                 cache = null;
9443             }
9444         },
9445
9446         /**
9447          * Returns the size of the browser scrollbars. This can differ depending on
9448          * operating system settings, such as the theme or font size.
9449          * @param {Boolean} force (optional) true to force a recalculation of the value.
9450          * @return {Object} An object containing the width of a vertical scrollbar and the
9451          * height of a horizontal scrollbar.
9452          */
9453         getScrollbarSize: function (force) {
9454             if(!Ext.isReady){
9455                 return 0;
9456             }
9457
9458             if(force === true || scrollbarSize === null){
9459                 // BrowserBug: IE9
9460                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
9461                 // inaccurately reported. For IE9 only, we render on screen before removing.
9462                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets',
9463                     // Append our div, do our calculation and then remove it
9464                     div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
9465                     child = div.child('div', true),
9466                     w1 = child.offsetWidth;
9467
9468                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
9469
9470                 var w2 = child.offsetWidth, width = w1 - w2;
9471                 div.remove();
9472
9473                 // We assume width == height for now. TODO: is this always true?
9474                 scrollbarSize = { width: width, height: width };
9475             }
9476
9477             return scrollbarSize;
9478         },
9479
9480         /**
9481          * Utility method for getting the width of the browser's vertical scrollbar. This
9482          * can differ depending on operating system settings, such as the theme or font size.
9483          *
9484          * This method is deprected in favor of {@link #getScrollbarSize}.
9485          *
9486          * @param {Boolean} force (optional) true to force a recalculation of the value.
9487          * @return {Number} The width of a vertical scrollbar.
9488          * @deprecated
9489          */
9490         getScrollBarWidth: function(force){
9491             var size = Ext.getScrollbarSize(force);
9492             return size.width + 2; // legacy fudge factor
9493         },
9494
9495         /**
9496          * Copies a set of named properties fom the source object to the destination object.
9497          *
9498          * Example:
9499          *
9500          *     ImageComponent = Ext.extend(Ext.Component, {
9501          *         initComponent: function() {
9502          *             this.autoEl = { tag: 'img' };
9503          *             MyComponent.superclass.initComponent.apply(this, arguments);
9504          *             this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
9505          *         }
9506          *     });
9507          *
9508          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
9509          *
9510          * @param {Object} dest The destination object.
9511          * @param {Object} source The source object.
9512          * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
9513          * of property names to copy.
9514          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
9515          * @return {Object} The modified object.
9516          */
9517         copyTo : function(dest, source, names, usePrototypeKeys){
9518             if(typeof names == 'string'){
9519                 names = names.split(/[,;\s]/);
9520             }
9521             Ext.each(names, function(name){
9522                 if(usePrototypeKeys || source.hasOwnProperty(name)){
9523                     dest[name] = source[name];
9524                 }
9525             }, this);
9526             return dest;
9527         },
9528
9529         /**
9530          * Attempts to destroy and then remove a set of named properties of the passed object.
9531          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
9532          * @param {String...} args One or more names of the properties to destroy and remove from the object.
9533          */
9534         destroyMembers : function(o){
9535             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
9536                 Ext.destroy(o[a[i]]);
9537                 delete o[a[i]];
9538             }
9539         },
9540
9541         /**
9542          * Logs a message. If a console is present it will be used. On Opera, the method
9543          * "opera.postError" is called. In other cases, the message is logged to an array
9544          * "Ext.log.out". An attached debugger can watch this array and view the log. The
9545          * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
9546          * The `Ext.log.out` array can also be written to a popup window by entering the
9547          * following in the URL bar (a "bookmarklet"):
9548          *
9549          *    javascript:void(Ext.log.show());
9550          *
9551          * If additional parameters are passed, they are joined and appended to the message.
9552          * A technique for tracing entry and exit of a function is this:
9553          *
9554          *      function foo () {
9555          *          Ext.log({ indent: 1 }, '>> foo');
9556          *
9557          *          // log statements in here or methods called from here will be indented
9558          *          // by one step
9559          *
9560          *          Ext.log({ outdent: 1 }, '<< foo');
9561          *      }
9562          *
9563          * This method does nothing in a release build.
9564          *
9565          * @param {String/Object} message The message to log or an options object with any
9566          * of the following properties:
9567          *
9568          *  - `msg`: The message to log (required).
9569          *  - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
9570          *  - `dump`: An object to dump to the log as part of the message.
9571          *  - `stack`: True to include a stack trace in the log.
9572          *  - `indent`: Cause subsequent log statements to be indented one step.
9573          *  - `outdent`: Cause this and following statements to be one step less indented.
9574          * @markdown
9575          */
9576         log :
9577             Ext.emptyFn,
9578
9579         /**
9580          * Partitions the set into two sets: a true set and a false set.
9581          * Example:
9582          * Example2:
9583          * <pre><code>
9584 // Example 1:
9585 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
9586
9587 // Example 2:
9588 Ext.partition(
9589     Ext.query("p"),
9590     function(val){
9591         return val.className == "class1"
9592     }
9593 );
9594 // true are those paragraph elements with a className of "class1",
9595 // false set are those that do not have that className.
9596          * </code></pre>
9597          * @param {Array/NodeList} arr The array to partition
9598          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
9599          * itself must be able to be evaluated for its truthfulness.
9600          * @return {Array} [array of truish values, array of falsy values]
9601          * @deprecated 4.0.0 Will be removed in the next major version
9602          */
9603         partition : function(arr, truth){
9604             var ret = [[],[]];
9605             Ext.each(arr, function(v, i, a) {
9606                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
9607             });
9608             return ret;
9609         },
9610
9611         /**
9612          * Invokes a method on each item in an Array.
9613          * <pre><code>
9614 // Example:
9615 Ext.invoke(Ext.query("p"), "getAttribute", "id");
9616 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
9617          * </code></pre>
9618          * @param {Array/NodeList} arr The Array of items to invoke the method on.
9619          * @param {String} methodName The method name to invoke.
9620          * @param {Object...} args Arguments to send into the method invocation.
9621          * @return {Array} The results of invoking the method on each item in the array.
9622          * @deprecated 4.0.0 Will be removed in the next major version
9623          */
9624         invoke : function(arr, methodName){
9625             var ret = [],
9626                 args = Array.prototype.slice.call(arguments, 2);
9627             Ext.each(arr, function(v,i) {
9628                 if (v && typeof v[methodName] == 'function') {
9629                     ret.push(v[methodName].apply(v, args));
9630                 } else {
9631                     ret.push(undefined);
9632                 }
9633             });
9634             return ret;
9635         },
9636
9637         /**
9638          * <p>Zips N sets together.</p>
9639          * <pre><code>
9640 // Example 1:
9641 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
9642 // Example 2:
9643 Ext.zip(
9644     [ "+", "-", "+"],
9645     [  12,  10,  22],
9646     [  43,  15,  96],
9647     function(a, b, c){
9648         return "$" + a + "" + b + "." + c
9649     }
9650 ); // ["$+12.43", "$-10.15", "$+22.96"]
9651          * </code></pre>
9652          * @param {Array/NodeList...} arr This argument may be repeated. Array(s) to contribute values.
9653          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
9654          * @return {Array} The zipped set.
9655          * @deprecated 4.0.0 Will be removed in the next major version
9656          */
9657         zip : function(){
9658             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
9659                 arrs = parts[0],
9660                 fn = parts[1][0],
9661                 len = Ext.max(Ext.pluck(arrs, "length")),
9662                 ret = [];
9663
9664             for (var i = 0; i < len; i++) {
9665                 ret[i] = [];
9666                 if(fn){
9667                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
9668                 }else{
9669                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
9670                         ret[i].push( arrs[j][i] );
9671                     }
9672                 }
9673             }
9674             return ret;
9675         },
9676
9677         /**
9678          * Turns an array into a sentence, joined by a specified connector - e.g.:
9679          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
9680          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
9681          * @param {String[]} items The array to create a sentence from
9682          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
9683          * @return {String} The sentence string
9684          * @deprecated 4.0.0 Will be removed in the next major version
9685          */
9686         toSentence: function(items, connector) {
9687             var length = items.length;
9688
9689             if (length <= 1) {
9690                 return items[0];
9691             } else {
9692                 var head = items.slice(0, length - 1),
9693                     tail = items[length - 1];
9694
9695                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
9696             }
9697         },
9698
9699         /**
9700          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
9701          * you may want to set this to true.
9702          * @type Boolean
9703          */
9704         useShims: isIE6
9705     });
9706 })();
9707
9708 /**
9709  * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
9710  *
9711  * See Ext.app.Application for details.
9712  *
9713  * @param {Object} config
9714  */
9715 Ext.application = function(config) {
9716     Ext.require('Ext.app.Application');
9717
9718     Ext.onReady(function() {
9719         Ext.create('Ext.app.Application', config);
9720     });
9721 };
9722
9723 /**
9724  * @class Ext.util.Format
9725
9726 This class is a centralized place for formatting functions. It includes
9727 functions to format various different types of data, such as text, dates and numeric values.
9728
9729 __Localization__
9730 This class contains several options for localization. These can be set once the library has loaded,
9731 all calls to the functions from that point will use the locale settings that were specified.
9732 Options include:
9733 - thousandSeparator
9734 - decimalSeparator
9735 - currenyPrecision
9736 - currencySign
9737 - currencyAtEnd
9738 This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
9739
9740 __Using with renderers__
9741 There are two helper functions that return a new function that can be used in conjunction with
9742 grid renderers:
9743
9744     columns: [{
9745         dataIndex: 'date',
9746         renderer: Ext.util.Format.dateRenderer('Y-m-d')
9747     }, {
9748         dataIndex: 'time',
9749         renderer: Ext.util.Format.numberRenderer('0.000')
9750     }]
9751
9752 Functions that only take a single argument can also be passed directly:
9753     columns: [{
9754         dataIndex: 'cost',
9755         renderer: Ext.util.Format.usMoney
9756     }, {
9757         dataIndex: 'productCode',
9758         renderer: Ext.util.Format.uppercase
9759     }]
9760
9761 __Using with XTemplates__
9762 XTemplates can also directly use Ext.util.Format functions:
9763
9764     new Ext.XTemplate([
9765         'Date: {startDate:date("Y-m-d")}',
9766         'Cost: {cost:usMoney}'
9767     ]);
9768
9769  * @markdown
9770  * @singleton
9771  */
9772 (function() {
9773     Ext.ns('Ext.util');
9774
9775     Ext.util.Format = {};
9776     var UtilFormat     = Ext.util.Format,
9777         stripTagsRE    = /<\/?[^>]+>/gi,
9778         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
9779         nl2brRe        = /\r?\n/g,
9780
9781         // A RegExp to remove from a number format string, all characters except digits and '.'
9782         formatCleanRe  = /[^\d\.]/g,
9783
9784         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
9785         // Created on first use. The local decimal separator character must be initialized for this to be created.
9786         I18NFormatCleanRe;
9787
9788     Ext.apply(UtilFormat, {
9789         /**
9790          * @property {String} thousandSeparator
9791          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
9792          * <p>This may be overridden in a locale file.</p>
9793          */
9794         thousandSeparator: ',',
9795
9796         /**
9797          * @property {String} decimalSeparator
9798          * <p>The character that the {@link #number} function uses as a decimal point.</p>
9799          * <p>This may be overridden in a locale file.</p>
9800          */
9801         decimalSeparator: '.',
9802
9803         /**
9804          * @property {Number} currencyPrecision
9805          * <p>The number of decimal places that the {@link #currency} function displays.</p>
9806          * <p>This may be overridden in a locale file.</p>
9807          */
9808         currencyPrecision: 2,
9809
9810         /**
9811          * @property {String} currencySign
9812          * <p>The currency sign that the {@link #currency} function displays.</p>
9813          * <p>This may be overridden in a locale file.</p>
9814          */
9815         currencySign: '$',
9816
9817         /**
9818          * @property {Boolean} currencyAtEnd
9819          * <p>This may be set to <code>true</code> to make the {@link #currency} function
9820          * append the currency sign to the formatted value.</p>
9821          * <p>This may be overridden in a locale file.</p>
9822          */
9823         currencyAtEnd: false,
9824
9825         /**
9826          * Checks a reference and converts it to empty string if it is undefined
9827          * @param {Object} value Reference to check
9828          * @return {Object} Empty string if converted, otherwise the original value
9829          */
9830         undef : function(value) {
9831             return value !== undefined ? value : "";
9832         },
9833
9834         /**
9835          * Checks a reference and converts it to the default value if it's empty
9836          * @param {Object} value Reference to check
9837          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
9838          * @return {String}
9839          */
9840         defaultValue : function(value, defaultValue) {
9841             return value !== undefined && value !== '' ? value : defaultValue;
9842         },
9843
9844         /**
9845          * Returns a substring from within an original string
9846          * @param {String} value The original text
9847          * @param {Number} start The start index of the substring
9848          * @param {Number} length The length of the substring
9849          * @return {String} The substring
9850          */
9851         substr : function(value, start, length) {
9852             return String(value).substr(start, length);
9853         },
9854
9855         /**
9856          * Converts a string to all lower case letters
9857          * @param {String} value The text to convert
9858          * @return {String} The converted text
9859          */
9860         lowercase : function(value) {
9861             return String(value).toLowerCase();
9862         },
9863
9864         /**
9865          * Converts a string to all upper case letters
9866          * @param {String} value The text to convert
9867          * @return {String} The converted text
9868          */
9869         uppercase : function(value) {
9870             return String(value).toUpperCase();
9871         },
9872
9873         /**
9874          * Format a number as US currency
9875          * @param {Number/String} value The numeric value to format
9876          * @return {String} The formatted currency string
9877          */
9878         usMoney : function(v) {
9879             return UtilFormat.currency(v, '$', 2);
9880         },
9881
9882         /**
9883          * Format a number as a currency
9884          * @param {Number/String} value The numeric value to format
9885          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
9886          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
9887          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
9888          * @return {String} The formatted currency string
9889          */
9890         currency: function(v, currencySign, decimals, end) {
9891             var negativeSign = '',
9892                 format = ",0",
9893                 i = 0;
9894             v = v - 0;
9895             if (v < 0) {
9896                 v = -v;
9897                 negativeSign = '-';
9898             }
9899             decimals = decimals || UtilFormat.currencyPrecision;
9900             format += format + (decimals > 0 ? '.' : '');
9901             for (; i < decimals; i++) {
9902                 format += '0';
9903             }
9904             v = UtilFormat.number(v, format);
9905             if ((end || UtilFormat.currencyAtEnd) === true) {
9906                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
9907             } else {
9908                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
9909             }
9910         },
9911
9912         /**
9913          * Formats the passed date using the specified format pattern.
9914          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
9915          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
9916          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9917          * @return {String} The formatted date string.
9918          */
9919         date: function(v, format) {
9920             if (!v) {
9921                 return "";
9922             }
9923             if (!Ext.isDate(v)) {
9924                 v = new Date(Date.parse(v));
9925             }
9926             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
9927         },
9928
9929         /**
9930          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
9931          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9932          * @return {Function} The date formatting function
9933          */
9934         dateRenderer : function(format) {
9935             return function(v) {
9936                 return UtilFormat.date(v, format);
9937             };
9938         },
9939
9940         /**
9941          * Strips all HTML tags
9942          * @param {Object} value The text from which to strip tags
9943          * @return {String} The stripped text
9944          */
9945         stripTags : function(v) {
9946             return !v ? v : String(v).replace(stripTagsRE, "");
9947         },
9948
9949         /**
9950          * Strips all script tags
9951          * @param {Object} value The text from which to strip script tags
9952          * @return {String} The stripped text
9953          */
9954         stripScripts : function(v) {
9955             return !v ? v : String(v).replace(stripScriptsRe, "");
9956         },
9957
9958         /**
9959          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
9960          * @param {Number/String} size The numeric value to format
9961          * @return {String} The formatted file size
9962          */
9963         fileSize : function(size) {
9964             if (size < 1024) {
9965                 return size + " bytes";
9966             } else if (size < 1048576) {
9967                 return (Math.round(((size*10) / 1024))/10) + " KB";
9968             } else {
9969                 return (Math.round(((size*10) / 1048576))/10) + " MB";
9970             }
9971         },
9972
9973         /**
9974          * It does simple math for use in a template, for example:<pre><code>
9975          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
9976          * </code></pre>
9977          * @return {Function} A function that operates on the passed value.
9978          * @method
9979          */
9980         math : function(){
9981             var fns = {};
9982
9983             return function(v, a){
9984                 if (!fns[a]) {
9985                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
9986                 }
9987                 return fns[a](v);
9988             };
9989         }(),
9990
9991         /**
9992          * Rounds the passed number to the required decimal precision.
9993          * @param {Number/String} value The numeric value to round.
9994          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
9995          * @return {Number} The rounded value.
9996          */
9997         round : function(value, precision) {
9998             var result = Number(value);
9999             if (typeof precision == 'number') {
10000                 precision = Math.pow(10, precision);
10001                 result = Math.round(value * precision) / precision;
10002             }
10003             return result;
10004         },
10005
10006         /**
10007          * <p>Formats the passed number according to the passed format string.</p>
10008          * <p>The number of digits after the decimal separator character specifies the number of
10009          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
10010          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
10011          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
10012          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
10013          * <p><b>New to Ext JS 4</b></p>
10014          * <p>Locale-specific characters are always used in the formatted output when inserting
10015          * thousand and decimal separators.</p>
10016          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
10017          * thousand separator, and "." as the decimal separator)</p>
10018          * <p>To allow specification of format strings according to local conventions for separator characters, add
10019          * the string <code>/i</code> to the end of the format string.</p>
10020          * <div style="margin-left:40px">examples (123456.789):
10021          * <div style="margin-left:10px">
10022          * 0 - (123456) show only digits, no precision<br>
10023          * 0.00 - (123456.78) show only digits, 2 precision<br>
10024          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
10025          * 0,000 - (123,456) show comma and digits, no precision<br>
10026          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
10027          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
10028          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
10029          * For example: 0.000,00/i
10030          * </div></div>
10031          * @param {Number} v The number to format.
10032          * @param {String} format The way you would like to format this text.
10033          * @return {String} The formatted number.
10034          */
10035         number: function(v, formatString) {
10036             if (!formatString) {
10037                 return v;
10038             }
10039             v = Ext.Number.from(v, NaN);
10040             if (isNaN(v)) {
10041                 return '';
10042             }
10043             var comma = UtilFormat.thousandSeparator,
10044                 dec   = UtilFormat.decimalSeparator,
10045                 i18n  = false,
10046                 neg   = v < 0,
10047                 hasComma,
10048                 psplit;
10049
10050             v = Math.abs(v);
10051
10052             // The "/i" suffix allows caller to use a locale-specific formatting string.
10053             // Clean the format string by removing all but numerals and the decimal separator.
10054             // Then split the format string into pre and post decimal segments according to *what* the
10055             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
10056             if (formatString.substr(formatString.length - 2) == '/i') {
10057                 if (!I18NFormatCleanRe) {
10058                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
10059                 }
10060                 formatString = formatString.substr(0, formatString.length - 2);
10061                 i18n   = true;
10062                 hasComma = formatString.indexOf(comma) != -1;
10063                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
10064             } else {
10065                 hasComma = formatString.indexOf(',') != -1;
10066                 psplit = formatString.replace(formatCleanRe, '').split('.');
10067             }
10068
10069             if (1 < psplit.length) {
10070                 v = v.toFixed(psplit[1].length);
10071             } else if(2 < psplit.length) {
10072             } else {
10073                 v = v.toFixed(0);
10074             }
10075
10076             var fnum = v.toString();
10077
10078             psplit = fnum.split('.');
10079
10080             if (hasComma) {
10081                 var cnum = psplit[0],
10082                     parr = [],
10083                     j    = cnum.length,
10084                     m    = Math.floor(j / 3),
10085                     n    = cnum.length % 3 || 3,
10086                     i;
10087
10088                 for (i = 0; i < j; i += n) {
10089                     if (i !== 0) {
10090                         n = 3;
10091                     }
10092
10093                     parr[parr.length] = cnum.substr(i, n);
10094                     m -= 1;
10095                 }
10096                 fnum = parr.join(comma);
10097                 if (psplit[1]) {
10098                     fnum += dec + psplit[1];
10099                 }
10100             } else {
10101                 if (psplit[1]) {
10102                     fnum = psplit[0] + dec + psplit[1];
10103                 }
10104             }
10105
10106             if (neg) {
10107                 /*
10108                  * Edge case. If we have a very small negative number it will get rounded to 0,
10109                  * however the initial check at the top will still report as negative. Replace
10110                  * everything but 1-9 and check if the string is empty to determine a 0 value.
10111                  */
10112                 neg = fnum.replace(/[^1-9]/g, '') !== '';
10113             }
10114
10115             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
10116         },
10117
10118         /**
10119          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
10120          * @param {String} format Any valid number format string for {@link #number}
10121          * @return {Function} The number formatting function
10122          */
10123         numberRenderer : function(format) {
10124             return function(v) {
10125                 return UtilFormat.number(v, format);
10126             };
10127         },
10128
10129         /**
10130          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
10131          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
10132          * if the value is 0 or greater than 1.
10133          * @param {Number} value The value to compare against
10134          * @param {String} singular The singular form of the word
10135          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
10136          */
10137         plural : function(v, s, p) {
10138             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
10139         },
10140
10141         /**
10142          * Converts newline characters to the HTML tag &lt;br/>
10143          * @param {String} The string value to format.
10144          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
10145          */
10146         nl2br : function(v) {
10147             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
10148         },
10149
10150         /**
10151          * Alias for {@link Ext.String#capitalize}.
10152          * @method
10153          * @alias Ext.String#capitalize
10154          */
10155         capitalize: Ext.String.capitalize,
10156
10157         /**
10158          * Alias for {@link Ext.String#ellipsis}.
10159          * @method
10160          * @alias Ext.String#ellipsis
10161          */
10162         ellipsis: Ext.String.ellipsis,
10163
10164         /**
10165          * Alias for {@link Ext.String#format}.
10166          * @method
10167          * @alias Ext.String#format
10168          */
10169         format: Ext.String.format,
10170
10171         /**
10172          * Alias for {@link Ext.String#htmlDecode}.
10173          * @method
10174          * @alias Ext.String#htmlDecode
10175          */
10176         htmlDecode: Ext.String.htmlDecode,
10177
10178         /**
10179          * Alias for {@link Ext.String#htmlEncode}.
10180          * @method
10181          * @alias Ext.String#htmlEncode
10182          */
10183         htmlEncode: Ext.String.htmlEncode,
10184
10185         /**
10186          * Alias for {@link Ext.String#leftPad}.
10187          * @method
10188          * @alias Ext.String#leftPad
10189          */
10190         leftPad: Ext.String.leftPad,
10191
10192         /**
10193          * Alias for {@link Ext.String#trim}.
10194          * @method
10195          * @alias Ext.String#trim
10196          */
10197         trim : Ext.String.trim,
10198
10199         /**
10200          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
10201          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
10202          * @param {Number/String} v The encoded margins
10203          * @return {Object} An object with margin sizes for top, right, bottom and left
10204          */
10205         parseBox : function(box) {
10206             if (Ext.isNumber(box)) {
10207                 box = box.toString();
10208             }
10209             var parts  = box.split(' '),
10210                 ln = parts.length;
10211
10212             if (ln == 1) {
10213                 parts[1] = parts[2] = parts[3] = parts[0];
10214             }
10215             else if (ln == 2) {
10216                 parts[2] = parts[0];
10217                 parts[3] = parts[1];
10218             }
10219             else if (ln == 3) {
10220                 parts[3] = parts[1];
10221             }
10222
10223             return {
10224                 top   :parseInt(parts[0], 10) || 0,
10225                 right :parseInt(parts[1], 10) || 0,
10226                 bottom:parseInt(parts[2], 10) || 0,
10227                 left  :parseInt(parts[3], 10) || 0
10228             };
10229         },
10230
10231         /**
10232          * Escapes the passed string for use in a regular expression
10233          * @param {String} str
10234          * @return {String}
10235          */
10236         escapeRegex : function(s) {
10237             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
10238         }
10239     });
10240 })();
10241
10242 /**
10243  * @class Ext.util.TaskRunner
10244  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
10245  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
10246  * if needed, you can create separate instances of TaskRunner.  Any number of
10247  * separate tasks can be started at any time and will run independently of each
10248  * other. Example usage:
10249  * <pre><code>
10250 // Start a simple clock task that updates a div once per second
10251 var updateClock = function(){
10252     Ext.fly('clock').update(new Date().format('g:i:s A'));
10253
10254 var task = {
10255     run: updateClock,
10256     interval: 1000 //1 second
10257 }
10258 var runner = new Ext.util.TaskRunner();
10259 runner.start(task);
10260
10261 // equivalent using TaskManager
10262 Ext.TaskManager.start({
10263     run: updateClock,
10264     interval: 1000
10265 });
10266
10267  * </code></pre>
10268  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10269  * Also see {@link Ext.util.DelayedTask}. 
10270  * 
10271  * @constructor
10272  * @param {Number} [interval=10] The minimum precision in milliseconds supported by this TaskRunner instance
10273  */
10274 Ext.ns('Ext.util');
10275
10276 Ext.util.TaskRunner = function(interval) {
10277     interval = interval || 10;
10278     var tasks = [],
10279     removeQueue = [],
10280     id = 0,
10281     running = false,
10282
10283     // private
10284     stopThread = function() {
10285         running = false;
10286         clearInterval(id);
10287         id = 0;
10288     },
10289
10290     // private
10291     startThread = function() {
10292         if (!running) {
10293             running = true;
10294             id = setInterval(runTasks, interval);
10295         }
10296     },
10297
10298     // private
10299     removeTask = function(t) {
10300         removeQueue.push(t);
10301         if (t.onStop) {
10302             t.onStop.apply(t.scope || t);
10303         }
10304     },
10305
10306     // private
10307     runTasks = function() {
10308         var rqLen = removeQueue.length,
10309             now = new Date().getTime(),
10310             i;
10311
10312         if (rqLen > 0) {
10313             for (i = 0; i < rqLen; i++) {
10314                 Ext.Array.remove(tasks, removeQueue[i]);
10315             }
10316             removeQueue = [];
10317             if (tasks.length < 1) {
10318                 stopThread();
10319                 return;
10320             }
10321         }
10322         i = 0;
10323         var t,
10324             itime,
10325             rt,
10326             len = tasks.length;
10327         for (; i < len; ++i) {
10328             t = tasks[i];
10329             itime = now - t.taskRunTime;
10330             if (t.interval <= itime) {
10331                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
10332                 t.taskRunTime = now;
10333                 if (rt === false || t.taskRunCount === t.repeat) {
10334                     removeTask(t);
10335                     return;
10336                 }
10337             }
10338             if (t.duration && t.duration <= (now - t.taskStartTime)) {
10339                 removeTask(t);
10340             }
10341         }
10342     };
10343
10344     /**
10345      * Starts a new task.
10346      * @method start
10347      * @param {Object} task <p>A config object that supports the following properties:<ul>
10348      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
10349      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
10350      * current invocation count if not.</p>
10351      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
10352      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
10353      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
10354      * should be invoked.</div></li>
10355      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
10356      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
10357      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
10358      * <code>run</code> function. Defaults to the task config object.</div></li>
10359      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
10360      * the task before stopping automatically (defaults to indefinite).</div></li>
10361      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
10362      * stopping automatically (defaults to indefinite).</div></li>
10363      * </ul></p>
10364      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
10365      * that calculations based on the repeat count can be performed.</p>
10366      * @return {Object} The task
10367      */
10368     this.start = function(task) {
10369         tasks.push(task);
10370         task.taskStartTime = new Date().getTime();
10371         task.taskRunTime = 0;
10372         task.taskRunCount = 0;
10373         startThread();
10374         return task;
10375     };
10376
10377     /**
10378      * Stops an existing running task.
10379      * @method stop
10380      * @param {Object} task The task to stop
10381      * @return {Object} The task
10382      */
10383     this.stop = function(task) {
10384         removeTask(task);
10385         return task;
10386     };
10387
10388     /**
10389      * Stops all tasks that are currently running.
10390      * @method stopAll
10391      */
10392     this.stopAll = function() {
10393         stopThread();
10394         for (var i = 0, len = tasks.length; i < len; i++) {
10395             if (tasks[i].onStop) {
10396                 tasks[i].onStop();
10397             }
10398         }
10399         tasks = [];
10400         removeQueue = [];
10401     };
10402 };
10403
10404 /**
10405  * @class Ext.TaskManager
10406  * @extends Ext.util.TaskRunner
10407  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
10408  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
10409  * <pre><code>
10410 // Start a simple clock task that updates a div once per second
10411 var task = {
10412     run: function(){
10413         Ext.fly('clock').update(new Date().format('g:i:s A'));
10414     },
10415     interval: 1000 //1 second
10416 }
10417 Ext.TaskManager.start(task);
10418 </code></pre>
10419  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10420  * @singleton
10421  */
10422 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
10423 /**
10424  * @class Ext.is
10425  * 
10426  * Determines information about the current platform the application is running on.
10427  * 
10428  * @singleton
10429  */
10430 Ext.is = {
10431     init : function(navigator) {
10432         var platforms = this.platforms,
10433             ln = platforms.length,
10434             i, platform;
10435
10436         navigator = navigator || window.navigator;
10437
10438         for (i = 0; i < ln; i++) {
10439             platform = platforms[i];
10440             this[platform.identity] = platform.regex.test(navigator[platform.property]);
10441         }
10442
10443         /**
10444          * @property Desktop True if the browser is running on a desktop machine
10445          * @type {Boolean}
10446          */
10447         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
10448         /**
10449          * @property Tablet True if the browser is running on a tablet (iPad)
10450          */
10451         this.Tablet = this.iPad;
10452         /**
10453          * @property Phone True if the browser is running on a phone.
10454          * @type {Boolean}
10455          */
10456         this.Phone = !this.Desktop && !this.Tablet;
10457         /**
10458          * @property iOS True if the browser is running on iOS
10459          * @type {Boolean}
10460          */
10461         this.iOS = this.iPhone || this.iPad || this.iPod;
10462         
10463         /**
10464          * @property Standalone Detects when application has been saved to homescreen.
10465          * @type {Boolean}
10466          */
10467         this.Standalone = !!window.navigator.standalone;
10468     },
10469     
10470     /**
10471      * @property iPhone True when the browser is running on a iPhone
10472      * @type {Boolean}
10473      */
10474     platforms: [{
10475         property: 'platform',
10476         regex: /iPhone/i,
10477         identity: 'iPhone'
10478     },
10479     
10480     /**
10481      * @property iPod True when the browser is running on a iPod
10482      * @type {Boolean}
10483      */
10484     {
10485         property: 'platform',
10486         regex: /iPod/i,
10487         identity: 'iPod'
10488     },
10489     
10490     /**
10491      * @property iPad True when the browser is running on a iPad
10492      * @type {Boolean}
10493      */
10494     {
10495         property: 'userAgent',
10496         regex: /iPad/i,
10497         identity: 'iPad'
10498     },
10499     
10500     /**
10501      * @property Blackberry True when the browser is running on a Blackberry
10502      * @type {Boolean}
10503      */
10504     {
10505         property: 'userAgent',
10506         regex: /Blackberry/i,
10507         identity: 'Blackberry'
10508     },
10509     
10510     /**
10511      * @property Android True when the browser is running on an Android device
10512      * @type {Boolean}
10513      */
10514     {
10515         property: 'userAgent',
10516         regex: /Android/i,
10517         identity: 'Android'
10518     },
10519     
10520     /**
10521      * @property Mac True when the browser is running on a Mac
10522      * @type {Boolean}
10523      */
10524     {
10525         property: 'platform',
10526         regex: /Mac/i,
10527         identity: 'Mac'
10528     },
10529     
10530     /**
10531      * @property Windows True when the browser is running on Windows
10532      * @type {Boolean}
10533      */
10534     {
10535         property: 'platform',
10536         regex: /Win/i,
10537         identity: 'Windows'
10538     },
10539     
10540     /**
10541      * @property Linux True when the browser is running on Linux
10542      * @type {Boolean}
10543      */
10544     {
10545         property: 'platform',
10546         regex: /Linux/i,
10547         identity: 'Linux'
10548     }]
10549 };
10550
10551 Ext.is.init();
10552
10553 /**
10554  * @class Ext.supports
10555  *
10556  * Determines information about features are supported in the current environment
10557  * 
10558  * @singleton
10559  */
10560 Ext.supports = {
10561     init : function() {
10562         var doc = document,
10563             div = doc.createElement('div'),
10564             tests = this.tests,
10565             ln = tests.length,
10566             i, test;
10567
10568         div.innerHTML = [
10569             '<div style="height:30px;width:50px;">',
10570                 '<div style="height:20px;width:20px;"></div>',
10571             '</div>',
10572             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
10573                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
10574             '</div>',
10575             '<div style="float:left; background-color:transparent;"></div>'
10576         ].join('');
10577
10578         doc.body.appendChild(div);
10579
10580         for (i = 0; i < ln; i++) {
10581             test = tests[i];
10582             this[test.identity] = test.fn.call(this, doc, div);
10583         }
10584
10585         doc.body.removeChild(div);
10586     },
10587
10588     /**
10589      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
10590      * @type {Boolean}
10591      */
10592     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
10593
10594     /**
10595      * @property ClassList True if document environment supports the HTML5 classList API.
10596      * @type {Boolean}
10597      */
10598     ClassList: !!document.documentElement.classList,
10599
10600     /**
10601      * @property OrientationChange True if the device supports orientation change
10602      * @type {Boolean}
10603      */
10604     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
10605     
10606     /**
10607      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
10608      * @type {Boolean}
10609      */
10610     DeviceMotion: ('ondevicemotion' in window),
10611     
10612     /**
10613      * @property Touch True if the device supports touch
10614      * @type {Boolean}
10615      */
10616     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
10617     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
10618     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
10619
10620     tests: [
10621         /**
10622          * @property Transitions True if the device supports CSS3 Transitions
10623          * @type {Boolean}
10624          */
10625         {
10626             identity: 'Transitions',
10627             fn: function(doc, div) {
10628                 var prefix = [
10629                         'webkit',
10630                         'Moz',
10631                         'o',
10632                         'ms',
10633                         'khtml'
10634                     ],
10635                     TE = 'TransitionEnd',
10636                     transitionEndName = [
10637                         prefix[0] + TE,
10638                         'transitionend', //Moz bucks the prefixing convention
10639                         prefix[2] + TE,
10640                         prefix[3] + TE,
10641                         prefix[4] + TE
10642                     ],
10643                     ln = prefix.length,
10644                     i = 0,
10645                     out = false;
10646                 div = Ext.get(div);
10647                 for (; i < ln; i++) {
10648                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
10649                         Ext.supports.CSS3Prefix = prefix[i];
10650                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
10651                         out = true;
10652                         break;
10653                     }
10654                 }
10655                 return out;
10656             }
10657         },
10658         
10659         /**
10660          * @property RightMargin True if the device supports right margin.
10661          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
10662          * @type {Boolean}
10663          */
10664         {
10665             identity: 'RightMargin',
10666             fn: function(doc, div) {
10667                 var view = doc.defaultView;
10668                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
10669             }
10670         },
10671
10672         /**
10673          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
10674          * selection when their display style is changed. Essentially, if a text input
10675          * has focus and its display style is changed, the I-beam disappears.
10676          * 
10677          * This bug is encountered due to the work around in place for the {@link #RightMargin}
10678          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
10679          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
10680          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
10681          */
10682         {
10683             identity: 'DisplayChangeInputSelectionBug',
10684             fn: function() {
10685                 var webKitVersion = Ext.webKitVersion;
10686                 // WebKit but older than Safari 5 or Chrome 6:
10687                 return 0 < webKitVersion && webKitVersion < 533;
10688             }
10689         },
10690
10691         /**
10692          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
10693          * selection when their display style is changed. Essentially, if a text area has
10694          * focus and its display style is changed, the I-beam disappears.
10695          *
10696          * This bug is encountered due to the work around in place for the {@link #RightMargin}
10697          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
10698          * be fixed in Chrome 11.
10699          */
10700         {
10701             identity: 'DisplayChangeTextAreaSelectionBug',
10702             fn: function() {
10703                 var webKitVersion = Ext.webKitVersion;
10704
10705                 /*
10706                 Has bug w/textarea:
10707
10708                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
10709                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
10710                             Safari/534.16
10711                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
10712                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
10713                             Safari/533.21.1
10714
10715                 No bug:
10716
10717                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
10718                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
10719                             Safari/534.24
10720                 */
10721                 return 0 < webKitVersion && webKitVersion < 534.24;
10722             }
10723         },
10724
10725         /**
10726          * @property TransparentColor True if the device supports transparent color
10727          * @type {Boolean}
10728          */
10729         {
10730             identity: 'TransparentColor',
10731             fn: function(doc, div, view) {
10732                 view = doc.defaultView;
10733                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
10734             }
10735         },
10736
10737         /**
10738          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
10739          * @type {Boolean}
10740          */
10741         {
10742             identity: 'ComputedStyle',
10743             fn: function(doc, div, view) {
10744                 view = doc.defaultView;
10745                 return view && view.getComputedStyle;
10746             }
10747         },
10748         
10749         /**
10750          * @property SVG True if the device supports SVG
10751          * @type {Boolean}
10752          */
10753         {
10754             identity: 'Svg',
10755             fn: function(doc) {
10756                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
10757             }
10758         },
10759     
10760         /**
10761          * @property Canvas True if the device supports Canvas
10762          * @type {Boolean}
10763          */
10764         {
10765             identity: 'Canvas',
10766             fn: function(doc) {
10767                 return !!doc.createElement('canvas').getContext;
10768             }
10769         },
10770         
10771         /**
10772          * @property VML True if the device supports VML
10773          * @type {Boolean}
10774          */
10775         {
10776             identity: 'Vml',
10777             fn: function(doc) {
10778                 var d = doc.createElement("div");
10779                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
10780                 return (d.childNodes.length == 2);
10781             }
10782         },
10783         
10784         /**
10785          * @property Float True if the device supports CSS float
10786          * @type {Boolean}
10787          */
10788         {
10789             identity: 'Float',
10790             fn: function(doc, div) {
10791                 return !!div.lastChild.style.cssFloat;
10792             }
10793         },
10794         
10795         /**
10796          * @property AudioTag True if the device supports the HTML5 audio tag
10797          * @type {Boolean}
10798          */
10799         {
10800             identity: 'AudioTag',
10801             fn: function(doc) {
10802                 return !!doc.createElement('audio').canPlayType;
10803             }
10804         },
10805         
10806         /**
10807          * @property History True if the device supports HTML5 history
10808          * @type {Boolean}
10809          */
10810         {
10811             identity: 'History',
10812             fn: function() {
10813                 return !!(window.history && history.pushState);
10814             }
10815         },
10816         
10817         /**
10818          * @property CSS3DTransform True if the device supports CSS3DTransform
10819          * @type {Boolean}
10820          */
10821         {
10822             identity: 'CSS3DTransform',
10823             fn: function() {
10824                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
10825             }
10826         },
10827
10828                 /**
10829          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
10830          * @type {Boolean}
10831          */
10832         {
10833             identity: 'CSS3LinearGradient',
10834             fn: function(doc, div) {
10835                 var property = 'background-image:',
10836                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
10837                     w3c      = 'linear-gradient(left top, black, white)',
10838                     moz      = '-moz-' + w3c,
10839                     options  = [property + webkit, property + w3c, property + moz];
10840                 
10841                 div.style.cssText = options.join(';');
10842                 
10843                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
10844             }
10845         },
10846         
10847         /**
10848          * @property CSS3BorderRadius True if the device supports CSS3 border radius
10849          * @type {Boolean}
10850          */
10851         {
10852             identity: 'CSS3BorderRadius',
10853             fn: function(doc, div) {
10854                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
10855                     pass = false,
10856                     i;
10857                 for (i = 0; i < domPrefixes.length; i++) {
10858                     if (document.body.style[domPrefixes[i]] !== undefined) {
10859                         return true;
10860                     }
10861                 }
10862                 return pass;
10863             }
10864         },
10865         
10866         /**
10867          * @property GeoLocation True if the device supports GeoLocation
10868          * @type {Boolean}
10869          */
10870         {
10871             identity: 'GeoLocation',
10872             fn: function() {
10873                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
10874             }
10875         },
10876         /**
10877          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
10878          * @type {Boolean}
10879          */
10880         {
10881             identity: 'MouseEnterLeave',
10882             fn: function(doc, div){
10883                 return ('onmouseenter' in div && 'onmouseleave' in div);
10884             }
10885         },
10886         /**
10887          * @property MouseWheel True if the browser supports the mousewheel event
10888          * @type {Boolean}
10889          */
10890         {
10891             identity: 'MouseWheel',
10892             fn: function(doc, div) {
10893                 return ('onmousewheel' in div);
10894             }
10895         },
10896         /**
10897          * @property Opacity True if the browser supports normal css opacity
10898          * @type {Boolean}
10899          */
10900         {
10901             identity: 'Opacity',
10902             fn: function(doc, div){
10903                 // Not a strict equal comparison in case opacity can be converted to a number.
10904                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
10905                     return false;
10906                 }
10907                 div.firstChild.style.cssText = 'opacity:0.73';
10908                 return div.firstChild.style.opacity == '0.73';
10909             }
10910         },
10911         /**
10912          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
10913          * @type {Boolean}
10914          */
10915         {
10916             identity: 'Placeholder',
10917             fn: function(doc) {
10918                 return 'placeholder' in doc.createElement('input');
10919             }
10920         },
10921         
10922         /**
10923          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
10924          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
10925          * @type {Boolean}
10926          */
10927         {
10928             identity: 'Direct2DBug',
10929             fn: function() {
10930                 return Ext.isString(document.body.style.msTransformOrigin);
10931             }
10932         },
10933         /**
10934          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
10935          * @type {Boolean}
10936          */
10937         {
10938             identity: 'BoundingClientRect',
10939             fn: function(doc, div) {
10940                 return Ext.isFunction(div.getBoundingClientRect);
10941             }
10942         },
10943         {
10944             identity: 'IncludePaddingInWidthCalculation',
10945             fn: function(doc, div){
10946                 var el = Ext.get(div.childNodes[1].firstChild);
10947                 return el.getWidth() == 210;
10948             }
10949         },
10950         {
10951             identity: 'IncludePaddingInHeightCalculation',
10952             fn: function(doc, div){
10953                 var el = Ext.get(div.childNodes[1].firstChild);
10954                 return el.getHeight() == 210;
10955             }
10956         },
10957         
10958         /**
10959          * @property ArraySort True if the Array sort native method isn't bugged.
10960          * @type {Boolean}
10961          */
10962         {
10963             identity: 'ArraySort',
10964             fn: function() {
10965                 var a = [1,2,3,4,5].sort(function(){ return 0; });
10966                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
10967             }
10968         },
10969         /**
10970          * @property Range True if browser support document.createRange native method.
10971          * @type {Boolean}
10972          */
10973         {
10974             identity: 'Range',
10975             fn: function() {
10976                 return !!document.createRange;
10977             }
10978         },
10979         /**
10980          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
10981          * @type {Boolean}
10982          */
10983         {
10984             identity: 'CreateContextualFragment',
10985             fn: function() {
10986                 var range = Ext.supports.Range ? document.createRange() : false;
10987                 
10988                 return range && !!range.createContextualFragment;
10989             }
10990         },
10991
10992         /**
10993          * @property WindowOnError True if browser supports window.onerror.
10994          * @type {Boolean}
10995          */
10996         {
10997             identity: 'WindowOnError',
10998             fn: function () {
10999                 // sadly, we cannot feature detect this...
11000                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
11001             }
11002         }
11003     ]
11004 };
11005
11006
11007
11008 /*
11009
11010 This file is part of Ext JS 4
11011
11012 Copyright (c) 2011 Sencha Inc
11013
11014 Contact:  http://www.sencha.com/contact
11015
11016 GNU General Public License Usage
11017 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11018
11019 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
11020
11021 */
11022 /**
11023  * @class Ext.DomHelper
11024  * @alternateClassName Ext.core.DomHelper
11025  *
11026  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
11027  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11028  * from your DOM building code.</p>
11029  *
11030  * <p><b><u>DomHelper element specification object</u></b></p>
11031  * <p>A specification object is used when creating elements. Attributes of this object
11032  * are assumed to be element attributes, except for 4 special attributes:
11033  * <div class="mdetail-params"><ul>
11034  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
11035  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
11036  * same kind of element definition objects to be created and appended. These can be nested
11037  * as deep as you want.</div></li>
11038  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
11039  * This will end up being either the "class" attribute on a HTML fragment or className
11040  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
11041  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
11042  * </ul></div></p>
11043  * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
11044  * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
11045  * contains special characters that would not normally be allowed in a double-quoted attribute value,
11046  * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
11047  * malformed HTML being created. This behavior may change in a future release.</p>
11048  *
11049  * <p><b><u>Insertion methods</u></b></p>
11050  * <p>Commonly used insertion methods:
11051  * <div class="mdetail-params"><ul>
11052  * <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
11053  * <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
11054  * <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
11055  * <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
11056  * <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
11057  * <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
11058  * </ul></div></p>
11059  *
11060  * <p><b><u>Example</u></b></p>
11061  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
11062  * element with id <tt>'my-div'</tt>:<br>
11063  <pre><code>
11064 var dh = Ext.DomHelper; // create shorthand alias
11065 // specification object
11066 var spec = {
11067     id: 'my-ul',
11068     tag: 'ul',
11069     cls: 'my-list',
11070     // append children after creating
11071     children: [     // may also specify 'cn' instead of 'children'
11072         {tag: 'li', id: 'item0', html: 'List Item 0'},
11073         {tag: 'li', id: 'item1', html: 'List Item 1'},
11074         {tag: 'li', id: 'item2', html: 'List Item 2'}
11075     ]
11076 };
11077 var list = dh.append(
11078     'my-div', // the context element 'my-div' can either be the id or the actual node
11079     spec      // the specification object
11080 );
11081  </code></pre></p>
11082  * <p>Element creation specification parameters in this class may also be passed as an Array of
11083  * specification objects. This can be used to insert multiple sibling nodes into an existing
11084  * container very efficiently. For example, to add more list items to the example above:<pre><code>
11085 dh.append('my-ul', [
11086     {tag: 'li', id: 'item3', html: 'List Item 3'},
11087     {tag: 'li', id: 'item4', html: 'List Item 4'}
11088 ]);
11089  * </code></pre></p>
11090  *
11091  * <p><b><u>Templating</u></b></p>
11092  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
11093  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
11094  * insert new elements. Revisiting the example above, we could utilize templating this time:
11095  * <pre><code>
11096 // create the node
11097 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
11098 // get template
11099 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
11100
11101 for(var i = 0; i < 5, i++){
11102     tpl.append(list, [i]); // use template to append to the actual node
11103 }
11104  * </code></pre></p>
11105  * <p>An example using a template:<pre><code>
11106 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
11107
11108 var tpl = new Ext.DomHelper.createTemplate(html);
11109 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
11110 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
11111  * </code></pre></p>
11112  *
11113  * <p>The same example using named parameters:<pre><code>
11114 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11115
11116 var tpl = new Ext.DomHelper.createTemplate(html);
11117 tpl.append('blog-roll', {
11118     id: 'link1',
11119     url: 'http://www.edspencer.net/',
11120     text: "Ed&#39;s Site"
11121 });
11122 tpl.append('blog-roll', {
11123     id: 'link2',
11124     url: 'http://www.dustindiaz.com/',
11125     text: "Dustin&#39;s Site"
11126 });
11127  * </code></pre></p>
11128  *
11129  * <p><b><u>Compiling Templates</u></b></p>
11130  * <p>Templates are applied using regular expressions. The performance is great, but if
11131  * you are adding a bunch of DOM elements using the same template, you can increase
11132  * performance even further by {@link Ext.Template#compile "compiling"} the template.
11133  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
11134  * broken up at the different variable points and a dynamic function is created and eval'ed.
11135  * The generated function performs string concatenation of these parts and the passed
11136  * variables instead of using regular expressions.
11137  * <pre><code>
11138 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11139
11140 var tpl = new Ext.DomHelper.createTemplate(html);
11141 tpl.compile();
11142
11143 //... use template like normal
11144  * </code></pre></p>
11145  *
11146  * <p><b><u>Performance Boost</u></b></p>
11147  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
11148  * of DOM can significantly boost performance.</p>
11149  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
11150  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
11151  * results in the creation of a text node. Usage:</p>
11152  * <pre><code>
11153 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
11154  * </code></pre>
11155  * @singleton
11156  */
11157 Ext.ns('Ext.core');
11158 Ext.core.DomHelper = Ext.DomHelper = function(){
11159     var tempTableEl = null,
11160         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
11161         tableRe = /^table|tbody|tr|td$/i,
11162         confRe = /tag|children|cn|html$/i,
11163         tableElRe = /td|tr|tbody/i,
11164         endRe = /end/i,
11165         pub,
11166         // kill repeat to save bytes
11167         afterbegin = 'afterbegin',
11168         afterend = 'afterend',
11169         beforebegin = 'beforebegin',
11170         beforeend = 'beforeend',
11171         ts = '<table>',
11172         te = '</table>',
11173         tbs = ts+'<tbody>',
11174         tbe = '</tbody>'+te,
11175         trs = tbs + '<tr>',
11176         tre = '</tr>'+tbe;
11177
11178     // private
11179     function doInsert(el, o, returnElement, pos, sibling, append){
11180         el = Ext.getDom(el);
11181         var newNode;
11182         if (pub.useDom) {
11183             newNode = createDom(o, null);
11184             if (append) {
11185                 el.appendChild(newNode);
11186             } else {
11187                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
11188             }
11189         } else {
11190             newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
11191         }
11192         return returnElement ? Ext.get(newNode, true) : newNode;
11193     }
11194
11195     function createDom(o, parentNode){
11196         var el,
11197             doc = document,
11198             useSet,
11199             attr,
11200             val,
11201             cn;
11202
11203         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
11204             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
11205             for (var i = 0, l = o.length; i < l; i++) {
11206                 createDom(o[i], el);
11207             }
11208         } else if (typeof o == 'string') {         // Allow a string as a child spec.
11209             el = doc.createTextNode(o);
11210         } else {
11211             el = doc.createElement( o.tag || 'div' );
11212             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
11213             for (attr in o) {
11214                 if(!confRe.test(attr)){
11215                     val = o[attr];
11216                     if(attr == 'cls'){
11217                         el.className = val;
11218                     }else{
11219                         if(useSet){
11220                             el.setAttribute(attr, val);
11221                         }else{
11222                             el[attr] = val;
11223                         }
11224                     }
11225                 }
11226             }
11227             Ext.DomHelper.applyStyles(el, o.style);
11228
11229             if ((cn = o.children || o.cn)) {
11230                 createDom(cn, el);
11231             } else if (o.html) {
11232                 el.innerHTML = o.html;
11233             }
11234         }
11235         if(parentNode){
11236            parentNode.appendChild(el);
11237         }
11238         return el;
11239     }
11240
11241     // build as innerHTML where available
11242     function createHtml(o){
11243         var b = '',
11244             attr,
11245             val,
11246             key,
11247             cn,
11248             i;
11249
11250         if(typeof o == "string"){
11251             b = o;
11252         } else if (Ext.isArray(o)) {
11253             for (i=0; i < o.length; i++) {
11254                 if(o[i]) {
11255                     b += createHtml(o[i]);
11256                 }
11257             }
11258         } else {
11259             b += '<' + (o.tag = o.tag || 'div');
11260             for (attr in o) {
11261                 val = o[attr];
11262                 if(!confRe.test(attr)){
11263                     if (typeof val == "object") {
11264                         b += ' ' + attr + '="';
11265                         for (key in val) {
11266                             b += key + ':' + val[key] + ';';
11267                         }
11268                         b += '"';
11269                     }else{
11270                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
11271                     }
11272                 }
11273             }
11274             // Now either just close the tag or try to add children and close the tag.
11275             if (emptyTags.test(o.tag)) {
11276                 b += '/>';
11277             } else {
11278                 b += '>';
11279                 if ((cn = o.children || o.cn)) {
11280                     b += createHtml(cn);
11281                 } else if(o.html){
11282                     b += o.html;
11283                 }
11284                 b += '</' + o.tag + '>';
11285             }
11286         }
11287         return b;
11288     }
11289
11290     function ieTable(depth, s, h, e){
11291         tempTableEl.innerHTML = [s, h, e].join('');
11292         var i = -1,
11293             el = tempTableEl,
11294             ns;
11295         while(++i < depth){
11296             el = el.firstChild;
11297         }
11298 //      If the result is multiple siblings, then encapsulate them into one fragment.
11299         ns = el.nextSibling;
11300         if (ns){
11301             var df = document.createDocumentFragment();
11302             while(el){
11303                 ns = el.nextSibling;
11304                 df.appendChild(el);
11305                 el = ns;
11306             }
11307             el = df;
11308         }
11309         return el;
11310     }
11311
11312     /**
11313      * @ignore
11314      * Nasty code for IE's broken table implementation
11315      */
11316     function insertIntoTable(tag, where, el, html) {
11317         var node,
11318             before;
11319
11320         tempTableEl = tempTableEl || document.createElement('div');
11321
11322         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
11323            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
11324             return null;
11325         }
11326         before = where == beforebegin ? el :
11327                  where == afterend ? el.nextSibling :
11328                  where == afterbegin ? el.firstChild : null;
11329
11330         if (where == beforebegin || where == afterend) {
11331             el = el.parentNode;
11332         }
11333
11334         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
11335             node = ieTable(4, trs, html, tre);
11336         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
11337                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
11338             node = ieTable(3, tbs, html, tbe);
11339         } else {
11340             node = ieTable(2, ts, html, te);
11341         }
11342         el.insertBefore(node, before);
11343         return node;
11344     }
11345
11346     /**
11347      * @ignore
11348      * Fix for IE9 createContextualFragment missing method
11349      */
11350     function createContextualFragment(html){
11351         var div = document.createElement("div"),
11352             fragment = document.createDocumentFragment(),
11353             i = 0,
11354             length, childNodes;
11355
11356         div.innerHTML = html;
11357         childNodes = div.childNodes;
11358         length = childNodes.length;
11359
11360         for (; i < length; i++) {
11361             fragment.appendChild(childNodes[i].cloneNode(true));
11362         }
11363
11364         return fragment;
11365     }
11366
11367     pub = {
11368         /**
11369          * Returns the markup for the passed Element(s) config.
11370          * @param {Object} o The DOM object spec (and children)
11371          * @return {String}
11372          */
11373         markup : function(o){
11374             return createHtml(o);
11375         },
11376
11377         /**
11378          * Applies a style specification to an element.
11379          * @param {String/HTMLElement} el The element to apply styles to
11380          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
11381          * a function which returns such a specification.
11382          */
11383         applyStyles : function(el, styles){
11384             if (styles) {
11385                 el = Ext.fly(el);
11386                 if (typeof styles == "function") {
11387                     styles = styles.call();
11388                 }
11389                 if (typeof styles == "string") {
11390                     styles = Ext.Element.parseStyles(styles);
11391                 }
11392                 if (typeof styles == "object") {
11393                     el.setStyle(styles);
11394                 }
11395             }
11396         },
11397
11398         /**
11399          * Inserts an HTML fragment into the DOM.
11400          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
11401          *
11402          * For example take the following HTML: `<div>Contents</div>`
11403          *
11404          * Using different `where` values inserts element to the following places:
11405          *
11406          * - beforeBegin: `<HERE><div>Contents</div>`
11407          * - afterBegin: `<div><HERE>Contents</div>`
11408          * - beforeEnd: `<div>Contents<HERE></div>`
11409          * - afterEnd: `<div>Contents</div><HERE>`
11410          *
11411          * @param {HTMLElement/TextNode} el The context element
11412          * @param {String} html The HTML fragment
11413          * @return {HTMLElement} The new node
11414          */
11415         insertHtml : function(where, el, html){
11416             var hash = {},
11417                 hashVal,
11418                 range,
11419                 rangeEl,
11420                 setStart,
11421                 frag,
11422                 rs;
11423
11424             where = where.toLowerCase();
11425             // add these here because they are used in both branches of the condition.
11426             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
11427             hash[afterend] = ['AfterEnd', 'nextSibling'];
11428
11429             // if IE and context element is an HTMLElement
11430             if (el.insertAdjacentHTML) {
11431                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
11432                     return rs;
11433                 }
11434
11435                 // add these two to the hash.
11436                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
11437                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
11438                 if ((hashVal = hash[where])) {
11439                     el.insertAdjacentHTML(hashVal[0], html);
11440                     return el[hashVal[1]];
11441                 }
11442             // if (not IE and context element is an HTMLElement) or TextNode
11443             } else {
11444                 // we cannot insert anything inside a textnode so...
11445                 if (Ext.isTextNode(el)) {
11446                     where = where === 'afterbegin' ? 'beforebegin' : where;
11447                     where = where === 'beforeend' ? 'afterend' : where;
11448                 }
11449                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
11450                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
11451                 if (hash[where]) {
11452                     if (range) {
11453                         range[setStart](el);
11454                         frag = range.createContextualFragment(html);
11455                     } else {
11456                         frag = createContextualFragment(html);
11457                     }
11458                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
11459                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
11460                 } else {
11461                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
11462                     if (el.firstChild) {
11463                         if (range) {
11464                             range[setStart](el[rangeEl]);
11465                             frag = range.createContextualFragment(html);
11466                         } else {
11467                             frag = createContextualFragment(html);
11468                         }
11469
11470                         if(where == afterbegin){
11471                             el.insertBefore(frag, el.firstChild);
11472                         }else{
11473                             el.appendChild(frag);
11474                         }
11475                     } else {
11476                         el.innerHTML = html;
11477                     }
11478                     return el[rangeEl];
11479                 }
11480             }
11481         },
11482
11483         /**
11484          * Creates new DOM element(s) and inserts them before el.
11485          * @param {String/HTMLElement/Ext.Element} el The context element
11486          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11487          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11488          * @return {HTMLElement/Ext.Element} The new node
11489          */
11490         insertBefore : function(el, o, returnElement){
11491             return doInsert(el, o, returnElement, beforebegin);
11492         },
11493
11494         /**
11495          * Creates new DOM element(s) and inserts them after el.
11496          * @param {String/HTMLElement/Ext.Element} el The context element
11497          * @param {Object} o The DOM object spec (and children)
11498          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11499          * @return {HTMLElement/Ext.Element} The new node
11500          */
11501         insertAfter : function(el, o, returnElement){
11502             return doInsert(el, o, returnElement, afterend, 'nextSibling');
11503         },
11504
11505         /**
11506          * Creates new DOM element(s) and inserts them as the first child of el.
11507          * @param {String/HTMLElement/Ext.Element} el The context element
11508          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11509          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11510          * @return {HTMLElement/Ext.Element} The new node
11511          */
11512         insertFirst : function(el, o, returnElement){
11513             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
11514         },
11515
11516         /**
11517          * Creates new DOM element(s) and appends them to el.
11518          * @param {String/HTMLElement/Ext.Element} el The context element
11519          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11520          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11521          * @return {HTMLElement/Ext.Element} The new node
11522          */
11523         append : function(el, o, returnElement){
11524             return doInsert(el, o, returnElement, beforeend, '', true);
11525         },
11526
11527         /**
11528          * Creates new DOM element(s) and overwrites the contents of el with them.
11529          * @param {String/HTMLElement/Ext.Element} el The context element
11530          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11531          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11532          * @return {HTMLElement/Ext.Element} The new node
11533          */
11534         overwrite : function(el, o, returnElement){
11535             el = Ext.getDom(el);
11536             el.innerHTML = createHtml(o);
11537             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
11538         },
11539
11540         createHtml : createHtml,
11541
11542         /**
11543          * Creates new DOM element(s) without inserting them to the document.
11544          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11545          * @return {HTMLElement} The new uninserted node
11546          * @method
11547          */
11548         createDom: createDom,
11549
11550         /** True to force the use of DOM instead of html fragments @type Boolean */
11551         useDom : false,
11552
11553         /**
11554          * Creates a new Ext.Template from the DOM object spec.
11555          * @param {Object} o The DOM object spec (and children)
11556          * @return {Ext.Template} The new template
11557          */
11558         createTemplate : function(o){
11559             var html = Ext.DomHelper.createHtml(o);
11560             return Ext.create('Ext.Template', html);
11561         }
11562     };
11563     return pub;
11564 }();
11565
11566 /*
11567  * This is code is also distributed under MIT license for use
11568  * with jQuery and prototype JavaScript libraries.
11569  */
11570 /**
11571  * @class Ext.DomQuery
11572 Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).
11573 <p>
11574 DomQuery supports most of the <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors">CSS3 selectors spec</a>, along with some custom selectors and basic XPath.</p>
11575
11576 <p>
11577 All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example "div.foo:nth-child(odd)[@foo=bar].bar:first" would be a perfectly valid selector. Node filters are processed in the order in which they appear, which allows you to optimize your queries for your document structure.
11578 </p>
11579 <h4>Element Selectors:</h4>
11580 <ul class="list">
11581     <li> <b>*</b> any element</li>
11582     <li> <b>E</b> an element with the tag E</li>
11583     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
11584     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
11585     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
11586     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
11587 </ul>
11588 <h4>Attribute Selectors:</h4>
11589 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
11590 <ul class="list">
11591     <li> <b>E[foo]</b> has an attribute "foo"</li>
11592     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
11593     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
11594     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
11595     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
11596     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
11597     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
11598 </ul>
11599 <h4>Pseudo Classes:</h4>
11600 <ul class="list">
11601     <li> <b>E:first-child</b> E is the first child of its parent</li>
11602     <li> <b>E:last-child</b> E is the last child of its parent</li>
11603     <li> <b>E:nth-child(<i>n</i>)</b> E is the <i>n</i>th child of its parent (1 based as per the spec)</li>
11604     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
11605     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
11606     <li> <b>E:only-child</b> E is the only child of its parent</li>
11607     <li> <b>E:checked</b> E is an element that is has a checked attribute that is true (e.g. a radio or checkbox) </li>
11608     <li> <b>E:first</b> the first E in the resultset</li>
11609     <li> <b>E:last</b> the last E in the resultset</li>
11610     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
11611     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
11612     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
11613     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
11614     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
11615     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
11616     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
11617     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
11618     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
11619     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
11620 </ul>
11621 <h4>CSS Value Selectors:</h4>
11622 <ul class="list">
11623     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
11624     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
11625     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
11626     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
11627     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
11628     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
11629 </ul>
11630  * @singleton
11631  */
11632 Ext.ns('Ext.core');
11633
11634 Ext.core.DomQuery = Ext.DomQuery = function(){
11635     var cache = {},
11636         simpleCache = {},
11637         valueCache = {},
11638         nonSpace = /\S/,
11639         trimRe = /^\s+|\s+$/g,
11640         tplRe = /\{(\d+)\}/g,
11641         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
11642         tagTokenRe = /^(#)?([\w-\*]+)/,
11643         nthRe = /(\d*)n\+?(\d*)/,
11644         nthRe2 = /\D/,
11645         startIdRe = /^\s*\#/,
11646         // This is for IE MSXML which does not support expandos.
11647     // IE runs the same speed using setAttribute, however FF slows way down
11648     // and Safari completely fails so they need to continue to use expandos.
11649     isIE = window.ActiveXObject ? true : false,
11650     key = 30803;
11651
11652     // this eval is stop the compressor from
11653     // renaming the variable to something shorter
11654     eval("var batch = 30803;");
11655
11656     // Retrieve the child node from a particular
11657     // parent at the specified index.
11658     function child(parent, index){
11659         var i = 0,
11660             n = parent.firstChild;
11661         while(n){
11662             if(n.nodeType == 1){
11663                if(++i == index){
11664                    return n;
11665                }
11666             }
11667             n = n.nextSibling;
11668         }
11669         return null;
11670     }
11671
11672     // retrieve the next element node
11673     function next(n){
11674         while((n = n.nextSibling) && n.nodeType != 1);
11675         return n;
11676     }
11677
11678     // retrieve the previous element node
11679     function prev(n){
11680         while((n = n.previousSibling) && n.nodeType != 1);
11681         return n;
11682     }
11683
11684     // Mark each child node with a nodeIndex skipping and
11685     // removing empty text nodes.
11686     function children(parent){
11687         var n = parent.firstChild,
11688         nodeIndex = -1,
11689         nextNode;
11690         while(n){
11691             nextNode = n.nextSibling;
11692             // clean worthless empty nodes.
11693             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
11694             parent.removeChild(n);
11695             }else{
11696             // add an expando nodeIndex
11697             n.nodeIndex = ++nodeIndex;
11698             }
11699             n = nextNode;
11700         }
11701         return this;
11702     }
11703
11704
11705     // nodeSet - array of nodes
11706     // cls - CSS Class
11707     function byClassName(nodeSet, cls){
11708         if(!cls){
11709             return nodeSet;
11710         }
11711         var result = [], ri = -1;
11712         for(var i = 0, ci; ci = nodeSet[i]; i++){
11713             if((' '+ci.className+' ').indexOf(cls) != -1){
11714                 result[++ri] = ci;
11715             }
11716         }
11717         return result;
11718     };
11719
11720     function attrValue(n, attr){
11721         // if its an array, use the first node.
11722         if(!n.tagName && typeof n.length != "undefined"){
11723             n = n[0];
11724         }
11725         if(!n){
11726             return null;
11727         }
11728
11729         if(attr == "for"){
11730             return n.htmlFor;
11731         }
11732         if(attr == "class" || attr == "className"){
11733             return n.className;
11734         }
11735         return n.getAttribute(attr) || n[attr];
11736
11737     };
11738
11739
11740     // ns - nodes
11741     // mode - false, /, >, +, ~
11742     // tagName - defaults to "*"
11743     function getNodes(ns, mode, tagName){
11744         var result = [], ri = -1, cs;
11745         if(!ns){
11746             return result;
11747         }
11748         tagName = tagName || "*";
11749         // convert to array
11750         if(typeof ns.getElementsByTagName != "undefined"){
11751             ns = [ns];
11752         }
11753
11754         // no mode specified, grab all elements by tagName
11755         // at any depth
11756         if(!mode){
11757             for(var i = 0, ni; ni = ns[i]; i++){
11758                 cs = ni.getElementsByTagName(tagName);
11759                 for(var j = 0, ci; ci = cs[j]; j++){
11760                     result[++ri] = ci;
11761                 }
11762             }
11763         // Direct Child mode (/ or >)
11764         // E > F or E/F all direct children elements of E that have the tag
11765         } else if(mode == "/" || mode == ">"){
11766             var utag = tagName.toUpperCase();
11767             for(var i = 0, ni, cn; ni = ns[i]; i++){
11768                 cn = ni.childNodes;
11769                 for(var j = 0, cj; cj = cn[j]; j++){
11770                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
11771                         result[++ri] = cj;
11772                     }
11773                 }
11774             }
11775         // Immediately Preceding mode (+)
11776         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
11777         }else if(mode == "+"){
11778             var utag = tagName.toUpperCase();
11779             for(var i = 0, n; n = ns[i]; i++){
11780                 while((n = n.nextSibling) && n.nodeType != 1);
11781                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
11782                     result[++ri] = n;
11783                 }
11784             }
11785         // Sibling mode (~)
11786         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
11787         }else if(mode == "~"){
11788             var utag = tagName.toUpperCase();
11789             for(var i = 0, n; n = ns[i]; i++){
11790                 while((n = n.nextSibling)){
11791                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
11792                         result[++ri] = n;
11793                     }
11794                 }
11795             }
11796         }
11797         return result;
11798     }
11799
11800     function concat(a, b){
11801         if(b.slice){
11802             return a.concat(b);
11803         }
11804         for(var i = 0, l = b.length; i < l; i++){
11805             a[a.length] = b[i];
11806         }
11807         return a;
11808     }
11809
11810     function byTag(cs, tagName){
11811         if(cs.tagName || cs == document){
11812             cs = [cs];
11813         }
11814         if(!tagName){
11815             return cs;
11816         }
11817         var result = [], ri = -1;
11818         tagName = tagName.toLowerCase();
11819         for(var i = 0, ci; ci = cs[i]; i++){
11820             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
11821                 result[++ri] = ci;
11822             }
11823         }
11824         return result;
11825     }
11826
11827     function byId(cs, id){
11828         if(cs.tagName || cs == document){
11829             cs = [cs];
11830         }
11831         if(!id){
11832             return cs;
11833         }
11834         var result = [], ri = -1;
11835         for(var i = 0, ci; ci = cs[i]; i++){
11836             if(ci && ci.id == id){
11837                 result[++ri] = ci;
11838                 return result;
11839             }
11840         }
11841         return result;
11842     }
11843
11844     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
11845     // custom can be "{"
11846     function byAttribute(cs, attr, value, op, custom){
11847         var result = [],
11848             ri = -1,
11849             useGetStyle = custom == "{",
11850             fn = Ext.DomQuery.operators[op],
11851             a,
11852             xml,
11853             hasXml;
11854
11855         for(var i = 0, ci; ci = cs[i]; i++){
11856             // skip non-element nodes.
11857             if(ci.nodeType != 1){
11858                 continue;
11859             }
11860             // only need to do this for the first node
11861             if(!hasXml){
11862                 xml = Ext.DomQuery.isXml(ci);
11863                 hasXml = true;
11864             }
11865
11866             // we only need to change the property names if we're dealing with html nodes, not XML
11867             if(!xml){
11868                 if(useGetStyle){
11869                     a = Ext.DomQuery.getStyle(ci, attr);
11870                 } else if (attr == "class" || attr == "className"){
11871                     a = ci.className;
11872                 } else if (attr == "for"){
11873                     a = ci.htmlFor;
11874                 } else if (attr == "href"){
11875                     // getAttribute href bug
11876                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
11877                     a = ci.getAttribute("href", 2);
11878                 } else{
11879                     a = ci.getAttribute(attr);
11880                 }
11881             }else{
11882                 a = ci.getAttribute(attr);
11883             }
11884             if((fn && fn(a, value)) || (!fn && a)){
11885                 result[++ri] = ci;
11886             }
11887         }
11888         return result;
11889     }
11890
11891     function byPseudo(cs, name, value){
11892         return Ext.DomQuery.pseudos[name](cs, value);
11893     }
11894
11895     function nodupIEXml(cs){
11896         var d = ++key,
11897             r;
11898         cs[0].setAttribute("_nodup", d);
11899         r = [cs[0]];
11900         for(var i = 1, len = cs.length; i < len; i++){
11901             var c = cs[i];
11902             if(!c.getAttribute("_nodup") != d){
11903                 c.setAttribute("_nodup", d);
11904                 r[r.length] = c;
11905             }
11906         }
11907         for(var i = 0, len = cs.length; i < len; i++){
11908             cs[i].removeAttribute("_nodup");
11909         }
11910         return r;
11911     }
11912
11913     function nodup(cs){
11914         if(!cs){
11915             return [];
11916         }
11917         var len = cs.length, c, i, r = cs, cj, ri = -1;
11918         if(!len || typeof cs.nodeType != "undefined" || len == 1){
11919             return cs;
11920         }
11921         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
11922             return nodupIEXml(cs);
11923         }
11924         var d = ++key;
11925         cs[0]._nodup = d;
11926         for(i = 1; c = cs[i]; i++){
11927             if(c._nodup != d){
11928                 c._nodup = d;
11929             }else{
11930                 r = [];
11931                 for(var j = 0; j < i; j++){
11932                     r[++ri] = cs[j];
11933                 }
11934                 for(j = i+1; cj = cs[j]; j++){
11935                     if(cj._nodup != d){
11936                         cj._nodup = d;
11937                         r[++ri] = cj;
11938                     }
11939                 }
11940                 return r;
11941             }
11942         }
11943         return r;
11944     }
11945
11946     function quickDiffIEXml(c1, c2){
11947         var d = ++key,
11948             r = [];
11949         for(var i = 0, len = c1.length; i < len; i++){
11950             c1[i].setAttribute("_qdiff", d);
11951         }
11952         for(var i = 0, len = c2.length; i < len; i++){
11953             if(c2[i].getAttribute("_qdiff") != d){
11954                 r[r.length] = c2[i];
11955             }
11956         }
11957         for(var i = 0, len = c1.length; i < len; i++){
11958            c1[i].removeAttribute("_qdiff");
11959         }
11960         return r;
11961     }
11962
11963     function quickDiff(c1, c2){
11964         var len1 = c1.length,
11965             d = ++key,
11966             r = [];
11967         if(!len1){
11968             return c2;
11969         }
11970         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
11971             return quickDiffIEXml(c1, c2);
11972         }
11973         for(var i = 0; i < len1; i++){
11974             c1[i]._qdiff = d;
11975         }
11976         for(var i = 0, len = c2.length; i < len; i++){
11977             if(c2[i]._qdiff != d){
11978                 r[r.length] = c2[i];
11979             }
11980         }
11981         return r;
11982     }
11983
11984     function quickId(ns, mode, root, id){
11985         if(ns == root){
11986            var d = root.ownerDocument || root;
11987            return d.getElementById(id);
11988         }
11989         ns = getNodes(ns, mode, "*");
11990         return byId(ns, id);
11991     }
11992
11993     return {
11994         getStyle : function(el, name){
11995             return Ext.fly(el).getStyle(name);
11996         },
11997         /**
11998          * Compiles a selector/xpath query into a reusable function. The returned function
11999          * takes one parameter "root" (optional), which is the context node from where the query should start.
12000          * @param {String} selector The selector/xpath query
12001          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
12002          * @return {Function}
12003          */
12004         compile : function(path, type){
12005             type = type || "select";
12006
12007             // setup fn preamble
12008             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
12009                 mode,
12010                 lastPath,
12011                 matchers = Ext.DomQuery.matchers,
12012                 matchersLn = matchers.length,
12013                 modeMatch,
12014                 // accept leading mode switch
12015                 lmode = path.match(modeRe);
12016
12017             if(lmode && lmode[1]){
12018                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
12019                 path = path.replace(lmode[1], "");
12020             }
12021
12022             // strip leading slashes
12023             while(path.substr(0, 1)=="/"){
12024                 path = path.substr(1);
12025             }
12026
12027             while(path && lastPath != path){
12028                 lastPath = path;
12029                 var tokenMatch = path.match(tagTokenRe);
12030                 if(type == "select"){
12031                     if(tokenMatch){
12032                         // ID Selector
12033                         if(tokenMatch[1] == "#"){
12034                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
12035                         }else{
12036                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
12037                         }
12038                         path = path.replace(tokenMatch[0], "");
12039                     }else if(path.substr(0, 1) != '@'){
12040                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
12041                     }
12042                 // type of "simple"
12043                 }else{
12044                     if(tokenMatch){
12045                         if(tokenMatch[1] == "#"){
12046                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
12047                         }else{
12048                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
12049                         }
12050                         path = path.replace(tokenMatch[0], "");
12051                     }
12052                 }
12053                 while(!(modeMatch = path.match(modeRe))){
12054                     var matched = false;
12055                     for(var j = 0; j < matchersLn; j++){
12056                         var t = matchers[j];
12057                         var m = path.match(t.re);
12058                         if(m){
12059                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
12060                                 return m[i];
12061                             });
12062                             path = path.replace(m[0], "");
12063                             matched = true;
12064                             break;
12065                         }
12066                     }
12067                     // prevent infinite loop on bad selector
12068                     if(!matched){
12069                     }
12070                 }
12071                 if(modeMatch[1]){
12072                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
12073                     path = path.replace(modeMatch[1], "");
12074                 }
12075             }
12076             // close fn out
12077             fn[fn.length] = "return nodup(n);\n}";
12078
12079             // eval fn and return it
12080             eval(fn.join(""));
12081             return f;
12082         },
12083
12084         /**
12085          * Selects an array of DOM nodes using JavaScript-only implementation.
12086          *
12087          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
12088          *
12089          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
12090          * @param {HTMLElement/String} root (optional) The start of the query (defaults to document).
12091          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12092          * no matches, and empty Array is returned.
12093          */
12094         jsSelect: function(path, root, type){
12095             // set root to doc if not specified.
12096             root = root || document;
12097
12098             if(typeof root == "string"){
12099                 root = document.getElementById(root);
12100             }
12101             var paths = path.split(","),
12102                 results = [];
12103
12104             // loop over each selector
12105             for(var i = 0, len = paths.length; i < len; i++){
12106                 var subPath = paths[i].replace(trimRe, "");
12107                 // compile and place in cache
12108                 if(!cache[subPath]){
12109                     cache[subPath] = Ext.DomQuery.compile(subPath);
12110                     if(!cache[subPath]){
12111                     }
12112                 }
12113                 var result = cache[subPath](root);
12114                 if(result && result != document){
12115                     results = results.concat(result);
12116                 }
12117             }
12118
12119             // if there were multiple selectors, make sure dups
12120             // are eliminated
12121             if(paths.length > 1){
12122                 return nodup(results);
12123             }
12124             return results;
12125         },
12126
12127         isXml: function(el) {
12128             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
12129             return docEl ? docEl.nodeName !== "HTML" : false;
12130         },
12131
12132         /**
12133          * Selects an array of DOM nodes by CSS/XPath selector.
12134          *
12135          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
12136          * {@link Ext.DomQuery#jsSelect} to do the work.
12137          *
12138          * Aliased as {@link Ext#query}.
12139          *
12140          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
12141          *
12142          * @param {String} path The selector/xpath query
12143          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12144          * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
12145          * Empty array when no matches.
12146          * @method
12147          */
12148         select : document.querySelectorAll ? function(path, root, type) {
12149             root = root || document;
12150             /* 
12151              * Safari 3.x can't handle uppercase or unicode characters when in quirks mode.
12152              */
12153             if (!Ext.DomQuery.isXml(root) && !(Ext.isSafari3 && !Ext.isStrict)) { 
12154                 try {
12155                     /*
12156                      * This checking here is to "fix" the behaviour of querySelectorAll
12157                      * for non root document queries. The way qsa works is intentional,
12158                      * however it's definitely not the expected way it should work.
12159                      * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/
12160                      *
12161                      * We only modify the path for single selectors (ie, no multiples),
12162                      * without a full parser it makes it difficult to do this correctly.
12163                      */
12164                     var isDocumentRoot = root.nodeType === 9,
12165                         _path = path,
12166                         _root = root;
12167
12168                     if (!isDocumentRoot && path.indexOf(',') === -1 && !startIdRe.test(path)) {
12169                         _path = '#' + Ext.id(root) + ' ' + path;
12170                         _root = root.parentNode;
12171                     }
12172                     return Ext.Array.toArray(_root.querySelectorAll(_path));
12173                 }
12174                 catch (e) {
12175                 }
12176             }
12177             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12178         } : function(path, root, type) {
12179             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12180         },
12181
12182         /**
12183          * Selects a single element.
12184          * @param {String} selector The selector/xpath query
12185          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12186          * @return {HTMLElement} The DOM element which matched the selector.
12187          */
12188         selectNode : function(path, root){
12189             return Ext.DomQuery.select(path, root)[0];
12190         },
12191
12192         /**
12193          * Selects the value of a node, optionally replacing null with the defaultValue.
12194          * @param {String} selector The selector/xpath query
12195          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12196          * @param {String} defaultValue (optional) When specified, this is return as empty value.
12197          * @return {String}
12198          */
12199         selectValue : function(path, root, defaultValue){
12200             path = path.replace(trimRe, "");
12201             if(!valueCache[path]){
12202                 valueCache[path] = Ext.DomQuery.compile(path, "select");
12203             }
12204             var n = valueCache[path](root), v;
12205             n = n[0] ? n[0] : n;
12206
12207             // overcome a limitation of maximum textnode size
12208             // Rumored to potentially crash IE6 but has not been confirmed.
12209             // http://reference.sitepoint.com/javascript/Node/normalize
12210             // https://developer.mozilla.org/En/DOM/Node.normalize
12211             if (typeof n.normalize == 'function') n.normalize();
12212
12213             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
12214             return ((v === null||v === undefined||v==='') ? defaultValue : v);
12215         },
12216
12217         /**
12218          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
12219          * @param {String} selector The selector/xpath query
12220          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12221          * @param {Number} defaultValue (optional) When specified, this is return as empty value.
12222          * @return {Number}
12223          */
12224         selectNumber : function(path, root, defaultValue){
12225             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
12226             return parseFloat(v);
12227         },
12228
12229         /**
12230          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
12231          * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements
12232          * @param {String} selector The simple selector to test
12233          * @return {Boolean}
12234          */
12235         is : function(el, ss){
12236             if(typeof el == "string"){
12237                 el = document.getElementById(el);
12238             }
12239             var isArray = Ext.isArray(el),
12240                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
12241             return isArray ? (result.length == el.length) : (result.length > 0);
12242         },
12243
12244         /**
12245          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
12246          * @param {HTMLElement[]} el An array of elements to filter
12247          * @param {String} selector The simple selector to test
12248          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
12249          * the selector instead of the ones that match
12250          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12251          * no matches, and empty Array is returned.
12252          */
12253         filter : function(els, ss, nonMatches){
12254             ss = ss.replace(trimRe, "");
12255             if(!simpleCache[ss]){
12256                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
12257             }
12258             var result = simpleCache[ss](els);
12259             return nonMatches ? quickDiff(result, els) : result;
12260         },
12261
12262         /**
12263          * Collection of matching regular expressions and code snippets.
12264          * Each capture group within () will be replace the {} in the select
12265          * statement as specified by their index.
12266          */
12267         matchers : [{
12268                 re: /^\.([\w-]+)/,
12269                 select: 'n = byClassName(n, " {1} ");'
12270             }, {
12271                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
12272                 select: 'n = byPseudo(n, "{1}", "{2}");'
12273             },{
12274                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
12275                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
12276             }, {
12277                 re: /^#([\w-]+)/,
12278                 select: 'n = byId(n, "{1}");'
12279             },{
12280                 re: /^@([\w-]+)/,
12281                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
12282             }
12283         ],
12284
12285         /**
12286          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
12287          * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, &gt; &lt;.
12288          */
12289         operators : {
12290             "=" : function(a, v){
12291                 return a == v;
12292             },
12293             "!=" : function(a, v){
12294                 return a != v;
12295             },
12296             "^=" : function(a, v){
12297                 return a && a.substr(0, v.length) == v;
12298             },
12299             "$=" : function(a, v){
12300                 return a && a.substr(a.length-v.length) == v;
12301             },
12302             "*=" : function(a, v){
12303                 return a && a.indexOf(v) !== -1;
12304             },
12305             "%=" : function(a, v){
12306                 return (a % v) == 0;
12307             },
12308             "|=" : function(a, v){
12309                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
12310             },
12311             "~=" : function(a, v){
12312                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
12313             }
12314         },
12315
12316         /**
12317 Object hash of "pseudo class" filter functions which are used when filtering selections.
12318 Each function is passed two parameters:
12319
12320 - **c** : Array
12321     An Array of DOM elements to filter.
12322
12323 - **v** : String
12324     The argument (if any) supplied in the selector.
12325
12326 A filter function returns an Array of DOM elements which conform to the pseudo class.
12327 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
12328 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
12329
12330 For example, to filter `a` elements to only return links to __external__ resources:
12331
12332     Ext.DomQuery.pseudos.external = function(c, v){
12333         var r = [], ri = -1;
12334         for(var i = 0, ci; ci = c[i]; i++){
12335             // Include in result set only if it's a link to an external resource
12336             if(ci.hostname != location.hostname){
12337                 r[++ri] = ci;
12338             }
12339         }
12340         return r;
12341     };
12342
12343 Then external links could be gathered with the following statement:
12344
12345     var externalLinks = Ext.select("a:external");
12346
12347         * @markdown
12348         */
12349         pseudos : {
12350             "first-child" : function(c){
12351                 var r = [], ri = -1, n;
12352                 for(var i = 0, ci; ci = n = c[i]; i++){
12353                     while((n = n.previousSibling) && n.nodeType != 1);
12354                     if(!n){
12355                         r[++ri] = ci;
12356                     }
12357                 }
12358                 return r;
12359             },
12360
12361             "last-child" : function(c){
12362                 var r = [], ri = -1, n;
12363                 for(var i = 0, ci; ci = n = c[i]; i++){
12364                     while((n = n.nextSibling) && n.nodeType != 1);
12365                     if(!n){
12366                         r[++ri] = ci;
12367                     }
12368                 }
12369                 return r;
12370             },
12371
12372             "nth-child" : function(c, a) {
12373                 var r = [], ri = -1,
12374                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
12375                     f = (m[1] || 1) - 0, l = m[2] - 0;
12376                 for(var i = 0, n; n = c[i]; i++){
12377                     var pn = n.parentNode;
12378                     if (batch != pn._batch) {
12379                         var j = 0;
12380                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
12381                             if(cn.nodeType == 1){
12382                                cn.nodeIndex = ++j;
12383                             }
12384                         }
12385                         pn._batch = batch;
12386                     }
12387                     if (f == 1) {
12388                         if (l == 0 || n.nodeIndex == l){
12389                             r[++ri] = n;
12390                         }
12391                     } else if ((n.nodeIndex + l) % f == 0){
12392                         r[++ri] = n;
12393                     }
12394                 }
12395
12396                 return r;
12397             },
12398
12399             "only-child" : function(c){
12400                 var r = [], ri = -1;;
12401                 for(var i = 0, ci; ci = c[i]; i++){
12402                     if(!prev(ci) && !next(ci)){
12403                         r[++ri] = ci;
12404                     }
12405                 }
12406                 return r;
12407             },
12408
12409             "empty" : function(c){
12410                 var r = [], ri = -1;
12411                 for(var i = 0, ci; ci = c[i]; i++){
12412                     var cns = ci.childNodes, j = 0, cn, empty = true;
12413                     while(cn = cns[j]){
12414                         ++j;
12415                         if(cn.nodeType == 1 || cn.nodeType == 3){
12416                             empty = false;
12417                             break;
12418                         }
12419                     }
12420                     if(empty){
12421                         r[++ri] = ci;
12422                     }
12423                 }
12424                 return r;
12425             },
12426
12427             "contains" : function(c, v){
12428                 var r = [], ri = -1;
12429                 for(var i = 0, ci; ci = c[i]; i++){
12430                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
12431                         r[++ri] = ci;
12432                     }
12433                 }
12434                 return r;
12435             },
12436
12437             "nodeValue" : function(c, v){
12438                 var r = [], ri = -1;
12439                 for(var i = 0, ci; ci = c[i]; i++){
12440                     if(ci.firstChild && ci.firstChild.nodeValue == v){
12441                         r[++ri] = ci;
12442                     }
12443                 }
12444                 return r;
12445             },
12446
12447             "checked" : function(c){
12448                 var r = [], ri = -1;
12449                 for(var i = 0, ci; ci = c[i]; i++){
12450                     if(ci.checked == true){
12451                         r[++ri] = ci;
12452                     }
12453                 }
12454                 return r;
12455             },
12456
12457             "not" : function(c, ss){
12458                 return Ext.DomQuery.filter(c, ss, true);
12459             },
12460
12461             "any" : function(c, selectors){
12462                 var ss = selectors.split('|'),
12463                     r = [], ri = -1, s;
12464                 for(var i = 0, ci; ci = c[i]; i++){
12465                     for(var j = 0; s = ss[j]; j++){
12466                         if(Ext.DomQuery.is(ci, s)){
12467                             r[++ri] = ci;
12468                             break;
12469                         }
12470                     }
12471                 }
12472                 return r;
12473             },
12474
12475             "odd" : function(c){
12476                 return this["nth-child"](c, "odd");
12477             },
12478
12479             "even" : function(c){
12480                 return this["nth-child"](c, "even");
12481             },
12482
12483             "nth" : function(c, a){
12484                 return c[a-1] || [];
12485             },
12486
12487             "first" : function(c){
12488                 return c[0] || [];
12489             },
12490
12491             "last" : function(c){
12492                 return c[c.length-1] || [];
12493             },
12494
12495             "has" : function(c, ss){
12496                 var s = Ext.DomQuery.select,
12497                     r = [], ri = -1;
12498                 for(var i = 0, ci; ci = c[i]; i++){
12499                     if(s(ss, ci).length > 0){
12500                         r[++ri] = ci;
12501                     }
12502                 }
12503                 return r;
12504             },
12505
12506             "next" : function(c, ss){
12507                 var is = Ext.DomQuery.is,
12508                     r = [], ri = -1;
12509                 for(var i = 0, ci; ci = c[i]; i++){
12510                     var n = next(ci);
12511                     if(n && is(n, ss)){
12512                         r[++ri] = ci;
12513                     }
12514                 }
12515                 return r;
12516             },
12517
12518             "prev" : function(c, ss){
12519                 var is = Ext.DomQuery.is,
12520                     r = [], ri = -1;
12521                 for(var i = 0, ci; ci = c[i]; i++){
12522                     var n = prev(ci);
12523                     if(n && is(n, ss)){
12524                         r[++ri] = ci;
12525                     }
12526                 }
12527                 return r;
12528             }
12529         }
12530     };
12531 }();
12532
12533 /**
12534  * Shorthand of {@link Ext.DomQuery#select}
12535  * @member Ext
12536  * @method query
12537  * @alias Ext.DomQuery#select
12538  */
12539 Ext.query = Ext.DomQuery.select;
12540
12541 /**
12542  * @class Ext.Element
12543  * @alternateClassName Ext.core.Element
12544  *
12545  * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
12546  *
12547  * All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all
12548  * DOM elements.
12549  *
12550  * Note that the events documented in this class are not Ext events, they encapsulate browser events. Some older browsers
12551  * may not support the full range of events. Which events are supported is beyond the control of Ext JS.
12552  *
12553  * Usage:
12554  *
12555  *     // by id
12556  *     var el = Ext.get("my-div");
12557  *
12558  *     // by DOM element reference
12559  *     var el = Ext.get(myDivElement);
12560  *
12561  * # Animations
12562  *
12563  * When an element is manipulated, by default there is no animation.
12564  *
12565  *     var el = Ext.get("my-div");
12566  *
12567  *     // no animation
12568  *     el.setWidth(100);
12569  *
12570  * Many of the functions for manipulating an element have an optional "animate" parameter. This parameter can be
12571  * specified as boolean (true) for default animation effects.
12572  *
12573  *     // default animation
12574  *     el.setWidth(100, true);
12575  *
12576  * To configure the effects, an object literal with animation options to use as the Element animation configuration
12577  * object can also be specified. Note that the supported Element animation configuration options are a subset of the
12578  * {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
12579  * are:
12580  *
12581  *     Option    Default   Description
12582  *     --------- --------  ---------------------------------------------
12583  *     {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
12584  *     {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
12585  *     {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
12586  *     {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
12587  *
12588  * Usage:
12589  *
12590  *     // Element animation options object
12591  *     var opt = {
12592  *         {@link Ext.fx.Anim#duration duration}: 1,
12593  *         {@link Ext.fx.Anim#easing easing}: 'elasticIn',
12594  *         {@link Ext.fx.Anim#callback callback}: this.foo,
12595  *         {@link Ext.fx.Anim#scope scope}: this
12596  *     };
12597  *     // animation with some options set
12598  *     el.setWidth(100, opt);
12599  *
12600  * The Element animation object being used for the animation will be set on the options object as "anim", which allows
12601  * you to stop or manipulate the animation. Here is an example:
12602  *
12603  *     // using the "anim" property to get the Anim object
12604  *     if(opt.anim.isAnimated()){
12605  *         opt.anim.stop();
12606  *     }
12607  *
12608  * # Composite (Collections of) Elements
12609  *
12610  * For working with collections of Elements, see {@link Ext.CompositeElement}
12611  *
12612  * @constructor
12613  * Creates new Element directly.
12614  * @param {String/HTMLElement} element
12615  * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this
12616  * element in the cache and if there is it returns the same instance. This will skip that check (useful for extending
12617  * this class).
12618  * @return {Object}
12619  */
12620  (function() {
12621     var DOC = document,
12622         EC = Ext.cache;
12623
12624     Ext.Element = Ext.core.Element = function(element, forceNew) {
12625         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
12626         id;
12627
12628         if (!dom) {
12629             return null;
12630         }
12631
12632         id = dom.id;
12633
12634         if (!forceNew && id && EC[id]) {
12635             // element object already exists
12636             return EC[id].el;
12637         }
12638
12639         /**
12640          * @property {HTMLElement} dom
12641          * The DOM element
12642          */
12643         this.dom = dom;
12644
12645         /**
12646          * @property {String} id
12647          * The DOM element ID
12648          */
12649         this.id = id || Ext.id(dom);
12650     };
12651
12652     var DH = Ext.DomHelper,
12653     El = Ext.Element;
12654
12655
12656     El.prototype = {
12657         /**
12658          * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
12659          * @param {Object} o The object with the attributes
12660          * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
12661          * @return {Ext.Element} this
12662          */
12663         set: function(o, useSet) {
12664             var el = this.dom,
12665                 attr,
12666                 val;
12667             useSet = (useSet !== false) && !!el.setAttribute;
12668
12669             for (attr in o) {
12670                 if (o.hasOwnProperty(attr)) {
12671                     val = o[attr];
12672                     if (attr == 'style') {
12673                         DH.applyStyles(el, val);
12674                     } else if (attr == 'cls') {
12675                         el.className = val;
12676                     } else if (useSet) {
12677                         el.setAttribute(attr, val);
12678                     } else {
12679                         el[attr] = val;
12680                     }
12681                 }
12682             }
12683             return this;
12684         },
12685
12686         //  Mouse events
12687         /**
12688          * @event click
12689          * Fires when a mouse click is detected within the element.
12690          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12691          * @param {HTMLElement} t The target of the event.
12692          */
12693         /**
12694          * @event contextmenu
12695          * Fires when a right click is detected within the element.
12696          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12697          * @param {HTMLElement} t The target of the event.
12698          */
12699         /**
12700          * @event dblclick
12701          * Fires when a mouse double click is detected within the element.
12702          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12703          * @param {HTMLElement} t The target of the event.
12704          */
12705         /**
12706          * @event mousedown
12707          * Fires when a mousedown is detected within the element.
12708          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12709          * @param {HTMLElement} t The target of the event.
12710          */
12711         /**
12712          * @event mouseup
12713          * Fires when a mouseup is detected within the element.
12714          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12715          * @param {HTMLElement} t The target of the event.
12716          */
12717         /**
12718          * @event mouseover
12719          * Fires when a mouseover is detected within the element.
12720          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12721          * @param {HTMLElement} t The target of the event.
12722          */
12723         /**
12724          * @event mousemove
12725          * Fires when a mousemove is detected with the element.
12726          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12727          * @param {HTMLElement} t The target of the event.
12728          */
12729         /**
12730          * @event mouseout
12731          * Fires when a mouseout is detected with the element.
12732          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12733          * @param {HTMLElement} t The target of the event.
12734          */
12735         /**
12736          * @event mouseenter
12737          * Fires when the mouse enters the element.
12738          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12739          * @param {HTMLElement} t The target of the event.
12740          */
12741         /**
12742          * @event mouseleave
12743          * Fires when the mouse leaves the element.
12744          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12745          * @param {HTMLElement} t The target of the event.
12746          */
12747
12748         //  Keyboard events
12749         /**
12750          * @event keypress
12751          * Fires when a keypress is detected within the element.
12752          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12753          * @param {HTMLElement} t The target of the event.
12754          */
12755         /**
12756          * @event keydown
12757          * Fires when a keydown is detected within the element.
12758          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12759          * @param {HTMLElement} t The target of the event.
12760          */
12761         /**
12762          * @event keyup
12763          * Fires when a keyup is detected within the element.
12764          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12765          * @param {HTMLElement} t The target of the event.
12766          */
12767
12768
12769         //  HTML frame/object events
12770         /**
12771          * @event load
12772          * Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
12773          * objects and images.
12774          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12775          * @param {HTMLElement} t The target of the event.
12776          */
12777         /**
12778          * @event unload
12779          * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
12780          * element or any of its content has been removed.
12781          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12782          * @param {HTMLElement} t The target of the event.
12783          */
12784         /**
12785          * @event abort
12786          * Fires when an object/image is stopped from loading before completely loaded.
12787          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12788          * @param {HTMLElement} t The target of the event.
12789          */
12790         /**
12791          * @event error
12792          * Fires when an object/image/frame cannot be loaded properly.
12793          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12794          * @param {HTMLElement} t The target of the event.
12795          */
12796         /**
12797          * @event resize
12798          * Fires when a document view is resized.
12799          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12800          * @param {HTMLElement} t The target of the event.
12801          */
12802         /**
12803          * @event scroll
12804          * Fires when a document view is scrolled.
12805          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12806          * @param {HTMLElement} t The target of the event.
12807          */
12808
12809         //  Form events
12810         /**
12811          * @event select
12812          * Fires when a user selects some text in a text field, including input and textarea.
12813          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12814          * @param {HTMLElement} t The target of the event.
12815          */
12816         /**
12817          * @event change
12818          * Fires when a control loses the input focus and its value has been modified since gaining focus.
12819          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12820          * @param {HTMLElement} t The target of the event.
12821          */
12822         /**
12823          * @event submit
12824          * Fires when a form is submitted.
12825          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12826          * @param {HTMLElement} t The target of the event.
12827          */
12828         /**
12829          * @event reset
12830          * Fires when a form is reset.
12831          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12832          * @param {HTMLElement} t The target of the event.
12833          */
12834         /**
12835          * @event focus
12836          * Fires when an element receives focus either via the pointing device or by tab navigation.
12837          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12838          * @param {HTMLElement} t The target of the event.
12839          */
12840         /**
12841          * @event blur
12842          * Fires when an element loses focus either via the pointing device or by tabbing navigation.
12843          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12844          * @param {HTMLElement} t The target of the event.
12845          */
12846
12847         //  User Interface events
12848         /**
12849          * @event DOMFocusIn
12850          * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
12851          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12852          * @param {HTMLElement} t The target of the event.
12853          */
12854         /**
12855          * @event DOMFocusOut
12856          * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
12857          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12858          * @param {HTMLElement} t The target of the event.
12859          */
12860         /**
12861          * @event DOMActivate
12862          * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
12863          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12864          * @param {HTMLElement} t The target of the event.
12865          */
12866
12867         //  DOM Mutation events
12868         /**
12869          * @event DOMSubtreeModified
12870          * Where supported. Fires when the subtree is modified.
12871          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12872          * @param {HTMLElement} t The target of the event.
12873          */
12874         /**
12875          * @event DOMNodeInserted
12876          * Where supported. Fires when a node has been added as a child of another node.
12877          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12878          * @param {HTMLElement} t The target of the event.
12879          */
12880         /**
12881          * @event DOMNodeRemoved
12882          * Where supported. Fires when a descendant node of the element is removed.
12883          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12884          * @param {HTMLElement} t The target of the event.
12885          */
12886         /**
12887          * @event DOMNodeRemovedFromDocument
12888          * Where supported. Fires when a node is being removed from a document.
12889          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12890          * @param {HTMLElement} t The target of the event.
12891          */
12892         /**
12893          * @event DOMNodeInsertedIntoDocument
12894          * Where supported. Fires when a node is being inserted into a document.
12895          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12896          * @param {HTMLElement} t The target of the event.
12897          */
12898         /**
12899          * @event DOMAttrModified
12900          * Where supported. Fires when an attribute has been modified.
12901          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12902          * @param {HTMLElement} t The target of the event.
12903          */
12904         /**
12905          * @event DOMCharacterDataModified
12906          * Where supported. Fires when the character data has been modified.
12907          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12908          * @param {HTMLElement} t The target of the event.
12909          */
12910
12911         /**
12912          * @property {String} defaultUnit
12913          * The default unit to append to CSS values where a unit isn't provided.
12914          */
12915         defaultUnit: "px",
12916
12917         /**
12918          * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
12919          * @param {String} selector The simple selector to test
12920          * @return {Boolean} True if this element matches the selector, else false
12921          */
12922         is: function(simpleSelector) {
12923             return Ext.DomQuery.is(this.dom, simpleSelector);
12924         },
12925
12926         /**
12927          * Tries to focus the element. Any exceptions are caught and ignored.
12928          * @param {Number} defer (optional) Milliseconds to defer the focus
12929          * @return {Ext.Element} this
12930          */
12931         focus: function(defer,
12932                         /* private */
12933                         dom) {
12934             var me = this;
12935             dom = dom || me.dom;
12936             try {
12937                 if (Number(defer)) {
12938                     Ext.defer(me.focus, defer, null, [null, dom]);
12939                 } else {
12940                     dom.focus();
12941                 }
12942             } catch(e) {}
12943             return me;
12944         },
12945
12946         /**
12947          * Tries to blur the element. Any exceptions are caught and ignored.
12948          * @return {Ext.Element} this
12949          */
12950         blur: function() {
12951             try {
12952                 this.dom.blur();
12953             } catch(e) {}
12954             return this;
12955         },
12956
12957         /**
12958          * Returns the value of the "value" attribute
12959          * @param {Boolean} asNumber true to parse the value as a number
12960          * @return {String/Number}
12961          */
12962         getValue: function(asNumber) {
12963             var val = this.dom.value;
12964             return asNumber ? parseInt(val, 10) : val;
12965         },
12966
12967         /**
12968          * Appends an event handler to this element.
12969          *
12970          * @param {String} eventName The name of event to handle.
12971          *
12972          * @param {Function} fn The handler function the event invokes. This function is passed the following parameters:
12973          *
12974          * - **evt** : EventObject
12975          *
12976          *   The {@link Ext.EventObject EventObject} describing the event.
12977          *
12978          * - **el** : HtmlElement
12979          *
12980          *   The DOM element which was the target of the event. Note that this may be filtered by using the delegate option.
12981          *
12982          * - **o** : Object
12983          *
12984          *   The options object from the addListener call.
12985          *
12986          * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed. **If
12987          * omitted, defaults to this Element.**
12988          *
12989          * @param {Object} options (optional) An object containing handler configuration properties. This may contain any of
12990          * the following properties:
12991          *
12992          * - **scope** Object :
12993          *
12994          *   The scope (**this** reference) in which the handler function is executed. **If omitted, defaults to this
12995          *   Element.**
12996          *
12997          * - **delegate** String:
12998          *
12999          *   A simple selector to filter the target or look for a descendant of the target. See below for additional details.
13000          *
13001          * - **stopEvent** Boolean:
13002          *
13003          *   True to stop the event. That is stop propagation, and prevent the default action.
13004          *
13005          * - **preventDefault** Boolean:
13006          *
13007          *   True to prevent the default action
13008          *
13009          * - **stopPropagation** Boolean:
13010          *
13011          *   True to prevent event propagation
13012          *
13013          * - **normalized** Boolean:
13014          *
13015          *   False to pass a browser event to the handler function instead of an Ext.EventObject
13016          *
13017          * - **target** Ext.Element:
13018          *
13019          *   Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a
13020          *   child node.
13021          *
13022          * - **delay** Number:
13023          *
13024          *   The number of milliseconds to delay the invocation of the handler after the event fires.
13025          *
13026          * - **single** Boolean:
13027          *
13028          *   True to add a handler to handle just the next firing of the event, and then remove itself.
13029          *
13030          * - **buffer** Number:
13031          *
13032          *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
13033          *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
13034          *   handler is scheduled in its place.
13035          *
13036          * **Combining Options**
13037          *
13038          * In the following examples, the shorthand form {@link #on} is used rather than the more verbose addListener. The
13039          * two are equivalent. Using the options argument, it is possible to combine different types of listeners:
13040          *
13041          * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the options
13042          * object. The options object is available as the third parameter in the handler function.
13043          *
13044          * Code:
13045          *
13046          *     el.on('click', this.onClick, this, {
13047          *         single: true,
13048          *         delay: 100,
13049          *         stopEvent : true,
13050          *         forumId: 4
13051          *     });
13052          *
13053          * **Attaching multiple handlers in 1 call**
13054          *
13055          * The method also allows for a single argument to be passed which is a config object containing properties which
13056          * specify multiple handlers.
13057          *
13058          * Code:
13059          *
13060          *     el.on({
13061          *         'click' : {
13062          *             fn: this.onClick,
13063          *             scope: this,
13064          *             delay: 100
13065          *         },
13066          *         'mouseover' : {
13067          *             fn: this.onMouseOver,
13068          *             scope: this
13069          *         },
13070          *         'mouseout' : {
13071          *             fn: this.onMouseOut,
13072          *             scope: this
13073          *         }
13074          *     });
13075          *
13076          * Or a shorthand syntax:
13077          *
13078          * Code:
13079          *
13080          *     el.on({
13081          *         'click' : this.onClick,
13082          *         'mouseover' : this.onMouseOver,
13083          *         'mouseout' : this.onMouseOut,
13084          *         scope: this
13085          *     });
13086          *
13087          * **delegate**
13088          *
13089          * This is a configuration option that you can pass along when registering a handler for an event to assist with
13090          * event delegation. Event delegation is a technique that is used to reduce memory consumption and prevent exposure
13091          * to memory-leaks. By registering an event for a container element as opposed to each element within a container.
13092          * By setting this configuration option to a simple selector, the target element will be filtered to look for a
13093          * descendant of the target. For example:
13094          *
13095          *     // using this markup:
13096          *     <div id='elId'>
13097          *         <p id='p1'>paragraph one</p>
13098          *         <p id='p2' class='clickable'>paragraph two</p>
13099          *         <p id='p3'>paragraph three</p>
13100          *     </div>
13101          *
13102          *     // utilize event delegation to registering just one handler on the container element:
13103          *     el = Ext.get('elId');
13104          *     el.on(
13105          *         'click',
13106          *         function(e,t) {
13107          *             // handle click
13108          *             console.info(t.id); // 'p2'
13109          *         },
13110          *         this,
13111          *         {
13112          *             // filter the target element to be a descendant with the class 'clickable'
13113          *             delegate: '.clickable'
13114          *         }
13115          *     );
13116          *
13117          * @return {Ext.Element} this
13118          */
13119         addListener: function(eventName, fn, scope, options) {
13120             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
13121             return this;
13122         },
13123
13124         /**
13125          * Removes an event handler from this element.
13126          *
13127          * **Note**: if a *scope* was explicitly specified when {@link #addListener adding} the listener,
13128          * the same scope must be specified here.
13129          *
13130          * Example:
13131          *
13132          *     el.removeListener('click', this.handlerFn);
13133          *     // or
13134          *     el.un('click', this.handlerFn);
13135          *
13136          * @param {String} eventName The name of the event from which to remove the handler.
13137          * @param {Function} fn The handler function to remove. **This must be a reference to the function passed into the
13138          * {@link #addListener} call.**
13139          * @param {Object} scope If a scope (**this** reference) was specified when the listener was added, then this must
13140          * refer to the same object.
13141          * @return {Ext.Element} this
13142          */
13143         removeListener: function(eventName, fn, scope) {
13144             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
13145             return this;
13146         },
13147
13148         /**
13149          * Removes all previous added listeners from this element
13150          * @return {Ext.Element} this
13151          */
13152         removeAllListeners: function() {
13153             Ext.EventManager.removeAll(this.dom);
13154             return this;
13155         },
13156
13157         /**
13158          * Recursively removes all previous added listeners from this element and its children
13159          * @return {Ext.Element} this
13160          */
13161         purgeAllListeners: function() {
13162             Ext.EventManager.purgeElement(this);
13163             return this;
13164         },
13165
13166         /**
13167          * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
13168          * @param size {Mixed} The size to set
13169          * @param units {String} The units to append to a numeric size value
13170          * @private
13171          */
13172         addUnits: function(size, units) {
13173
13174             // Most common case first: Size is set to a number
13175             if (Ext.isNumber(size)) {
13176                 return size + (units || this.defaultUnit || 'px');
13177             }
13178
13179             // Size set to a value which means "auto"
13180             if (size === "" || size == "auto" || size == null) {
13181                 return size || '';
13182             }
13183
13184             // Otherwise, warn if it's not a valid CSS measurement
13185             if (!unitPattern.test(size)) {
13186                 return size || '';
13187             }
13188             return size;
13189         },
13190
13191         /**
13192          * Tests various css rules/browsers to determine if this element uses a border box
13193          * @return {Boolean}
13194          */
13195         isBorderBox: function() {
13196             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
13197         },
13198
13199         /**
13200          * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13201          * Ext.removeNode}
13202          */
13203         remove: function() {
13204             var me = this,
13205             dom = me.dom;
13206
13207             if (dom) {
13208                 delete me.dom;
13209                 Ext.removeNode(dom);
13210             }
13211         },
13212
13213         /**
13214          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
13215          * @param {Function} overFn The function to call when the mouse enters the Element.
13216          * @param {Function} outFn The function to call when the mouse leaves the Element.
13217          * @param {Object} scope (optional) The scope (`this` reference) in which the functions are executed. Defaults
13218          * to the Element's DOM element.
13219          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the
13220          * options parameter}.
13221          * @return {Ext.Element} this
13222          */
13223         hover: function(overFn, outFn, scope, options) {
13224             var me = this;
13225             me.on('mouseenter', overFn, scope || me.dom, options);
13226             me.on('mouseleave', outFn, scope || me.dom, options);
13227             return me;
13228         },
13229
13230         /**
13231          * Returns true if this element is an ancestor of the passed element
13232          * @param {HTMLElement/String} el The element to check
13233          * @return {Boolean} True if this element is an ancestor of el, else false
13234          */
13235         contains: function(el) {
13236             return ! el ? false: Ext.Element.isAncestor(this.dom, el.dom ? el.dom: el);
13237         },
13238
13239         /**
13240          * Returns the value of a namespaced attribute from the element's underlying DOM node.
13241          * @param {String} namespace The namespace in which to look for the attribute
13242          * @param {String} name The attribute name
13243          * @return {String} The attribute value
13244          */
13245         getAttributeNS: function(ns, name) {
13246             return this.getAttribute(name, ns);
13247         },
13248
13249         /**
13250          * Returns the value of an attribute from the element's underlying DOM node.
13251          * @param {String} name The attribute name
13252          * @param {String} namespace (optional) The namespace in which to look for the attribute
13253          * @return {String} The attribute value
13254          * @method
13255          */
13256         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
13257         function(name, ns) {
13258             var d = this.dom,
13259             type;
13260             if(ns) {
13261                 type = typeof d[ns + ":" + name];
13262                 if (type != 'undefined' && type != 'unknown') {
13263                     return d[ns + ":" + name] || null;
13264                 }
13265                 return null;
13266             }
13267             if (name === "for") {
13268                 name = "htmlFor";
13269             }
13270             return d[name] || null;
13271         }: function(name, ns) {
13272             var d = this.dom;
13273             if (ns) {
13274                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
13275             }
13276             return  d.getAttribute(name) || d[name] || null;
13277         },
13278
13279         /**
13280          * Update the innerHTML of this element
13281          * @param {String} html The new HTML
13282          * @return {Ext.Element} this
13283          */
13284         update: function(html) {
13285             if (this.dom) {
13286                 this.dom.innerHTML = html;
13287             }
13288             return this;
13289         }
13290     };
13291
13292     var ep = El.prototype;
13293
13294     El.addMethods = function(o) {
13295         Ext.apply(ep, o);
13296     };
13297
13298     /**
13299      * @method
13300      * @alias Ext.Element#addListener
13301      * Shorthand for {@link #addListener}.
13302      */
13303     ep.on = ep.addListener;
13304
13305     /**
13306      * @method
13307      * @alias Ext.Element#removeListener
13308      * Shorthand for {@link #removeListener}.
13309      */
13310     ep.un = ep.removeListener;
13311
13312     /**
13313      * @method
13314      * @alias Ext.Element#removeAllListeners
13315      * Alias for {@link #removeAllListeners}.
13316      */
13317     ep.clearListeners = ep.removeAllListeners;
13318
13319     /**
13320      * @method destroy
13321      * @member Ext.Element
13322      * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13323      * Ext.removeNode}. Alias to {@link #remove}.
13324      */
13325     ep.destroy = ep.remove;
13326
13327     /**
13328      * @property {Boolean} autoBoxAdjust
13329      * true to automatically adjust width and height settings for box-model issues (default to true)
13330      */
13331     ep.autoBoxAdjust = true;
13332
13333     // private
13334     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
13335     docEl;
13336
13337     /**
13338      * Retrieves Ext.Element objects. {@link Ext#get} is an alias for {@link Ext.Element#get}.
13339      *
13340      * **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.Element
13341      * objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
13342      *
13343      * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
13344      * the same id via AJAX or DOM.
13345      *
13346      * @param {String/HTMLElement/Ext.Element} el The id of the node, a DOM Node or an existing Element.
13347      * @return {Ext.Element} The Element object (or null if no matching element was found)
13348      * @static
13349      */
13350     El.get = function(el) {
13351         var ex,
13352         elm,
13353         id;
13354         if (!el) {
13355             return null;
13356         }
13357         if (typeof el == "string") {
13358             // element id
13359             if (! (elm = DOC.getElementById(el))) {
13360                 return null;
13361             }
13362             if (EC[el] && EC[el].el) {
13363                 ex = EC[el].el;
13364                 ex.dom = elm;
13365             } else {
13366                 ex = El.addToCache(new El(elm));
13367             }
13368             return ex;
13369         } else if (el.tagName) {
13370             // dom element
13371             if (! (id = el.id)) {
13372                 id = Ext.id(el);
13373             }
13374             if (EC[id] && EC[id].el) {
13375                 ex = EC[id].el;
13376                 ex.dom = el;
13377             } else {
13378                 ex = El.addToCache(new El(el));
13379             }
13380             return ex;
13381         } else if (el instanceof El) {
13382             if (el != docEl) {
13383                 // refresh dom element in case no longer valid,
13384                 // catch case where it hasn't been appended
13385                 // If an el instance is passed, don't pass to getElementById without some kind of id
13386                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
13387                     el.dom = el.dom;
13388                 } else {
13389                     el.dom = DOC.getElementById(el.id) || el.dom;
13390                 }
13391             }
13392             return el;
13393         } else if (el.isComposite) {
13394             return el;
13395         } else if (Ext.isArray(el)) {
13396             return El.select(el);
13397         } else if (el == DOC) {
13398             // create a bogus element object representing the document object
13399             if (!docEl) {
13400                 var f = function() {};
13401                 f.prototype = El.prototype;
13402                 docEl = new f();
13403                 docEl.dom = DOC;
13404             }
13405             return docEl;
13406         }
13407         return null;
13408     };
13409
13410     /**
13411      * Retrieves Ext.Element objects like {@link Ext#get} but is optimized for sub-elements.
13412      * This is helpful for performance, because in IE (prior to IE 9), `getElementById` uses
13413      * an non-optimized search. In those browsers, starting the search for an element with a
13414      * matching ID at a parent of that element will greatly speed up the process.
13415      *
13416      * Unlike {@link Ext#get}, this method only accepts ID's. If the ID is not a child of
13417      * this element, it will still be found if it exists in the document, but will be slower
13418      * than calling {@link Ext#get} directly.
13419      *
13420      * @param {String} id The id of the element to get.
13421      * @return {Ext.Element} The Element object (or null if no matching element was found)
13422      * @member Ext.Element
13423      * @method getById
13424      * @markdown
13425      */
13426     ep.getById = (!Ext.isIE6 && !Ext.isIE7 && !Ext.isIE8) ? El.get :
13427         function (id) {
13428             var dom = this.dom,
13429                 cached, el, ret;
13430
13431             if (dom) {
13432                 el = dom.all[id];
13433                 if (el) {
13434                     // calling El.get here is a real hit (2x slower) because it has to
13435                     // redetermine that we are giving it a dom el.
13436                     cached = EC[id];
13437                     if (cached && cached.el) {
13438                         ret = cached.el;
13439                         ret.dom = el;
13440                     } else {
13441                         ret = El.addToCache(new El(el));
13442                     }
13443                     return ret;
13444                 }
13445             }
13446
13447             return El.get(id);
13448         };
13449
13450     El.addToCache = function(el, id) {
13451         if (el) {
13452             id = id || el.id;
13453             EC[id] = {
13454                 el: el,
13455                 data: {},
13456                 events: {}
13457             };
13458         }
13459         return el;
13460     };
13461
13462     // private method for getting and setting element data
13463     El.data = function(el, key, value) {
13464         el = El.get(el);
13465         if (!el) {
13466             return null;
13467         }
13468         var c = EC[el.id].data;
13469         if (arguments.length == 2) {
13470             return c[key];
13471         } else {
13472             return (c[key] = value);
13473         }
13474     };
13475
13476     // private
13477     // Garbage collection - uncache elements/purge listeners on orphaned elements
13478     // so we don't hold a reference and cause the browser to retain them
13479     function garbageCollect() {
13480         if (!Ext.enableGarbageCollector) {
13481             clearInterval(El.collectorThreadId);
13482         } else {
13483             var eid,
13484             el,
13485             d,
13486             o;
13487
13488             for (eid in EC) {
13489                 if (!EC.hasOwnProperty(eid)) {
13490                     continue;
13491                 }
13492                 o = EC[eid];
13493                 if (o.skipGarbageCollection) {
13494                     continue;
13495                 }
13496                 el = o.el;
13497                 d = el.dom;
13498                 // -------------------------------------------------------
13499                 // Determining what is garbage:
13500                 // -------------------------------------------------------
13501                 // !d
13502                 // dom node is null, definitely garbage
13503                 // -------------------------------------------------------
13504                 // !d.parentNode
13505                 // no parentNode == direct orphan, definitely garbage
13506                 // -------------------------------------------------------
13507                 // !d.offsetParent && !document.getElementById(eid)
13508                 // display none elements have no offsetParent so we will
13509                 // also try to look it up by it's id. However, check
13510                 // offsetParent first so we don't do unneeded lookups.
13511                 // This enables collection of elements that are not orphans
13512                 // directly, but somewhere up the line they have an orphan
13513                 // parent.
13514                 // -------------------------------------------------------
13515                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
13516                     if (d && Ext.enableListenerCollection) {
13517                         Ext.EventManager.removeAll(d);
13518                     }
13519                     delete EC[eid];
13520                 }
13521             }
13522             // Cleanup IE Object leaks
13523             if (Ext.isIE) {
13524                 var t = {};
13525                 for (eid in EC) {
13526                     if (!EC.hasOwnProperty(eid)) {
13527                         continue;
13528                     }
13529                     t[eid] = EC[eid];
13530                 }
13531                 EC = Ext.cache = t;
13532             }
13533         }
13534     }
13535     El.collectorThreadId = setInterval(garbageCollect, 30000);
13536
13537     var flyFn = function() {};
13538     flyFn.prototype = El.prototype;
13539
13540     // dom is optional
13541     El.Flyweight = function(dom) {
13542         this.dom = dom;
13543     };
13544
13545     El.Flyweight.prototype = new flyFn();
13546     El.Flyweight.prototype.isFlyweight = true;
13547     El._flyweights = {};
13548
13549     /**
13550      * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
13551      * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
13552      * {@link Ext.Element#fly}.
13553      *
13554      * Use this to make one-time references to DOM elements which are not going to be accessed again either by
13555      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
13556      * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the Ext.Element
13557      * class.
13558      *
13559      * @param {String/HTMLElement} el The dom node or id
13560      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts (e.g.
13561      * internally Ext uses "_global")
13562      * @return {Ext.Element} The shared Element object (or null if no matching element was found)
13563      * @static
13564      */
13565     El.fly = function(el, named) {
13566         var ret = null;
13567         named = named || '_global';
13568         el = Ext.getDom(el);
13569         if (el) {
13570             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
13571             ret = El._flyweights[named];
13572         }
13573         return ret;
13574     };
13575
13576     /**
13577      * @member Ext
13578      * @method get
13579      * @alias Ext.Element#get
13580      */
13581     Ext.get = El.get;
13582
13583     /**
13584      * @member Ext
13585      * @method fly
13586      * @alias Ext.Element#fly
13587      */
13588     Ext.fly = El.fly;
13589
13590     // speedy lookup for elements never to box adjust
13591     var noBoxAdjust = Ext.isStrict ? {
13592         select: 1
13593     }: {
13594         input: 1,
13595         select: 1,
13596         textarea: 1
13597     };
13598     if (Ext.isIE || Ext.isGecko) {
13599         noBoxAdjust['button'] = 1;
13600     }
13601 })();
13602
13603 /**
13604  * @class Ext.Element
13605  */
13606 Ext.Element.addMethods({
13607     /**
13608      * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
13609      * @param {String} selector The simple selector to test
13610      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13611      * The max depth to search as a number or element (defaults to 50 || document.body)
13612      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
13613      * @return {HTMLElement} The matching DOM node (or null if no match was found)
13614      */
13615     findParent : function(simpleSelector, maxDepth, returnEl) {
13616         var p = this.dom,
13617             b = document.body,
13618             depth = 0,
13619             stopEl;
13620
13621         maxDepth = maxDepth || 50;
13622         if (isNaN(maxDepth)) {
13623             stopEl = Ext.getDom(maxDepth);
13624             maxDepth = Number.MAX_VALUE;
13625         }
13626         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
13627             if (Ext.DomQuery.is(p, simpleSelector)) {
13628                 return returnEl ? Ext.get(p) : p;
13629             }
13630             depth++;
13631             p = p.parentNode;
13632         }
13633         return null;
13634     },
13635
13636     /**
13637      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
13638      * @param {String} selector The simple selector to test
13639      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13640      * The max depth to search as a number or element (defaults to 10 || document.body)
13641      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
13642      * @return {HTMLElement} The matching DOM node (or null if no match was found)
13643      */
13644     findParentNode : function(simpleSelector, maxDepth, returnEl) {
13645         var p = Ext.fly(this.dom.parentNode, '_internal');
13646         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
13647     },
13648
13649     /**
13650      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
13651      * This is a shortcut for findParentNode() that always returns an Ext.Element.
13652      * @param {String} selector The simple selector to test
13653      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13654      * The max depth to search as a number or element (defaults to 10 || document.body)
13655      * @return {Ext.Element} The matching DOM node (or null if no match was found)
13656      */
13657     up : function(simpleSelector, maxDepth) {
13658         return this.findParentNode(simpleSelector, maxDepth, true);
13659     },
13660
13661     /**
13662      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
13663      * @param {String} selector The CSS selector
13664      * @return {Ext.CompositeElement/Ext.CompositeElement} The composite element
13665      */
13666     select : function(selector) {
13667         return Ext.Element.select(selector, false,  this.dom);
13668     },
13669
13670     /**
13671      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
13672      * @param {String} selector The CSS selector
13673      * @return {HTMLElement[]} An array of the matched nodes
13674      */
13675     query : function(selector) {
13676         return Ext.DomQuery.select(selector, this.dom);
13677     },
13678
13679     /**
13680      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
13681      * @param {String} selector The CSS selector
13682      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
13683      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
13684      */
13685     down : function(selector, returnDom) {
13686         var n = Ext.DomQuery.selectNode(selector, this.dom);
13687         return returnDom ? n : Ext.get(n);
13688     },
13689
13690     /**
13691      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
13692      * @param {String} selector The CSS selector
13693      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
13694      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
13695      */
13696     child : function(selector, returnDom) {
13697         var node,
13698             me = this,
13699             id;
13700         id = Ext.get(me).id;
13701         // Escape . or :
13702         id = id.replace(/[\.:]/g, "\\$0");
13703         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
13704         return returnDom ? node : Ext.get(node);
13705     },
13706
13707      /**
13708      * Gets the parent node for this element, optionally chaining up trying to match a selector
13709      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
13710      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13711      * @return {Ext.Element/HTMLElement} The parent node or null
13712      */
13713     parent : function(selector, returnDom) {
13714         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
13715     },
13716
13717      /**
13718      * Gets the next sibling, skipping text nodes
13719      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
13720      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13721      * @return {Ext.Element/HTMLElement} The next sibling or null
13722      */
13723     next : function(selector, returnDom) {
13724         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
13725     },
13726
13727     /**
13728      * Gets the previous sibling, skipping text nodes
13729      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
13730      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13731      * @return {Ext.Element/HTMLElement} The previous sibling or null
13732      */
13733     prev : function(selector, returnDom) {
13734         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
13735     },
13736
13737
13738     /**
13739      * Gets the first child, skipping text nodes
13740      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
13741      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13742      * @return {Ext.Element/HTMLElement} The first child or null
13743      */
13744     first : function(selector, returnDom) {
13745         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
13746     },
13747
13748     /**
13749      * Gets the last child, skipping text nodes
13750      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
13751      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13752      * @return {Ext.Element/HTMLElement} The last child or null
13753      */
13754     last : function(selector, returnDom) {
13755         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
13756     },
13757
13758     matchNode : function(dir, start, selector, returnDom) {
13759         if (!this.dom) {
13760             return null;
13761         }
13762
13763         var n = this.dom[start];
13764         while (n) {
13765             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
13766                 return !returnDom ? Ext.get(n) : n;
13767             }
13768             n = n[dir];
13769         }
13770         return null;
13771     }
13772 });
13773
13774 /**
13775  * @class Ext.Element
13776  */
13777 Ext.Element.addMethods({
13778     /**
13779      * Appends the passed element(s) to this element
13780      * @param {String/HTMLElement/Ext.Element} el
13781      * The id of the node, a DOM Node or an existing Element.
13782      * @return {Ext.Element} this
13783      */
13784     appendChild : function(el) {
13785         return Ext.get(el).appendTo(this);
13786     },
13787
13788     /**
13789      * Appends this element to the passed element
13790      * @param {String/HTMLElement/Ext.Element} el The new parent element.
13791      * The id of the node, a DOM Node or an existing Element.
13792      * @return {Ext.Element} this
13793      */
13794     appendTo : function(el) {
13795         Ext.getDom(el).appendChild(this.dom);
13796         return this;
13797     },
13798
13799     /**
13800      * Inserts this element before the passed element in the DOM
13801      * @param {String/HTMLElement/Ext.Element} el The element before which this element will be inserted.
13802      * The id of the node, a DOM Node or an existing Element.
13803      * @return {Ext.Element} this
13804      */
13805     insertBefore : function(el) {
13806         el = Ext.getDom(el);
13807         el.parentNode.insertBefore(this.dom, el);
13808         return this;
13809     },
13810
13811     /**
13812      * Inserts this element after the passed element in the DOM
13813      * @param {String/HTMLElement/Ext.Element} el The element to insert after.
13814      * The id of the node, a DOM Node or an existing Element.
13815      * @return {Ext.Element} this
13816      */
13817     insertAfter : function(el) {
13818         el = Ext.getDom(el);
13819         el.parentNode.insertBefore(this.dom, el.nextSibling);
13820         return this;
13821     },
13822
13823     /**
13824      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
13825      * @param {String/HTMLElement/Ext.Element/Object} el The id or element to insert or a DomHelper config
13826      * to create and insert
13827      * @return {Ext.Element} The new child
13828      */
13829     insertFirst : function(el, returnDom) {
13830         el = el || {};
13831         if (el.nodeType || el.dom || typeof el == 'string') { // element
13832             el = Ext.getDom(el);
13833             this.dom.insertBefore(el, this.dom.firstChild);
13834             return !returnDom ? Ext.get(el) : el;
13835         }
13836         else { // dh config
13837             return this.createChild(el, this.dom.firstChild, returnDom);
13838         }
13839     },
13840
13841     /**
13842      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
13843      * @param {String/HTMLElement/Ext.Element/Object/Array} el The id, element to insert or a DomHelper config
13844      * to create and insert *or* an array of any of those.
13845      * @param {String} where (optional) 'before' or 'after' defaults to before
13846      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.Element
13847      * @return {Ext.Element} The inserted Element. If an array is passed, the last inserted element is returned.
13848      */
13849     insertSibling: function(el, where, returnDom){
13850         var me = this, rt,
13851         isAfter = (where || 'before').toLowerCase() == 'after',
13852         insertEl;
13853
13854         if(Ext.isArray(el)){
13855             insertEl = me;
13856             Ext.each(el, function(e) {
13857                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
13858                 if(isAfter){
13859                     insertEl = rt;
13860                 }
13861             });
13862             return rt;
13863         }
13864
13865         el = el || {};
13866
13867         if(el.nodeType || el.dom){
13868             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
13869             if (!returnDom) {
13870                 rt = Ext.get(rt);
13871             }
13872         }else{
13873             if (isAfter && !me.dom.nextSibling) {
13874                 rt = Ext.DomHelper.append(me.dom.parentNode, el, !returnDom);
13875             } else {
13876                 rt = Ext.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
13877             }
13878         }
13879         return rt;
13880     },
13881
13882     /**
13883      * Replaces the passed element with this element
13884      * @param {String/HTMLElement/Ext.Element} el The element to replace.
13885      * The id of the node, a DOM Node or an existing Element.
13886      * @return {Ext.Element} this
13887      */
13888     replace : function(el) {
13889         el = Ext.get(el);
13890         this.insertBefore(el);
13891         el.remove();
13892         return this;
13893     },
13894     
13895     /**
13896      * Replaces this element with the passed element
13897      * @param {String/HTMLElement/Ext.Element/Object} el The new element (id of the node, a DOM Node
13898      * or an existing Element) or a DomHelper config of an element to create
13899      * @return {Ext.Element} this
13900      */
13901     replaceWith: function(el){
13902         var me = this;
13903             
13904         if(el.nodeType || el.dom || typeof el == 'string'){
13905             el = Ext.get(el);
13906             me.dom.parentNode.insertBefore(el, me.dom);
13907         }else{
13908             el = Ext.DomHelper.insertBefore(me.dom, el);
13909         }
13910         
13911         delete Ext.cache[me.id];
13912         Ext.removeNode(me.dom);      
13913         me.id = Ext.id(me.dom = el);
13914         Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);     
13915         return me;
13916     },
13917     
13918     /**
13919      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
13920      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
13921      * automatically generated with the specified attributes.
13922      * @param {HTMLElement} insertBefore (optional) a child element of this element
13923      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
13924      * @return {Ext.Element} The new child element
13925      */
13926     createChild : function(config, insertBefore, returnDom) {
13927         config = config || {tag:'div'};
13928         if (insertBefore) {
13929             return Ext.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
13930         }
13931         else {
13932             return Ext.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
13933         }
13934     },
13935
13936     /**
13937      * Creates and wraps this element with another element
13938      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
13939      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
13940      * @return {HTMLElement/Ext.Element} The newly created wrapper element
13941      */
13942     wrap : function(config, returnDom) {
13943         var newEl = Ext.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
13944             d = newEl.dom || newEl;
13945
13946         d.appendChild(this.dom);
13947         return newEl;
13948     },
13949
13950     /**
13951      * Inserts an html fragment into this element
13952      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
13953      * See {@link Ext.DomHelper#insertHtml} for details.
13954      * @param {String} html The HTML fragment
13955      * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
13956      * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
13957      */
13958     insertHtml : function(where, html, returnEl) {
13959         var el = Ext.DomHelper.insertHtml(where, this.dom, html);
13960         return returnEl ? Ext.get(el) : el;
13961     }
13962 });
13963
13964 /**
13965  * @class Ext.Element
13966  */
13967 (function(){
13968     // local style camelizing for speed
13969     var ELEMENT = Ext.Element,
13970         supports = Ext.supports,
13971         view = document.defaultView,
13972         opacityRe = /alpha\(opacity=(.*)\)/i,
13973         trimRe = /^\s+|\s+$/g,
13974         spacesRe = /\s+/,
13975         wordsRe = /\w/g,
13976         adjustDirect2DTableRe = /table-row|table-.*-group/,
13977         INTERNAL = '_internal',
13978         PADDING = 'padding',
13979         MARGIN = 'margin',
13980         BORDER = 'border',
13981         LEFT = '-left',
13982         RIGHT = '-right',
13983         TOP = '-top',
13984         BOTTOM = '-bottom',
13985         WIDTH = '-width',
13986         MATH = Math,
13987         HIDDEN = 'hidden',
13988         ISCLIPPED = 'isClipped',
13989         OVERFLOW = 'overflow',
13990         OVERFLOWX = 'overflow-x',
13991         OVERFLOWY = 'overflow-y',
13992         ORIGINALCLIP = 'originalClip',
13993         // special markup used throughout Ext when box wrapping elements
13994         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
13995         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
13996         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
13997         data = ELEMENT.data;
13998
13999     ELEMENT.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
14000
14001     // These property values are read from the parentNode if they cannot be read
14002     // from the child:
14003     ELEMENT.inheritedProps = {
14004         fontSize: 1,
14005         fontStyle: 1,
14006         opacity: 1
14007     };
14008
14009     Ext.override(ELEMENT, {
14010
14011         /**
14012          * TODO: Look at this
14013          */
14014         // private  ==> used by Fx
14015         adjustWidth : function(width) {
14016             var me = this,
14017                 isNum = (typeof width == 'number');
14018
14019             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14020                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14021             }
14022             return (isNum && width < 0) ? 0 : width;
14023         },
14024
14025         // private   ==> used by Fx
14026         adjustHeight : function(height) {
14027             var me = this,
14028                 isNum = (typeof height == "number");
14029
14030             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14031                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14032             }
14033             return (isNum && height < 0) ? 0 : height;
14034         },
14035
14036
14037         /**
14038          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
14039          * @param {String/String[]} className The CSS classes to add separated by space, or an array of classes
14040          * @return {Ext.Element} this
14041          */
14042         addCls : function(className){
14043             var me = this,
14044                 cls = [],
14045                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
14046                 i, len, v;
14047             if (className === undefined) {
14048                 return me;
14049             }
14050             // Separate case is for speed
14051             if (Object.prototype.toString.call(className) !== '[object Array]') {
14052                 if (typeof className === 'string') {
14053                     className = className.replace(trimRe, '').split(spacesRe);
14054                     if (className.length === 1) {
14055                         className = className[0];
14056                         if (!me.hasCls(className)) {
14057                             me.dom.className += space + className;
14058                         }
14059                     } else {
14060                         this.addCls(className);
14061                     }
14062                 }
14063             } else {
14064                 for (i = 0, len = className.length; i < len; i++) {
14065                     v = className[i];
14066                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
14067                         cls.push(v);
14068                     }
14069                 }
14070                 if (cls.length) {
14071                     me.dom.className += space + cls.join(" ");
14072                 }
14073             }
14074             return me;
14075         },
14076
14077         /**
14078          * Removes one or more CSS classes from the element.
14079          * @param {String/String[]} className The CSS classes to remove separated by space, or an array of classes
14080          * @return {Ext.Element} this
14081          */
14082         removeCls : function(className){
14083             var me = this,
14084                 i, idx, len, cls, elClasses;
14085             if (className === undefined) {
14086                 return me;
14087             }
14088             if (Object.prototype.toString.call(className) !== '[object Array]') {
14089                 className = className.replace(trimRe, '').split(spacesRe);
14090             }
14091             if (me.dom && me.dom.className) {
14092                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
14093                 for (i = 0, len = className.length; i < len; i++) {
14094                     cls = className[i];
14095                     if (typeof cls == 'string') {
14096                         cls = cls.replace(trimRe, '');
14097                         idx = Ext.Array.indexOf(elClasses, cls);
14098                         if (idx != -1) {
14099                             Ext.Array.erase(elClasses, idx, 1);
14100                         }
14101                     }
14102                 }
14103                 me.dom.className = elClasses.join(" ");
14104             }
14105             return me;
14106         },
14107
14108         /**
14109          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
14110          * @param {String/String[]} className The CSS class to add, or an array of classes
14111          * @return {Ext.Element} this
14112          */
14113         radioCls : function(className){
14114             var cn = this.dom.parentNode.childNodes,
14115                 v, i, len;
14116             className = Ext.isArray(className) ? className : [className];
14117             for (i = 0, len = cn.length; i < len; i++) {
14118                 v = cn[i];
14119                 if (v && v.nodeType == 1) {
14120                     Ext.fly(v, '_internal').removeCls(className);
14121                 }
14122             }
14123             return this.addCls(className);
14124         },
14125
14126         /**
14127          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
14128          * @param {String} className The CSS class to toggle
14129          * @return {Ext.Element} this
14130          * @method
14131          */
14132         toggleCls : Ext.supports.ClassList ?
14133             function(className) {
14134                 this.dom.classList.toggle(Ext.String.trim(className));
14135                 return this;
14136             } :
14137             function(className) {
14138                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
14139             },
14140
14141         /**
14142          * Checks if the specified CSS class exists on this element's DOM node.
14143          * @param {String} className The CSS class to check for
14144          * @return {Boolean} True if the class exists, else false
14145          * @method
14146          */
14147         hasCls : Ext.supports.ClassList ?
14148             function(className) {
14149                 if (!className) {
14150                     return false;
14151                 }
14152                 className = className.split(spacesRe);
14153                 var ln = className.length,
14154                     i = 0;
14155                 for (; i < ln; i++) {
14156                     if (className[i] && this.dom.classList.contains(className[i])) {
14157                         return true;
14158                     }
14159                 }
14160                 return false;
14161             } :
14162             function(className){
14163                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
14164             },
14165
14166         /**
14167          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
14168          * @param {String} oldClassName The CSS class to replace
14169          * @param {String} newClassName The replacement CSS class
14170          * @return {Ext.Element} this
14171          */
14172         replaceCls : function(oldClassName, newClassName){
14173             return this.removeCls(oldClassName).addCls(newClassName);
14174         },
14175
14176         isStyle : function(style, val) {
14177             return this.getStyle(style) == val;
14178         },
14179
14180         /**
14181          * Normalizes currentStyle and computedStyle.
14182          * @param {String} property The style property whose value is returned.
14183          * @return {String} The current value of the style property for this element.
14184          * @method
14185          */
14186         getStyle : function() {
14187             return view && view.getComputedStyle ?
14188                 function(prop){
14189                     var el = this.dom,
14190                         v, cs, out, display, cleaner;
14191
14192                     if(el == document){
14193                         return null;
14194                     }
14195                     prop = ELEMENT.normalize(prop);
14196                     out = (v = el.style[prop]) ? v :
14197                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
14198
14199                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
14200                     // numbers larger.
14201                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
14202                         cleaner = ELEMENT.getRightMarginFixCleaner(el);
14203                         display = this.getStyle('display');
14204                         el.style.display = 'inline-block';
14205                         out = view.getComputedStyle(el, '').marginRight;
14206                         el.style.display = display;
14207                         cleaner();
14208                     }
14209
14210                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
14211                         out = 'transparent';
14212                     }
14213                     return out;
14214                 } :
14215                 function (prop) {
14216                     var el = this.dom,
14217                         m, cs;
14218
14219                     if (el == document) {
14220                         return null;
14221                     }
14222                     prop = ELEMENT.normalize(prop);
14223
14224                     do {
14225                         if (prop == 'opacity') {
14226                             if (el.style.filter.match) {
14227                                 m = el.style.filter.match(opacityRe);
14228                                 if(m){
14229                                     var fv = parseFloat(m[1]);
14230                                     if(!isNaN(fv)){
14231                                         return fv ? fv / 100 : 0;
14232                                     }
14233                                 }
14234                             }
14235                             return 1;
14236                         }
14237
14238                         // the try statement does have a cost, so we avoid it unless we are
14239                         // on IE6
14240                         if (!Ext.isIE6) {
14241                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14242                         }
14243
14244                         try {
14245                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14246                         } catch (e) {
14247                             // in some cases, IE6 will throw Invalid Argument for properties
14248                             // like fontSize (see in /examples/tabs/tabs.html).
14249                         }
14250
14251                         if (!ELEMENT.inheritedProps[prop]) {
14252                             break;
14253                         }
14254
14255                         el = el.parentNode;
14256                         // this is _not_ perfect, but we can only hope that the style we
14257                         // need is inherited from a parentNode. If not and since IE won't
14258                         // give us the info we need, we are never going to be 100% right.
14259                     } while (el);
14260
14261                     return null;
14262                 }
14263         }(),
14264
14265         /**
14266          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
14267          * are convert to standard 6 digit hex color.
14268          * @param {String} attr The css attribute
14269          * @param {String} defaultValue The default value to use when a valid color isn't found
14270          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
14271          * color anims.
14272          */
14273         getColor : function(attr, defaultValue, prefix){
14274             var v = this.getStyle(attr),
14275                 color = prefix || prefix === '' ? prefix : '#',
14276                 h;
14277
14278             if(!v || (/transparent|inherit/.test(v))) {
14279                 return defaultValue;
14280             }
14281             if(/^r/.test(v)){
14282                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
14283                     h = parseInt(s, 10);
14284                     color += (h < 16 ? '0' : '') + h.toString(16);
14285                 });
14286             }else{
14287                 v = v.replace('#', '');
14288                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
14289             }
14290             return(color.length > 5 ? color.toLowerCase() : defaultValue);
14291         },
14292
14293         /**
14294          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
14295          * @param {String/Object} property The style property to be set, or an object of multiple styles.
14296          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
14297          * @return {Ext.Element} this
14298          */
14299         setStyle : function(prop, value){
14300             var me = this,
14301                 tmp, style;
14302
14303             if (!me.dom) {
14304                 return me;
14305             }
14306             if (typeof prop === 'string') {
14307                 tmp = {};
14308                 tmp[prop] = value;
14309                 prop = tmp;
14310             }
14311             for (style in prop) {
14312                 if (prop.hasOwnProperty(style)) {
14313                     value = Ext.value(prop[style], '');
14314                     if (style == 'opacity') {
14315                         me.setOpacity(value);
14316                     }
14317                     else {
14318                         me.dom.style[ELEMENT.normalize(style)] = value;
14319                     }
14320                 }
14321             }
14322             return me;
14323         },
14324
14325         /**
14326          * Set the opacity of the element
14327          * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
14328          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
14329          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
14330          * @return {Ext.Element} this
14331          */
14332         setOpacity: function(opacity, animate) {
14333             var me = this,
14334                 dom = me.dom,
14335                 val,
14336                 style;
14337
14338             if (!me.dom) {
14339                 return me;
14340             }
14341
14342             style = me.dom.style;
14343
14344             if (!animate || !me.anim) {
14345                 if (!Ext.supports.Opacity) {
14346                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
14347                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
14348
14349                     style.zoom = 1;
14350                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
14351                 }
14352                 else {
14353                     style.opacity = opacity;
14354                 }
14355             }
14356             else {
14357                 if (!Ext.isObject(animate)) {
14358                     animate = {
14359                         duration: 350,
14360                         easing: 'ease-in'
14361                     };
14362                 }
14363                 me.animate(Ext.applyIf({
14364                     to: {
14365                         opacity: opacity
14366                     }
14367                 },
14368                 animate));
14369             }
14370             return me;
14371         },
14372
14373
14374         /**
14375          * Clears any opacity settings from this element. Required in some cases for IE.
14376          * @return {Ext.Element} this
14377          */
14378         clearOpacity : function(){
14379             var style = this.dom.style;
14380             if(!Ext.supports.Opacity){
14381                 if(!Ext.isEmpty(style.filter)){
14382                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
14383                 }
14384             }else{
14385                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
14386             }
14387             return this;
14388         },
14389
14390         /**
14391          * @private
14392          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
14393          * @return {Number} 0 or 1
14394          */
14395         adjustDirect2DDimension: function(dimension) {
14396             var me = this,
14397                 dom = me.dom,
14398                 display = me.getStyle('display'),
14399                 inlineDisplay = dom.style['display'],
14400                 inlinePosition = dom.style['position'],
14401                 originIndex = dimension === 'width' ? 0 : 1,
14402                 floating;
14403
14404             if (display === 'inline') {
14405                 dom.style['display'] = 'inline-block';
14406             }
14407
14408             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
14409
14410             // floating will contain digits that appears after the decimal point
14411             // if height or width are set to auto we fallback to msTransformOrigin calculation
14412             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
14413
14414             dom.style['position'] = inlinePosition;
14415
14416             if (display === 'inline') {
14417                 dom.style['display'] = inlineDisplay;
14418             }
14419
14420             return floating;
14421         },
14422
14423         /**
14424          * Returns the offset height of the element
14425          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
14426          * @return {Number} The element's height
14427          */
14428         getHeight: function(contentHeight, preciseHeight) {
14429             var me = this,
14430                 dom = me.dom,
14431                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14432                 height, overflow, style, floating;
14433
14434             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14435             // We will put the overflow back to it's original value when we are done measuring.
14436             if (Ext.isIEQuirks) {
14437                 style = dom.style;
14438                 overflow = style.overflow;
14439                 me.setStyle({ overflow: 'hidden'});
14440             }
14441
14442             height = dom.offsetHeight;
14443
14444             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
14445
14446             // IE9 Direct2D dimension rounding bug
14447             if (!hidden && Ext.supports.Direct2DBug) {
14448                 floating = me.adjustDirect2DDimension('height');
14449                 if (preciseHeight) {
14450                     height += floating;
14451                 }
14452                 else if (floating > 0 && floating < 0.5) {
14453                     height++;
14454                 }
14455             }
14456
14457             if (contentHeight) {
14458                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14459             }
14460
14461             if (Ext.isIEQuirks) {
14462                 me.setStyle({ overflow: overflow});
14463             }
14464
14465             if (height < 0) {
14466                 height = 0;
14467             }
14468             return height;
14469         },
14470
14471         /**
14472          * Returns the offset width of the element
14473          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
14474          * @return {Number} The element's width
14475          */
14476         getWidth: function(contentWidth, preciseWidth) {
14477             var me = this,
14478                 dom = me.dom,
14479                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14480                 rect, width, overflow, style, floating, parentPosition;
14481
14482             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14483             // We will put the overflow back to it's original value when we are done measuring.
14484             if (Ext.isIEQuirks) {
14485                 style = dom.style;
14486                 overflow = style.overflow;
14487                 me.setStyle({overflow: 'hidden'});
14488             }
14489
14490             // Fix Opera 10.5x width calculation issues
14491             if (Ext.isOpera10_5) {
14492                 if (dom.parentNode.currentStyle.position === 'relative') {
14493                     parentPosition = dom.parentNode.style.position;
14494                     dom.parentNode.style.position = 'static';
14495                     width = dom.offsetWidth;
14496                     dom.parentNode.style.position = parentPosition;
14497                 }
14498                 width = Math.max(width || 0, dom.offsetWidth);
14499
14500             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
14501             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
14502             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
14503             // subpixel measurements so we can force them to always be rounded up. See
14504             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
14505             } else if (Ext.supports.BoundingClientRect) {
14506                 rect = dom.getBoundingClientRect();
14507                 width = rect.right - rect.left;
14508                 width = preciseWidth ? width : Math.ceil(width);
14509             } else {
14510                 width = dom.offsetWidth;
14511             }
14512
14513             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
14514
14515             // IE9 Direct2D dimension rounding bug
14516             if (!hidden && Ext.supports.Direct2DBug) {
14517                 floating = me.adjustDirect2DDimension('width');
14518                 if (preciseWidth) {
14519                     width += floating;
14520                 }
14521                 else if (floating > 0 && floating < 0.5) {
14522                     width++;
14523                 }
14524             }
14525
14526             if (contentWidth) {
14527                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14528             }
14529
14530             if (Ext.isIEQuirks) {
14531                 me.setStyle({ overflow: overflow});
14532             }
14533
14534             if (width < 0) {
14535                 width = 0;
14536             }
14537             return width;
14538         },
14539
14540         /**
14541          * Set the width of this Element.
14542          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
14543          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14544          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14545          * </ul></div>
14546          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14547          * @return {Ext.Element} this
14548          */
14549         setWidth : function(width, animate){
14550             var me = this;
14551             width = me.adjustWidth(width);
14552             if (!animate || !me.anim) {
14553                 me.dom.style.width = me.addUnits(width);
14554             }
14555             else {
14556                 if (!Ext.isObject(animate)) {
14557                     animate = {};
14558                 }
14559                 me.animate(Ext.applyIf({
14560                     to: {
14561                         width: width
14562                     }
14563                 }, animate));
14564             }
14565             return me;
14566         },
14567
14568         /**
14569          * Set the height of this Element.
14570          * <pre><code>
14571 // change the height to 200px and animate with default configuration
14572 Ext.fly('elementId').setHeight(200, true);
14573
14574 // change the height to 150px and animate with a custom configuration
14575 Ext.fly('elId').setHeight(150, {
14576     duration : .5, // animation will have a duration of .5 seconds
14577     // will change the content to "finished"
14578     callback: function(){ this.{@link #update}("finished"); }
14579 });
14580          * </code></pre>
14581          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
14582          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
14583          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
14584          * </ul></div>
14585          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14586          * @return {Ext.Element} this
14587          */
14588          setHeight : function(height, animate){
14589             var me = this;
14590             height = me.adjustHeight(height);
14591             if (!animate || !me.anim) {
14592                 me.dom.style.height = me.addUnits(height);
14593             }
14594             else {
14595                 if (!Ext.isObject(animate)) {
14596                     animate = {};
14597                 }
14598                 me.animate(Ext.applyIf({
14599                     to: {
14600                         height: height
14601                     }
14602                 }, animate));
14603             }
14604             return me;
14605         },
14606
14607         /**
14608          * Gets the width of the border(s) for the specified side(s)
14609          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
14610          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
14611          * @return {Number} The width of the sides passed added together
14612          */
14613         getBorderWidth : function(side){
14614             return this.addStyles(side, borders);
14615         },
14616
14617         /**
14618          * Gets the width of the padding(s) for the specified side(s)
14619          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
14620          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
14621          * @return {Number} The padding of the sides passed added together
14622          */
14623         getPadding : function(side){
14624             return this.addStyles(side, paddings);
14625         },
14626
14627         /**
14628          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
14629          * @return {Ext.Element} this
14630          */
14631         clip : function(){
14632             var me = this,
14633                 dom = me.dom;
14634
14635             if(!data(dom, ISCLIPPED)){
14636                 data(dom, ISCLIPPED, true);
14637                 data(dom, ORIGINALCLIP, {
14638                     o: me.getStyle(OVERFLOW),
14639                     x: me.getStyle(OVERFLOWX),
14640                     y: me.getStyle(OVERFLOWY)
14641                 });
14642                 me.setStyle(OVERFLOW, HIDDEN);
14643                 me.setStyle(OVERFLOWX, HIDDEN);
14644                 me.setStyle(OVERFLOWY, HIDDEN);
14645             }
14646             return me;
14647         },
14648
14649         /**
14650          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
14651          * @return {Ext.Element} this
14652          */
14653         unclip : function(){
14654             var me = this,
14655                 dom = me.dom,
14656                 clip;
14657
14658             if(data(dom, ISCLIPPED)){
14659                 data(dom, ISCLIPPED, false);
14660                 clip = data(dom, ORIGINALCLIP);
14661                 if(clip.o){
14662                     me.setStyle(OVERFLOW, clip.o);
14663                 }
14664                 if(clip.x){
14665                     me.setStyle(OVERFLOWX, clip.x);
14666                 }
14667                 if(clip.y){
14668                     me.setStyle(OVERFLOWY, clip.y);
14669                 }
14670             }
14671             return me;
14672         },
14673
14674         // private
14675         addStyles : function(sides, styles){
14676             var totalSize = 0,
14677                 sidesArr = sides.match(wordsRe),
14678                 i = 0,
14679                 len = sidesArr.length,
14680                 side, size;
14681             for (; i < len; i++) {
14682                 side = sidesArr[i];
14683                 size = side && parseInt(this.getStyle(styles[side]), 10);
14684                 if (size) {
14685                     totalSize += MATH.abs(size);
14686                 }
14687             }
14688             return totalSize;
14689         },
14690
14691         margins : margins,
14692
14693         /**
14694          * More flexible version of {@link #setStyle} for setting style properties.
14695          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
14696          * a function which returns such a specification.
14697          * @return {Ext.Element} this
14698          */
14699         applyStyles : function(style){
14700             Ext.DomHelper.applyStyles(this.dom, style);
14701             return this;
14702         },
14703
14704         /**
14705          * Returns an object with properties matching the styles requested.
14706          * For example, el.getStyles('color', 'font-size', 'width') might return
14707          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
14708          * @param {String} style1 A style name
14709          * @param {String} style2 A style name
14710          * @param {String} etc.
14711          * @return {Object} The style object
14712          */
14713         getStyles : function(){
14714             var styles = {},
14715                 len = arguments.length,
14716                 i = 0, style;
14717
14718             for(; i < len; ++i) {
14719                 style = arguments[i];
14720                 styles[style] = this.getStyle(style);
14721             }
14722             return styles;
14723         },
14724
14725        /**
14726         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
14727         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
14728         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
14729         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
14730         * is of this form:</p>
14731         * <pre><code>
14732     Ext.Element.boxMarkup =
14733     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
14734      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
14735      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
14736         * </code></pre>
14737         * <p>Example usage:</p>
14738         * <pre><code>
14739     // Basic box wrap
14740     Ext.get("foo").boxWrap();
14741
14742     // You can also add a custom class and use CSS inheritance rules to customize the box look.
14743     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
14744     // for how to create a custom box wrap style.
14745     Ext.get("foo").boxWrap().addCls("x-box-blue");
14746         * </code></pre>
14747         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
14748         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
14749         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
14750         * also supply all of the necessary rules.
14751         * @return {Ext.Element} The outermost wrapping element of the created box structure.
14752         */
14753         boxWrap : function(cls){
14754             cls = cls || Ext.baseCSSPrefix + 'box';
14755             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(ELEMENT.boxMarkup, cls) + "</div>"));
14756             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
14757             return el;
14758         },
14759
14760         /**
14761          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
14762          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
14763          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14764          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14765          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
14766          * </ul></div>
14767          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
14768          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14769          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
14770          * </ul></div>
14771          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14772          * @return {Ext.Element} this
14773          */
14774         setSize : function(width, height, animate){
14775             var me = this;
14776             if (Ext.isObject(width)) { // in case of object from getSize()
14777                 animate = height;
14778                 height = width.height;
14779                 width = width.width;
14780             }
14781             width = me.adjustWidth(width);
14782             height = me.adjustHeight(height);
14783             if(!animate || !me.anim){
14784                 // Must touch some property before setting style.width/height on non-quirk IE6,7, or the
14785                 // properties will not reflect the changes on the style immediately
14786                 if (!Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7)) {
14787                     me.dom.offsetTop;
14788                 }
14789                 me.dom.style.width = me.addUnits(width);
14790                 me.dom.style.height = me.addUnits(height);
14791             }
14792             else {
14793                 if (animate === true) {
14794                     animate = {};
14795                 }
14796                 me.animate(Ext.applyIf({
14797                     to: {
14798                         width: width,
14799                         height: height
14800                     }
14801                 }, animate));
14802             }
14803             return me;
14804         },
14805
14806         /**
14807          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
14808          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
14809          * if a height has not been set using CSS.
14810          * @return {Number}
14811          */
14812         getComputedHeight : function(){
14813             var me = this,
14814                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
14815             if(!h){
14816                 h = parseFloat(me.getStyle('height')) || 0;
14817                 if(!me.isBorderBox()){
14818                     h += me.getFrameWidth('tb');
14819                 }
14820             }
14821             return h;
14822         },
14823
14824         /**
14825          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
14826          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
14827          * if a width has not been set using CSS.
14828          * @return {Number}
14829          */
14830         getComputedWidth : function(){
14831             var me = this,
14832                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
14833
14834             if(!w){
14835                 w = parseFloat(me.getStyle('width')) || 0;
14836                 if(!me.isBorderBox()){
14837                     w += me.getFrameWidth('lr');
14838                 }
14839             }
14840             return w;
14841         },
14842
14843         /**
14844          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
14845          for more information about the sides.
14846          * @param {String} sides
14847          * @return {Number}
14848          */
14849         getFrameWidth : function(sides, onlyContentBox){
14850             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
14851         },
14852
14853         /**
14854          * Sets up event handlers to add and remove a css class when the mouse is over this element
14855          * @param {String} className
14856          * @return {Ext.Element} this
14857          */
14858         addClsOnOver : function(className){
14859             var dom = this.dom;
14860             this.hover(
14861                 function(){
14862                     Ext.fly(dom, INTERNAL).addCls(className);
14863                 },
14864                 function(){
14865                     Ext.fly(dom, INTERNAL).removeCls(className);
14866                 }
14867             );
14868             return this;
14869         },
14870
14871         /**
14872          * Sets up event handlers to add and remove a css class when this element has the focus
14873          * @param {String} className
14874          * @return {Ext.Element} this
14875          */
14876         addClsOnFocus : function(className){
14877             var me = this,
14878                 dom = me.dom;
14879             me.on("focus", function(){
14880                 Ext.fly(dom, INTERNAL).addCls(className);
14881             });
14882             me.on("blur", function(){
14883                 Ext.fly(dom, INTERNAL).removeCls(className);
14884             });
14885             return me;
14886         },
14887
14888         /**
14889          * Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
14890          * @param {String} className
14891          * @return {Ext.Element} this
14892          */
14893         addClsOnClick : function(className){
14894             var dom = this.dom;
14895             this.on("mousedown", function(){
14896                 Ext.fly(dom, INTERNAL).addCls(className);
14897                 var d = Ext.getDoc(),
14898                     fn = function(){
14899                         Ext.fly(dom, INTERNAL).removeCls(className);
14900                         d.removeListener("mouseup", fn);
14901                     };
14902                 d.on("mouseup", fn);
14903             });
14904             return this;
14905         },
14906
14907         /**
14908          * <p>Returns the dimensions of the element available to lay content out in.<p>
14909          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
14910          * example:<pre><code>
14911         var vpSize = Ext.getBody().getViewSize();
14912
14913         // all Windows created afterwards will have a default value of 90% height and 95% width
14914         Ext.Window.override({
14915             width: vpSize.width * 0.9,
14916             height: vpSize.height * 0.95
14917         });
14918         // To handle window resizing you would have to hook onto onWindowResize.
14919         * </code></pre>
14920         *
14921         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
14922         * To obtain the size including scrollbars, use getStyleSize
14923         *
14924         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14925         */
14926
14927         getViewSize : function(){
14928             var me = this,
14929                 dom = me.dom,
14930                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
14931                 style, overflow, ret;
14932
14933             // If the body, use static methods
14934             if (isDoc) {
14935                 ret = {
14936                     width : ELEMENT.getViewWidth(),
14937                     height : ELEMENT.getViewHeight()
14938                 };
14939
14940             // Else use clientHeight/clientWidth
14941             }
14942             else {
14943                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14944                 // We will put the overflow back to it's original value when we are done measuring.
14945                 if (Ext.isIE6 || Ext.isIEQuirks) {
14946                     style = dom.style;
14947                     overflow = style.overflow;
14948                     me.setStyle({ overflow: 'hidden'});
14949                 }
14950                 ret = {
14951                     width : dom.clientWidth,
14952                     height : dom.clientHeight
14953                 };
14954                 if (Ext.isIE6 || Ext.isIEQuirks) {
14955                     me.setStyle({ overflow: overflow });
14956                 }
14957             }
14958             return ret;
14959         },
14960
14961         /**
14962         * <p>Returns the dimensions of the element available to lay content out in.<p>
14963         *
14964         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
14965         * To obtain the size excluding scrollbars, use getViewSize
14966         *
14967         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14968         */
14969
14970         getStyleSize : function(){
14971             var me = this,
14972                 doc = document,
14973                 d = this.dom,
14974                 isDoc = (d == doc || d == doc.body),
14975                 s = d.style,
14976                 w, h;
14977
14978             // If the body, use static methods
14979             if (isDoc) {
14980                 return {
14981                     width : ELEMENT.getViewWidth(),
14982                     height : ELEMENT.getViewHeight()
14983                 };
14984             }
14985             // Use Styles if they are set
14986             if(s.width && s.width != 'auto'){
14987                 w = parseFloat(s.width);
14988                 if(me.isBorderBox()){
14989                    w -= me.getFrameWidth('lr');
14990                 }
14991             }
14992             // Use Styles if they are set
14993             if(s.height && s.height != 'auto'){
14994                 h = parseFloat(s.height);
14995                 if(me.isBorderBox()){
14996                    h -= me.getFrameWidth('tb');
14997                 }
14998             }
14999             // Use getWidth/getHeight if style not set.
15000             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
15001         },
15002
15003         /**
15004          * Returns the size of the element.
15005          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
15006          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
15007          */
15008         getSize : function(contentSize){
15009             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
15010         },
15011
15012         /**
15013          * Forces the browser to repaint this element
15014          * @return {Ext.Element} this
15015          */
15016         repaint : function(){
15017             var dom = this.dom;
15018             this.addCls(Ext.baseCSSPrefix + 'repaint');
15019             setTimeout(function(){
15020                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
15021             }, 1);
15022             return this;
15023         },
15024
15025         /**
15026          * Enable text selection for this element (normalized across browsers)
15027          * @return {Ext.Element} this
15028          */
15029         selectable : function() {
15030             var me = this;
15031             me.dom.unselectable = "off";
15032             // Prevent it from bubles up and enables it to be selectable
15033             me.on('selectstart', function (e) {
15034                 e.stopPropagation();
15035                 return true;
15036             });
15037             me.applyStyles("-moz-user-select: text; -khtml-user-select: text;");
15038             me.removeCls(Ext.baseCSSPrefix + 'unselectable');
15039             return me;
15040         },
15041
15042         /**
15043          * Disables text selection for this element (normalized across browsers)
15044          * @return {Ext.Element} this
15045          */
15046         unselectable : function(){
15047             var me = this;
15048             me.dom.unselectable = "on";
15049
15050             me.swallowEvent("selectstart", true);
15051             me.applyStyles("-moz-user-select:-moz-none;-khtml-user-select:none;");
15052             me.addCls(Ext.baseCSSPrefix + 'unselectable');
15053
15054             return me;
15055         },
15056
15057         /**
15058          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
15059          * then it returns the calculated width of the sides (see getPadding)
15060          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
15061          * @return {Object/Number}
15062          */
15063         getMargin : function(side){
15064             var me = this,
15065                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
15066                 o = {},
15067                 key;
15068
15069             if (!side) {
15070                 for (key in me.margins){
15071                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
15072                 }
15073                 return o;
15074             } else {
15075                 return me.addStyles.call(me, side, me.margins);
15076             }
15077         }
15078     });
15079 })();
15080 /**
15081  * @class Ext.Element
15082  */
15083 /**
15084  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
15085  * @static
15086  * @type Number
15087  */
15088 Ext.Element.VISIBILITY = 1;
15089 /**
15090  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
15091  * @static
15092  * @type Number
15093  */
15094 Ext.Element.DISPLAY = 2;
15095
15096 /**
15097  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
15098  * to hide element.
15099  * @static
15100  * @type Number
15101  */
15102 Ext.Element.OFFSETS = 3;
15103
15104
15105 Ext.Element.ASCLASS = 4;
15106
15107 /**
15108  * Defaults to 'x-hide-nosize'
15109  * @static
15110  * @type String
15111  */
15112 Ext.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
15113
15114 Ext.Element.addMethods(function(){
15115     var El = Ext.Element,
15116         OPACITY = "opacity",
15117         VISIBILITY = "visibility",
15118         DISPLAY = "display",
15119         HIDDEN = "hidden",
15120         OFFSETS = "offsets",
15121         ASCLASS = "asclass",
15122         NONE = "none",
15123         NOSIZE = 'nosize',
15124         ORIGINALDISPLAY = 'originalDisplay',
15125         VISMODE = 'visibilityMode',
15126         ISVISIBLE = 'isVisible',
15127         data = El.data,
15128         getDisplay = function(dom){
15129             var d = data(dom, ORIGINALDISPLAY);
15130             if(d === undefined){
15131                 data(dom, ORIGINALDISPLAY, d = '');
15132             }
15133             return d;
15134         },
15135         getVisMode = function(dom){
15136             var m = data(dom, VISMODE);
15137             if(m === undefined){
15138                 data(dom, VISMODE, m = 1);
15139             }
15140             return m;
15141         };
15142
15143     return {
15144         /**
15145          * @property {String} originalDisplay
15146          * The element's default display mode
15147          */
15148         originalDisplay : "",
15149         visibilityMode : 1,
15150
15151         /**
15152          * Sets the element's visibility mode. When setVisible() is called it
15153          * will use this to determine whether to set the visibility or the display property.
15154          * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
15155          * @return {Ext.Element} this
15156          */
15157         setVisibilityMode : function(visMode){
15158             data(this.dom, VISMODE, visMode);
15159             return this;
15160         },
15161
15162         /**
15163          * Checks whether the element is currently visible using both visibility and display properties.
15164          * @return {Boolean} True if the element is currently visible, else false
15165          */
15166         isVisible : function() {
15167             var me = this,
15168                 dom = me.dom,
15169                 visible = data(dom, ISVISIBLE);
15170
15171             if(typeof visible == 'boolean'){ //return the cached value if registered
15172                 return visible;
15173             }
15174             //Determine the current state based on display states
15175             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
15176                       !me.isStyle(DISPLAY, NONE) &&
15177                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
15178
15179             data(dom, ISVISIBLE, visible);
15180             return visible;
15181         },
15182
15183         /**
15184          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
15185          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
15186          * @param {Boolean} visible Whether the element is visible
15187          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15188          * @return {Ext.Element} this
15189          */
15190         setVisible : function(visible, animate){
15191             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
15192                 dom = me.dom,
15193                 visMode = getVisMode(dom);
15194
15195
15196             // hideMode string override
15197             if (typeof animate == 'string'){
15198                 switch (animate) {
15199                     case DISPLAY:
15200                         visMode = El.DISPLAY;
15201                         break;
15202                     case VISIBILITY:
15203                         visMode = El.VISIBILITY;
15204                         break;
15205                     case OFFSETS:
15206                         visMode = El.OFFSETS;
15207                         break;
15208                     case NOSIZE:
15209                     case ASCLASS:
15210                         visMode = El.ASCLASS;
15211                         break;
15212                 }
15213                 me.setVisibilityMode(visMode);
15214                 animate = false;
15215             }
15216
15217             if (!animate || !me.anim) {
15218                 if(visMode == El.ASCLASS ){
15219
15220                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
15221
15222                 } else if (visMode == El.DISPLAY){
15223
15224                     return me.setDisplayed(visible);
15225
15226                 } else if (visMode == El.OFFSETS){
15227
15228                     if (!visible){
15229                         // Remember position for restoring, if we are not already hidden by offsets.
15230                         if (!me.hideModeStyles) {
15231                             me.hideModeStyles = {
15232                                 position: me.getStyle('position'),
15233                                 top: me.getStyle('top'),
15234                                 left: me.getStyle('left')
15235                             };
15236                         }
15237                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
15238                     }
15239
15240                     // Only "restore" as position if we have actually been hidden using offsets.
15241                     // Calling setVisible(true) on a positioned element should not reposition it.
15242                     else if (me.hideModeStyles) {
15243                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
15244                         delete me.hideModeStyles;
15245                     }
15246
15247                 }else{
15248                     me.fixDisplay();
15249                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
15250                     dom.style.visibility = visible ? '' : HIDDEN;
15251                 }
15252             }else{
15253                 // closure for composites
15254                 if(visible){
15255                     me.setOpacity(0.01);
15256                     me.setVisible(true);
15257                 }
15258                 if (!Ext.isObject(animate)) {
15259                     animate = {
15260                         duration: 350,
15261                         easing: 'ease-in'
15262                     };
15263                 }
15264                 me.animate(Ext.applyIf({
15265                     callback: function() {
15266                         visible || me.setVisible(false).setOpacity(1);
15267                     },
15268                     to: {
15269                         opacity: (visible) ? 1 : 0
15270                     }
15271                 }, animate));
15272             }
15273             data(dom, ISVISIBLE, visible);  //set logical visibility state
15274             return me;
15275         },
15276
15277
15278         /**
15279          * @private
15280          * Determine if the Element has a relevant height and width available based
15281          * upon current logical visibility state
15282          */
15283         hasMetrics  : function(){
15284             var dom = this.dom;
15285             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
15286         },
15287
15288         /**
15289          * Toggles the element's visibility or display, depending on visibility mode.
15290          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15291          * @return {Ext.Element} this
15292          */
15293         toggle : function(animate){
15294             var me = this;
15295             me.setVisible(!me.isVisible(), me.anim(animate));
15296             return me;
15297         },
15298
15299         /**
15300          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
15301          * @param {Boolean/String} value Boolean value to display the element using its default display, or a string to set the display directly.
15302          * @return {Ext.Element} this
15303          */
15304         setDisplayed : function(value) {
15305             if(typeof value == "boolean"){
15306                value = value ? getDisplay(this.dom) : NONE;
15307             }
15308             this.setStyle(DISPLAY, value);
15309             return this;
15310         },
15311
15312         // private
15313         fixDisplay : function(){
15314             var me = this;
15315             if (me.isStyle(DISPLAY, NONE)) {
15316                 me.setStyle(VISIBILITY, HIDDEN);
15317                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
15318                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
15319                     me.setStyle(DISPLAY, "block");
15320                 }
15321             }
15322         },
15323
15324         /**
15325          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15326          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15327          * @return {Ext.Element} this
15328          */
15329         hide : function(animate){
15330             // hideMode override
15331             if (typeof animate == 'string'){
15332                 this.setVisible(false, animate);
15333                 return this;
15334             }
15335             this.setVisible(false, this.anim(animate));
15336             return this;
15337         },
15338
15339         /**
15340         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15341         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15342          * @return {Ext.Element} this
15343          */
15344         show : function(animate){
15345             // hideMode override
15346             if (typeof animate == 'string'){
15347                 this.setVisible(true, animate);
15348                 return this;
15349             }
15350             this.setVisible(true, this.anim(animate));
15351             return this;
15352         }
15353     };
15354 }());
15355 /**
15356  * @class Ext.Element
15357  */
15358 Ext.applyIf(Ext.Element.prototype, {
15359     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15360     animate: function(config) {
15361         var me = this;
15362         if (!me.id) {
15363             me = Ext.get(me.dom);
15364         }
15365         if (Ext.fx.Manager.hasFxBlock(me.id)) {
15366             return me;
15367         }
15368         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
15369         return this;
15370     },
15371
15372     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15373     anim: function(config) {
15374         if (!Ext.isObject(config)) {
15375             return (config) ? {} : false;
15376         }
15377
15378         var me = this,
15379             duration = config.duration || Ext.fx.Anim.prototype.duration,
15380             easing = config.easing || 'ease',
15381             animConfig;
15382
15383         if (config.stopAnimation) {
15384             me.stopAnimation();
15385         }
15386
15387         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
15388
15389         // Clear any 'paused' defaults.
15390         Ext.fx.Manager.setFxDefaults(me.id, {
15391             delay: 0
15392         });
15393
15394         animConfig = {
15395             target: me,
15396             remove: config.remove,
15397             alternate: config.alternate || false,
15398             duration: duration,
15399             easing: easing,
15400             callback: config.callback,
15401             listeners: config.listeners,
15402             iterations: config.iterations || 1,
15403             scope: config.scope,
15404             block: config.block,
15405             concurrent: config.concurrent,
15406             delay: config.delay || 0,
15407             paused: true,
15408             keyframes: config.keyframes,
15409             from: config.from || {},
15410             to: Ext.apply({}, config)
15411         };
15412         Ext.apply(animConfig.to, config.to);
15413
15414         // Anim API properties - backward compat
15415         delete animConfig.to.to;
15416         delete animConfig.to.from;
15417         delete animConfig.to.remove;
15418         delete animConfig.to.alternate;
15419         delete animConfig.to.keyframes;
15420         delete animConfig.to.iterations;
15421         delete animConfig.to.listeners;
15422         delete animConfig.to.target;
15423         delete animConfig.to.paused;
15424         delete animConfig.to.callback;
15425         delete animConfig.to.scope;
15426         delete animConfig.to.duration;
15427         delete animConfig.to.easing;
15428         delete animConfig.to.concurrent;
15429         delete animConfig.to.block;
15430         delete animConfig.to.stopAnimation;
15431         delete animConfig.to.delay;
15432         return animConfig;
15433     },
15434
15435     /**
15436      * Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide
15437      * effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15438      * Fx class overview for valid anchor point options. Usage:
15439      *
15440      *     // default: slide the element in from the top
15441      *     el.slideIn();
15442      *
15443      *     // custom: slide the element in from the right with a 2-second duration
15444      *     el.slideIn('r', { duration: 2000 });
15445      *
15446      *     // common config options shown with default values
15447      *     el.slideIn('t', {
15448      *         easing: 'easeOut',
15449      *         duration: 500
15450      *     });
15451      *
15452      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15453      * @param {Object} [options] Object literal with any of the Fx config options
15454      * @return {Ext.Element} The Element
15455      */
15456     slideIn: function(anchor, obj, slideOut) {
15457         var me = this,
15458             elStyle = me.dom.style,
15459             beforeAnim, wrapAnim;
15460
15461         anchor = anchor || "t";
15462         obj = obj || {};
15463
15464         beforeAnim = function() {
15465             var animScope = this,
15466                 listeners = obj.listeners,
15467                 box, position, restoreSize, wrap, anim;
15468
15469             if (!slideOut) {
15470                 me.fixDisplay();
15471             }
15472
15473             box = me.getBox();
15474             if ((anchor == 't' || anchor == 'b') && box.height === 0) {
15475                 box.height = me.dom.scrollHeight;
15476             }
15477             else if ((anchor == 'l' || anchor == 'r') && box.width === 0) {
15478                 box.width = me.dom.scrollWidth;
15479             }
15480
15481             position = me.getPositioning();
15482             me.setSize(box.width, box.height);
15483
15484             wrap = me.wrap({
15485                 style: {
15486                     visibility: slideOut ? 'visible' : 'hidden'
15487                 }
15488             });
15489             wrap.setPositioning(position);
15490             if (wrap.isStyle('position', 'static')) {
15491                 wrap.position('relative');
15492             }
15493             me.clearPositioning('auto');
15494             wrap.clip();
15495
15496             // This element is temporarily positioned absolute within its wrapper.
15497             // Restore to its default, CSS-inherited visibility setting.
15498             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
15499             me.setStyle({
15500                 visibility: '',
15501                 position: 'absolute'
15502             });
15503             if (slideOut) {
15504                 wrap.setSize(box.width, box.height);
15505             }
15506
15507             switch (anchor) {
15508                 case 't':
15509                     anim = {
15510                         from: {
15511                             width: box.width + 'px',
15512                             height: '0px'
15513                         },
15514                         to: {
15515                             width: box.width + 'px',
15516                             height: box.height + 'px'
15517                         }
15518                     };
15519                     elStyle.bottom = '0px';
15520                     break;
15521                 case 'l':
15522                     anim = {
15523                         from: {
15524                             width: '0px',
15525                             height: box.height + 'px'
15526                         },
15527                         to: {
15528                             width: box.width + 'px',
15529                             height: box.height + 'px'
15530                         }
15531                     };
15532                     elStyle.right = '0px';
15533                     break;
15534                 case 'r':
15535                     anim = {
15536                         from: {
15537                             x: box.x + box.width,
15538                             width: '0px',
15539                             height: box.height + 'px'
15540                         },
15541                         to: {
15542                             x: box.x,
15543                             width: box.width + 'px',
15544                             height: box.height + 'px'
15545                         }
15546                     };
15547                     break;
15548                 case 'b':
15549                     anim = {
15550                         from: {
15551                             y: box.y + box.height,
15552                             width: box.width + 'px',
15553                             height: '0px'
15554                         },
15555                         to: {
15556                             y: box.y,
15557                             width: box.width + 'px',
15558                             height: box.height + 'px'
15559                         }
15560                     };
15561                     break;
15562                 case 'tl':
15563                     anim = {
15564                         from: {
15565                             x: box.x,
15566                             y: box.y,
15567                             width: '0px',
15568                             height: '0px'
15569                         },
15570                         to: {
15571                             width: box.width + 'px',
15572                             height: box.height + 'px'
15573                         }
15574                     };
15575                     elStyle.bottom = '0px';
15576                     elStyle.right = '0px';
15577                     break;
15578                 case 'bl':
15579                     anim = {
15580                         from: {
15581                             x: box.x + box.width,
15582                             width: '0px',
15583                             height: '0px'
15584                         },
15585                         to: {
15586                             x: box.x,
15587                             width: box.width + 'px',
15588                             height: box.height + 'px'
15589                         }
15590                     };
15591                     elStyle.right = '0px';
15592                     break;
15593                 case 'br':
15594                     anim = {
15595                         from: {
15596                             x: box.x + box.width,
15597                             y: box.y + box.height,
15598                             width: '0px',
15599                             height: '0px'
15600                         },
15601                         to: {
15602                             x: box.x,
15603                             y: box.y,
15604                             width: box.width + 'px',
15605                             height: box.height + 'px'
15606                         }
15607                     };
15608                     break;
15609                 case 'tr':
15610                     anim = {
15611                         from: {
15612                             y: box.y + box.height,
15613                             width: '0px',
15614                             height: '0px'
15615                         },
15616                         to: {
15617                             y: box.y,
15618                             width: box.width + 'px',
15619                             height: box.height + 'px'
15620                         }
15621                     };
15622                     elStyle.bottom = '0px';
15623                     break;
15624             }
15625
15626             wrap.show();
15627             wrapAnim = Ext.apply({}, obj);
15628             delete wrapAnim.listeners;
15629             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
15630                 target: wrap,
15631                 duration: 500,
15632                 easing: 'ease-out',
15633                 from: slideOut ? anim.to : anim.from,
15634                 to: slideOut ? anim.from : anim.to
15635             }));
15636
15637             // In the absence of a callback, this listener MUST be added first
15638             wrapAnim.on('afteranimate', function() {
15639                 if (slideOut) {
15640                     me.setPositioning(position);
15641                     if (obj.useDisplay) {
15642                         me.setDisplayed(false);
15643                     } else {
15644                         me.hide();
15645                     }
15646                 }
15647                 else {
15648                     me.clearPositioning();
15649                     me.setPositioning(position);
15650                 }
15651                 if (wrap.dom) {
15652                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
15653                     wrap.remove();
15654                 }
15655                 me.setSize(box.width, box.height);
15656                 animScope.end();
15657             });
15658             // Add configured listeners after
15659             if (listeners) {
15660                 wrapAnim.on(listeners);
15661             }
15662         };
15663
15664         me.animate({
15665             duration: obj.duration ? obj.duration * 2 : 1000,
15666             listeners: {
15667                 beforeanimate: {
15668                     fn: beforeAnim
15669                 },
15670                 afteranimate: {
15671                     fn: function() {
15672                         if (wrapAnim && wrapAnim.running) {
15673                             wrapAnim.end();
15674                         }
15675                     }
15676                 }
15677             }
15678         });
15679         return me;
15680     },
15681
15682
15683     /**
15684      * Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide
15685      * effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will
15686      * still take up space in the document. The element must be removed from the DOM using the 'remove' config option if
15687      * desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15688      * Fx class overview for valid anchor point options. Usage:
15689      *
15690      *     // default: slide the element out to the top
15691      *     el.slideOut();
15692      *
15693      *     // custom: slide the element out to the right with a 2-second duration
15694      *     el.slideOut('r', { duration: 2000 });
15695      *
15696      *     // common config options shown with default values
15697      *     el.slideOut('t', {
15698      *         easing: 'easeOut',
15699      *         duration: 500,
15700      *         remove: false,
15701      *         useDisplay: false
15702      *     });
15703      *
15704      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15705      * @param {Object} [options] Object literal with any of the Fx config options
15706      * @return {Ext.Element} The Element
15707      */
15708     slideOut: function(anchor, o) {
15709         return this.slideIn(anchor, o, true);
15710     },
15711
15712     /**
15713      * Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will
15714      * be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage:
15715      *
15716      *     // default
15717      *     el.puff();
15718      *
15719      *     // common config options shown with default values
15720      *     el.puff({
15721      *         easing: 'easeOut',
15722      *         duration: 500,
15723      *         useDisplay: false
15724      *     });
15725      *
15726      * @param {Object} options (optional) Object literal with any of the Fx config options
15727      * @return {Ext.Element} The Element
15728      */
15729     puff: function(obj) {
15730         var me = this,
15731             beforeAnim;
15732         obj = Ext.applyIf(obj || {}, {
15733             easing: 'ease-out',
15734             duration: 500,
15735             useDisplay: false
15736         });
15737
15738         beforeAnim = function() {
15739             me.clearOpacity();
15740             me.show();
15741
15742             var box = me.getBox(),
15743                 fontSize = me.getStyle('fontSize'),
15744                 position = me.getPositioning();
15745             this.to = {
15746                 width: box.width * 2,
15747                 height: box.height * 2,
15748                 x: box.x - (box.width / 2),
15749                 y: box.y - (box.height /2),
15750                 opacity: 0,
15751                 fontSize: '200%'
15752             };
15753             this.on('afteranimate',function() {
15754                 if (me.dom) {
15755                     if (obj.useDisplay) {
15756                         me.setDisplayed(false);
15757                     } else {
15758                         me.hide();
15759                     }
15760                     me.clearOpacity();
15761                     me.setPositioning(position);
15762                     me.setStyle({fontSize: fontSize});
15763                 }
15764             });
15765         };
15766
15767         me.animate({
15768             duration: obj.duration,
15769             easing: obj.easing,
15770             listeners: {
15771                 beforeanimate: {
15772                     fn: beforeAnim
15773                 }
15774             }
15775         });
15776         return me;
15777     },
15778
15779     /**
15780      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
15781      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
15782      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if
15783      * desired. Usage:
15784      *
15785      *     // default
15786      *     el.switchOff();
15787      *
15788      *     // all config options shown with default values
15789      *     el.switchOff({
15790      *         easing: 'easeIn',
15791      *         duration: .3,
15792      *         remove: false,
15793      *         useDisplay: false
15794      *     });
15795      *
15796      * @param {Object} options (optional) Object literal with any of the Fx config options
15797      * @return {Ext.Element} The Element
15798      */
15799     switchOff: function(obj) {
15800         var me = this,
15801             beforeAnim;
15802
15803         obj = Ext.applyIf(obj || {}, {
15804             easing: 'ease-in',
15805             duration: 500,
15806             remove: false,
15807             useDisplay: false
15808         });
15809
15810         beforeAnim = function() {
15811             var animScope = this,
15812                 size = me.getSize(),
15813                 xy = me.getXY(),
15814                 keyframe, position;
15815             me.clearOpacity();
15816             me.clip();
15817             position = me.getPositioning();
15818
15819             keyframe = Ext.create('Ext.fx.Animator', {
15820                 target: me,
15821                 duration: obj.duration,
15822                 easing: obj.easing,
15823                 keyframes: {
15824                     33: {
15825                         opacity: 0.3
15826                     },
15827                     66: {
15828                         height: 1,
15829                         y: xy[1] + size.height / 2
15830                     },
15831                     100: {
15832                         width: 1,
15833                         x: xy[0] + size.width / 2
15834                     }
15835                 }
15836             });
15837             keyframe.on('afteranimate', function() {
15838                 if (obj.useDisplay) {
15839                     me.setDisplayed(false);
15840                 } else {
15841                     me.hide();
15842                 }
15843                 me.clearOpacity();
15844                 me.setPositioning(position);
15845                 me.setSize(size);
15846                 animScope.end();
15847             });
15848         };
15849         me.animate({
15850             duration: (obj.duration * 2),
15851             listeners: {
15852                 beforeanimate: {
15853                     fn: beforeAnim
15854                 }
15855             }
15856         });
15857         return me;
15858     },
15859
15860     /**
15861      * Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage:
15862      *
15863      *     // default: a single light blue ripple
15864      *     el.frame();
15865      *
15866      *     // custom: 3 red ripples lasting 3 seconds total
15867      *     el.frame("#ff0000", 3, { duration: 3 });
15868      *
15869      *     // common config options shown with default values
15870      *     el.frame("#C3DAF9", 1, {
15871      *         duration: 1 //duration of each individual ripple.
15872      *         // Note: Easing is not configurable and will be ignored if included
15873      *     });
15874      *
15875      * @param {String} [color='C3DAF9'] The color of the border. Should be a 6 char hex color without the leading #
15876      * (defaults to light blue).
15877      * @param {Number} [count=1] The number of ripples to display
15878      * @param {Object} [options] Object literal with any of the Fx config options
15879      * @return {Ext.Element} The Element
15880      */
15881     frame : function(color, count, obj){
15882         var me = this,
15883             beforeAnim;
15884
15885         color = color || '#C3DAF9';
15886         count = count || 1;
15887         obj = obj || {};
15888
15889         beforeAnim = function() {
15890             me.show();
15891             var animScope = this,
15892                 box = me.getBox(),
15893                 proxy = Ext.getBody().createChild({
15894                     style: {
15895                         position : 'absolute',
15896                         'pointer-events': 'none',
15897                         'z-index': 35000,
15898                         border : '0px solid ' + color
15899                     }
15900                 }),
15901                 proxyAnim;
15902             proxyAnim = Ext.create('Ext.fx.Anim', {
15903                 target: proxy,
15904                 duration: obj.duration || 1000,
15905                 iterations: count,
15906                 from: {
15907                     top: box.y,
15908                     left: box.x,
15909                     borderWidth: 0,
15910                     opacity: 1,
15911                     height: box.height,
15912                     width: box.width
15913                 },
15914                 to: {
15915                     top: box.y - 20,
15916                     left: box.x - 20,
15917                     borderWidth: 10,
15918                     opacity: 0,
15919                     height: box.height + 40,
15920                     width: box.width + 40
15921                 }
15922             });
15923             proxyAnim.on('afteranimate', function() {
15924                 proxy.remove();
15925                 animScope.end();
15926             });
15927         };
15928
15929         me.animate({
15930             duration: (obj.duration * 2) || 2000,
15931             listeners: {
15932                 beforeanimate: {
15933                     fn: beforeAnim
15934                 }
15935             }
15936         });
15937         return me;
15938     },
15939
15940     /**
15941      * Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point
15942      * of the effect. Usage:
15943      *
15944      *     // default: slide the element downward while fading out
15945      *     el.ghost();
15946      *
15947      *     // custom: slide the element out to the right with a 2-second duration
15948      *     el.ghost('r', { duration: 2000 });
15949      *
15950      *     // common config options shown with default values
15951      *     el.ghost('b', {
15952      *         easing: 'easeOut',
15953      *         duration: 500
15954      *     });
15955      *
15956      * @param {String} [anchor='b'] One of the valid Fx anchor positions
15957      * @param {Object} [options] Object literal with any of the Fx config options
15958      * @return {Ext.Element} The Element
15959      */
15960     ghost: function(anchor, obj) {
15961         var me = this,
15962             beforeAnim;
15963
15964         anchor = anchor || "b";
15965         beforeAnim = function() {
15966             var width = me.getWidth(),
15967                 height = me.getHeight(),
15968                 xy = me.getXY(),
15969                 position = me.getPositioning(),
15970                 to = {
15971                     opacity: 0
15972                 };
15973             switch (anchor) {
15974                 case 't':
15975                     to.y = xy[1] - height;
15976                     break;
15977                 case 'l':
15978                     to.x = xy[0] - width;
15979                     break;
15980                 case 'r':
15981                     to.x = xy[0] + width;
15982                     break;
15983                 case 'b':
15984                     to.y = xy[1] + height;
15985                     break;
15986                 case 'tl':
15987                     to.x = xy[0] - width;
15988                     to.y = xy[1] - height;
15989                     break;
15990                 case 'bl':
15991                     to.x = xy[0] - width;
15992                     to.y = xy[1] + height;
15993                     break;
15994                 case 'br':
15995                     to.x = xy[0] + width;
15996                     to.y = xy[1] + height;
15997                     break;
15998                 case 'tr':
15999                     to.x = xy[0] + width;
16000                     to.y = xy[1] - height;
16001                     break;
16002             }
16003             this.to = to;
16004             this.on('afteranimate', function () {
16005                 if (me.dom) {
16006                     me.hide();
16007                     me.clearOpacity();
16008                     me.setPositioning(position);
16009                 }
16010             });
16011         };
16012
16013         me.animate(Ext.applyIf(obj || {}, {
16014             duration: 500,
16015             easing: 'ease-out',
16016             listeners: {
16017                 beforeanimate: {
16018                     fn: beforeAnim
16019                 }
16020             }
16021         }));
16022         return me;
16023     },
16024
16025     /**
16026      * Highlights the Element by setting a color (applies to the background-color by default, but can be changed using
16027      * the "attr" config option) and then fading back to the original color. If no original color is available, you
16028      * should provide the "endColor" config option which will be cleared after the animation. Usage:
16029      *
16030      *     // default: highlight background to yellow
16031      *     el.highlight();
16032      *
16033      *     // custom: highlight foreground text to blue for 2 seconds
16034      *     el.highlight("0000ff", { attr: 'color', duration: 2000 });
16035      *
16036      *     // common config options shown with default values
16037      *     el.highlight("ffff9c", {
16038      *         attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
16039      *         endColor: (current color) or "ffffff",
16040      *         easing: 'easeIn',
16041      *         duration: 1000
16042      *     });
16043      *
16044      * @param {String} [color='ffff9c'] The highlight color. Should be a 6 char hex color without the leading #
16045      * @param {Object} [options] Object literal with any of the Fx config options
16046      * @return {Ext.Element} The Element
16047      */
16048     highlight: function(color, o) {
16049         var me = this,
16050             dom = me.dom,
16051             from = {},
16052             restore, to, attr, lns, event, fn;
16053
16054         o = o || {};
16055         lns = o.listeners || {};
16056         attr = o.attr || 'backgroundColor';
16057         from[attr] = color || 'ffff9c';
16058
16059         if (!o.to) {
16060             to = {};
16061             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
16062         }
16063         else {
16064             to = o.to;
16065         }
16066
16067         // Don't apply directly on lns, since we reference it in our own callbacks below
16068         o.listeners = Ext.apply(Ext.apply({}, lns), {
16069             beforeanimate: function() {
16070                 restore = dom.style[attr];
16071                 me.clearOpacity();
16072                 me.show();
16073
16074                 event = lns.beforeanimate;
16075                 if (event) {
16076                     fn = event.fn || event;
16077                     return fn.apply(event.scope || lns.scope || window, arguments);
16078                 }
16079             },
16080             afteranimate: function() {
16081                 if (dom) {
16082                     dom.style[attr] = restore;
16083                 }
16084
16085                 event = lns.afteranimate;
16086                 if (event) {
16087                     fn = event.fn || event;
16088                     fn.apply(event.scope || lns.scope || window, arguments);
16089                 }
16090             }
16091         });
16092
16093         me.animate(Ext.apply({}, o, {
16094             duration: 1000,
16095             easing: 'ease-in',
16096             from: from,
16097             to: to
16098         }));
16099         return me;
16100     },
16101
16102    /**
16103     * @deprecated 4.0
16104     * Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will
16105     * have no effect. Usage:
16106     *
16107     *     el.pause(1);
16108     *
16109     * @param {Number} seconds The length of time to pause (in seconds)
16110     * @return {Ext.Element} The Element
16111     */
16112     pause: function(ms) {
16113         var me = this;
16114         Ext.fx.Manager.setFxDefaults(me.id, {
16115             delay: ms
16116         });
16117         return me;
16118     },
16119
16120     /**
16121      * Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity`
16122      * config option. Usage:
16123      *
16124      *     // default: fade in from opacity 0 to 100%
16125      *     el.fadeIn();
16126      *
16127      *     // custom: fade in from opacity 0 to 75% over 2 seconds
16128      *     el.fadeIn({ opacity: .75, duration: 2000});
16129      *
16130      *     // common config options shown with default values
16131      *     el.fadeIn({
16132      *         opacity: 1, //can be any value between 0 and 1 (e.g. .5)
16133      *         easing: 'easeOut',
16134      *         duration: 500
16135      *     });
16136      *
16137      * @param {Object} options (optional) Object literal with any of the Fx config options
16138      * @return {Ext.Element} The Element
16139      */
16140     fadeIn: function(o) {
16141         this.animate(Ext.apply({}, o, {
16142             opacity: 1
16143         }));
16144         return this;
16145     },
16146
16147     /**
16148      * Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity`
16149      * config option. Note that IE may require `useDisplay:true` in order to redisplay correctly.
16150      * Usage:
16151      *
16152      *     // default: fade out from the element's current opacity to 0
16153      *     el.fadeOut();
16154      *
16155      *     // custom: fade out from the element's current opacity to 25% over 2 seconds
16156      *     el.fadeOut({ opacity: .25, duration: 2000});
16157      *
16158      *     // common config options shown with default values
16159      *     el.fadeOut({
16160      *         opacity: 0, //can be any value between 0 and 1 (e.g. .5)
16161      *         easing: 'easeOut',
16162      *         duration: 500,
16163      *         remove: false,
16164      *         useDisplay: false
16165      *     });
16166      *
16167      * @param {Object} options (optional) Object literal with any of the Fx config options
16168      * @return {Ext.Element} The Element
16169      */
16170     fadeOut: function(o) {
16171         this.animate(Ext.apply({}, o, {
16172             opacity: 0
16173         }));
16174         return this;
16175     },
16176
16177     /**
16178      * @deprecated 4.0
16179      * Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This
16180      * method is a convenience implementation of {@link #shift}. Usage:
16181      *
16182      *     // change height and width to 100x100 pixels
16183      *     el.scale(100, 100);
16184      *
16185      *     // common config options shown with default values.  The height and width will default to
16186      *     // the element's existing values if passed as null.
16187      *     el.scale(
16188      *         [element's width],
16189      *         [element's height], {
16190      *             easing: 'easeOut',
16191      *             duration: .35
16192      *         }
16193      *     );
16194      *
16195      * @param {Number} width The new width (pass undefined to keep the original width)
16196      * @param {Number} height The new height (pass undefined to keep the original height)
16197      * @param {Object} options (optional) Object literal with any of the Fx config options
16198      * @return {Ext.Element} The Element
16199      */
16200     scale: function(w, h, o) {
16201         this.animate(Ext.apply({}, o, {
16202             width: w,
16203             height: h
16204         }));
16205         return this;
16206     },
16207
16208     /**
16209      * @deprecated 4.0
16210      * Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these
16211      * properties not specified in the config object will not be changed. This effect requires that at least one new
16212      * dimension, position or opacity setting must be passed in on the config object in order for the function to have
16213      * any effect. Usage:
16214      *
16215      *     // slide the element horizontally to x position 200 while changing the height and opacity
16216      *     el.shift({ x: 200, height: 50, opacity: .8 });
16217      *
16218      *     // common config options shown with default values.
16219      *     el.shift({
16220      *         width: [element's width],
16221      *         height: [element's height],
16222      *         x: [element's x position],
16223      *         y: [element's y position],
16224      *         opacity: [element's opacity],
16225      *         easing: 'easeOut',
16226      *         duration: .35
16227      *     });
16228      *
16229      * @param {Object} options Object literal with any of the Fx config options
16230      * @return {Ext.Element} The Element
16231      */
16232     shift: function(config) {
16233         this.animate(config);
16234         return this;
16235     }
16236 });
16237
16238 /**
16239  * @class Ext.Element
16240  */
16241 Ext.applyIf(Ext.Element, {
16242     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
16243     camelRe: /(-[a-z])/gi,
16244     opacityRe: /alpha\(opacity=(.*)\)/i,
16245     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
16246     propertyCache: {},
16247     defaultUnit : "px",
16248     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
16249     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
16250     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
16251
16252     // Reference the prototype's version of the method. Signatures are identical.
16253     addUnits : Ext.Element.prototype.addUnits,
16254
16255     /**
16256      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16257      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16258      * @static
16259      * @param {Number/String} box The encoded margins
16260      * @return {Object} An object with margin sizes for top, right, bottom and left
16261      */
16262     parseBox : function(box) {
16263         if (Ext.isObject(box)) {
16264             return {
16265                 top: box.top || 0,
16266                 right: box.right || 0,
16267                 bottom: box.bottom || 0,
16268                 left: box.left || 0
16269             };
16270         } else {
16271             if (typeof box != 'string') {
16272                 box = box.toString();
16273             }
16274             var parts  = box.split(' '),
16275                 ln = parts.length;
16276     
16277             if (ln == 1) {
16278                 parts[1] = parts[2] = parts[3] = parts[0];
16279             }
16280             else if (ln == 2) {
16281                 parts[2] = parts[0];
16282                 parts[3] = parts[1];
16283             }
16284             else if (ln == 3) {
16285                 parts[3] = parts[1];
16286             }
16287     
16288             return {
16289                 top   :parseFloat(parts[0]) || 0,
16290                 right :parseFloat(parts[1]) || 0,
16291                 bottom:parseFloat(parts[2]) || 0,
16292                 left  :parseFloat(parts[3]) || 0
16293             };
16294         }
16295         
16296     },
16297     
16298     /**
16299      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16300      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16301      * @static
16302      * @param {Number/String} box The encoded margins
16303      * @param {String} units The type of units to add
16304      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
16305      */
16306     unitizeBox : function(box, units) {
16307         var A = this.addUnits,
16308             B = this.parseBox(box);
16309             
16310         return A(B.top, units) + ' ' +
16311                A(B.right, units) + ' ' +
16312                A(B.bottom, units) + ' ' +
16313                A(B.left, units);
16314         
16315     },
16316
16317     // private
16318     camelReplaceFn : function(m, a) {
16319         return a.charAt(1).toUpperCase();
16320     },
16321
16322     /**
16323      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
16324      * For example:
16325      * <ul>
16326      *  <li>border-width -> borderWidth</li>
16327      *  <li>padding-top -> paddingTop</li>
16328      * </ul>
16329      * @static
16330      * @param {String} prop The property to normalize
16331      * @return {String} The normalized string
16332      */
16333     normalize : function(prop) {
16334         if (prop == 'float') {
16335             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
16336         }
16337         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
16338     },
16339
16340     /**
16341      * Retrieves the document height
16342      * @static
16343      * @return {Number} documentHeight
16344      */
16345     getDocumentHeight: function() {
16346         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
16347     },
16348
16349     /**
16350      * Retrieves the document width
16351      * @static
16352      * @return {Number} documentWidth
16353      */
16354     getDocumentWidth: function() {
16355         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
16356     },
16357
16358     /**
16359      * Retrieves the viewport height of the window.
16360      * @static
16361      * @return {Number} viewportHeight
16362      */
16363     getViewportHeight: function(){
16364         return window.innerHeight;
16365     },
16366
16367     /**
16368      * Retrieves the viewport width of the window.
16369      * @static
16370      * @return {Number} viewportWidth
16371      */
16372     getViewportWidth : function() {
16373         return window.innerWidth;
16374     },
16375
16376     /**
16377      * Retrieves the viewport size of the window.
16378      * @static
16379      * @return {Object} object containing width and height properties
16380      */
16381     getViewSize : function() {
16382         return {
16383             width: window.innerWidth,
16384             height: window.innerHeight
16385         };
16386     },
16387
16388     /**
16389      * Retrieves the current orientation of the window. This is calculated by
16390      * determing if the height is greater than the width.
16391      * @static
16392      * @return {String} Orientation of window: 'portrait' or 'landscape'
16393      */
16394     getOrientation : function() {
16395         if (Ext.supports.OrientationChange) {
16396             return (window.orientation == 0) ? 'portrait' : 'landscape';
16397         }
16398         
16399         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
16400     },
16401
16402     /** 
16403      * Returns the top Element that is located at the passed coordinates
16404      * @static
16405      * @param {Number} x The x coordinate
16406      * @param {Number} y The y coordinate
16407      * @return {String} The found Element
16408      */
16409     fromPoint: function(x, y) {
16410         return Ext.get(document.elementFromPoint(x, y));
16411     },
16412     
16413     /**
16414      * Converts a CSS string into an object with a property for each style.
16415      * <p>
16416      * The sample code below would return an object with 2 properties, one
16417      * for background-color and one for color.</p>
16418      * <pre><code>
16419 var css = 'background-color: red;color: blue; ';
16420 console.log(Ext.Element.parseStyles(css));
16421      * </code></pre>
16422      * @static
16423      * @param {String} styles A CSS string
16424      * @return {Object} styles
16425      */
16426     parseStyles: function(styles){
16427         var out = {},
16428             cssRe = this.cssRe,
16429             matches;
16430             
16431         if (styles) {
16432             // Since we're using the g flag on the regex, we need to set the lastIndex.
16433             // This automatically happens on some implementations, but not others, see:
16434             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
16435             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
16436             cssRe.lastIndex = 0;
16437             while ((matches = cssRe.exec(styles))) {
16438                 out[matches[1]] = matches[2];
16439             }
16440         }
16441         return out;
16442     }
16443 });
16444
16445 /**
16446  * @class Ext.CompositeElementLite
16447  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
16448  * members, or to perform collective actions upon the whole set.</p>
16449  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
16450  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
16451  * Example:<pre><code>
16452 var els = Ext.select("#some-el div.some-class");
16453 // or select directly from an existing element
16454 var el = Ext.get('some-el');
16455 el.select('div.some-class');
16456
16457 els.setWidth(100); // all elements become 100 width
16458 els.hide(true); // all elements fade out and hide
16459 // or
16460 els.setWidth(100).hide(true);
16461 </code></pre>
16462  */
16463 Ext.CompositeElementLite = function(els, root){
16464     /**
16465      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
16466      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
16467      * to augment the capabilities of the CompositeElementLite class may use it when adding
16468      * methods to the class.</p>
16469      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
16470      * following siblings of selected elements, the code would be</p><code><pre>
16471 Ext.override(Ext.CompositeElementLite, {
16472     nextAll: function() {
16473         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
16474
16475 //      Loop through all elements in this Composite, accumulating
16476 //      an Array of all siblings.
16477         for (i = 0; i < l; i++) {
16478             for (n = els[i].nextSibling; n; n = n.nextSibling) {
16479                 r[++ri] = n;
16480             }
16481         }
16482
16483 //      Add all found siblings to this Composite
16484         return this.add(r);
16485     }
16486 });</pre></code>
16487      * @property {HTMLElement} elements
16488      */
16489     this.elements = [];
16490     this.add(els, root);
16491     this.el = new Ext.Element.Flyweight();
16492 };
16493
16494 Ext.CompositeElementLite.prototype = {
16495     isComposite: true,
16496
16497     // private
16498     getElement : function(el){
16499         // Set the shared flyweight dom property to the current element
16500         var e = this.el;
16501         e.dom = el;
16502         e.id = el.id;
16503         return e;
16504     },
16505
16506     // private
16507     transformElement : function(el){
16508         return Ext.getDom(el);
16509     },
16510
16511     /**
16512      * Returns the number of elements in this Composite.
16513      * @return Number
16514      */
16515     getCount : function(){
16516         return this.elements.length;
16517     },
16518     /**
16519      * Adds elements to this Composite object.
16520      * @param {HTMLElement[]/Ext.CompositeElement} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
16521      * @return {Ext.CompositeElement} This Composite object.
16522      */
16523     add : function(els, root){
16524         var me = this,
16525             elements = me.elements;
16526         if(!els){
16527             return this;
16528         }
16529         if(typeof els == "string"){
16530             els = Ext.Element.selectorFunction(els, root);
16531         }else if(els.isComposite){
16532             els = els.elements;
16533         }else if(!Ext.isIterable(els)){
16534             els = [els];
16535         }
16536
16537         for(var i = 0, len = els.length; i < len; ++i){
16538             elements.push(me.transformElement(els[i]));
16539         }
16540         return me;
16541     },
16542
16543     invoke : function(fn, args){
16544         var me = this,
16545             els = me.elements,
16546             len = els.length,
16547             e,
16548             i;
16549
16550         for(i = 0; i < len; i++) {
16551             e = els[i];
16552             if(e){
16553                 Ext.Element.prototype[fn].apply(me.getElement(e), args);
16554             }
16555         }
16556         return me;
16557     },
16558     /**
16559      * Returns a flyweight Element of the dom element object at the specified index
16560      * @param {Number} index
16561      * @return {Ext.Element}
16562      */
16563     item : function(index){
16564         var me = this,
16565             el = me.elements[index],
16566             out = null;
16567
16568         if(el){
16569             out = me.getElement(el);
16570         }
16571         return out;
16572     },
16573
16574     // fixes scope with flyweight
16575     addListener : function(eventName, handler, scope, opt){
16576         var els = this.elements,
16577             len = els.length,
16578             i, e;
16579
16580         for(i = 0; i<len; i++) {
16581             e = els[i];
16582             if(e) {
16583                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
16584             }
16585         }
16586         return this;
16587     },
16588     /**
16589      * <p>Calls the passed function for each element in this composite.</p>
16590      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
16591      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
16592      * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
16593      * a reference to the dom node, use el.dom.</b></div></li>
16594      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
16595      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
16596      * </ul>
16597      * @param {Object} [scope] The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
16598      * @return {Ext.CompositeElement} this
16599      */
16600     each : function(fn, scope){
16601         var me = this,
16602             els = me.elements,
16603             len = els.length,
16604             i, e;
16605
16606         for(i = 0; i<len; i++) {
16607             e = els[i];
16608             if(e){
16609                 e = this.getElement(e);
16610                 if(fn.call(scope || e, e, me, i) === false){
16611                     break;
16612                 }
16613             }
16614         }
16615         return me;
16616     },
16617
16618     /**
16619     * Clears this Composite and adds the elements passed.
16620     * @param {HTMLElement[]/Ext.CompositeElement} els Either an array of DOM elements, or another Composite from which to fill this Composite.
16621     * @return {Ext.CompositeElement} this
16622     */
16623     fill : function(els){
16624         var me = this;
16625         me.elements = [];
16626         me.add(els);
16627         return me;
16628     },
16629
16630     /**
16631      * Filters this composite to only elements that match the passed selector.
16632      * @param {String/Function} selector A string CSS selector or a comparison function.
16633      * The comparison function will be called with the following arguments:<ul>
16634      * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
16635      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
16636      * </ul>
16637      * @return {Ext.CompositeElement} this
16638      */
16639     filter : function(selector){
16640         var els = [],
16641             me = this,
16642             fn = Ext.isFunction(selector) ? selector
16643                 : function(el){
16644                     return el.is(selector);
16645                 };
16646
16647         me.each(function(el, self, i) {
16648             if (fn(el, i) !== false) {
16649                 els[els.length] = me.transformElement(el);
16650             }
16651         });
16652
16653         me.elements = els;
16654         return me;
16655     },
16656
16657     /**
16658      * Find the index of the passed element within the composite collection.
16659      * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
16660      * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
16661      */
16662     indexOf : function(el){
16663         return Ext.Array.indexOf(this.elements, this.transformElement(el));
16664     },
16665
16666     /**
16667     * Replaces the specified element with the passed element.
16668     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
16669     * to replace.
16670     * @param {String/Ext.Element} replacement The id of an element or the Element itself.
16671     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
16672     * @return {Ext.CompositeElement} this
16673     */
16674     replaceElement : function(el, replacement, domReplace){
16675         var index = !isNaN(el) ? el : this.indexOf(el),
16676             d;
16677         if(index > -1){
16678             replacement = Ext.getDom(replacement);
16679             if(domReplace){
16680                 d = this.elements[index];
16681                 d.parentNode.insertBefore(replacement, d);
16682                 Ext.removeNode(d);
16683             }
16684             Ext.Array.splice(this.elements, index, 1, replacement);
16685         }
16686         return this;
16687     },
16688
16689     /**
16690      * Removes all elements.
16691      */
16692     clear : function(){
16693         this.elements = [];
16694     }
16695 };
16696
16697 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
16698
16699 /**
16700  * @private
16701  * Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
16702  * This is called twice - once immediately below, and once again after additional Ext.Element
16703  * are added in Ext JS
16704  */
16705 Ext.CompositeElementLite.importElementMethods = function() {
16706     var fnName,
16707         ElProto = Ext.Element.prototype,
16708         CelProto = Ext.CompositeElementLite.prototype;
16709
16710     for (fnName in ElProto) {
16711         if (typeof ElProto[fnName] == 'function'){
16712             (function(fnName) {
16713                 CelProto[fnName] = CelProto[fnName] || function() {
16714                     return this.invoke(fnName, arguments);
16715                 };
16716             }).call(CelProto, fnName);
16717
16718         }
16719     }
16720 };
16721
16722 Ext.CompositeElementLite.importElementMethods();
16723
16724 if(Ext.DomQuery){
16725     Ext.Element.selectorFunction = Ext.DomQuery.select;
16726 }
16727
16728 /**
16729  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
16730  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16731  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16732  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
16733  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16734  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
16735  * @member Ext.Element
16736  * @method select
16737  */
16738 Ext.Element.select = function(selector, root){
16739     var els;
16740     if(typeof selector == "string"){
16741         els = Ext.Element.selectorFunction(selector, root);
16742     }else if(selector.length !== undefined){
16743         els = selector;
16744     }else{
16745     }
16746     return new Ext.CompositeElementLite(els);
16747 };
16748 /**
16749  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
16750  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16751  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16752  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
16753  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16754  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
16755  * @member Ext
16756  * @method select
16757  */
16758 Ext.select = Ext.Element.select;
16759
16760 /**
16761  * @class Ext.util.DelayedTask
16762  * 
16763  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
16764  * performing setTimeout where a new timeout cancels the old timeout. When called, the
16765  * task will wait the specified time period before executing. If durng that time period,
16766  * the task is called again, the original call will be cancelled. This continues so that
16767  * the function is only called a single time for each iteration.
16768  * 
16769  * This method is especially useful for things like detecting whether a user has finished
16770  * typing in a text field. An example would be performing validation on a keypress. You can
16771  * use this class to buffer the keypress events for a certain number of milliseconds, and
16772  * perform only if they stop for that amount of time.  
16773  * 
16774  * ## Usage
16775  * 
16776  *     var task = new Ext.util.DelayedTask(function(){
16777  *         alert(Ext.getDom('myInputField').value.length);
16778  *     });
16779  *     
16780  *     // Wait 500ms before calling our function. If the user presses another key
16781  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
16782  *     Ext.get('myInputField').on('keypress', function(){
16783  *         task.{@link #delay}(500);
16784  *     });
16785  * 
16786  * Note that we are using a DelayedTask here to illustrate a point. The configuration
16787  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
16788  * also setup a delayed task for you to buffer events.
16789  * 
16790  * @constructor The parameters to this constructor serve as defaults and are not required.
16791  * @param {Function} fn (optional) The default function to call. If not specified here, it must be specified during the {@link #delay} call.
16792  * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
16793  * function is called. If not specified, <code>this</code> will refer to the browser window.
16794  * @param {Array} args (optional) The default Array of arguments.
16795  */
16796 Ext.util.DelayedTask = function(fn, scope, args) {
16797     var me = this,
16798         id,
16799         call = function() {
16800             clearInterval(id);
16801             id = null;
16802             fn.apply(scope, args || []);
16803         };
16804
16805     /**
16806      * Cancels any pending timeout and queues a new one
16807      * @param {Number} delay The milliseconds to delay
16808      * @param {Function} newFn (optional) Overrides function passed to constructor
16809      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
16810      * is specified, <code>this</code> will refer to the browser window.
16811      * @param {Array} newArgs (optional) Overrides args passed to constructor
16812      */
16813     this.delay = function(delay, newFn, newScope, newArgs) {
16814         me.cancel();
16815         fn = newFn || fn;
16816         scope = newScope || scope;
16817         args = newArgs || args;
16818         id = setInterval(call, delay);
16819     };
16820
16821     /**
16822      * Cancel the last queued timeout
16823      */
16824     this.cancel = function(){
16825         if (id) {
16826             clearInterval(id);
16827             id = null;
16828         }
16829     };
16830 };
16831 Ext.require('Ext.util.DelayedTask', function() {
16832
16833     Ext.util.Event = Ext.extend(Object, (function() {
16834         function createBuffered(handler, listener, o, scope) {
16835             listener.task = new Ext.util.DelayedTask();
16836             return function() {
16837                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
16838             };
16839         }
16840
16841         function createDelayed(handler, listener, o, scope) {
16842             return function() {
16843                 var task = new Ext.util.DelayedTask();
16844                 if (!listener.tasks) {
16845                     listener.tasks = [];
16846                 }
16847                 listener.tasks.push(task);
16848                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
16849             };
16850         }
16851
16852         function createSingle(handler, listener, o, scope) {
16853             return function() {
16854                 listener.ev.removeListener(listener.fn, scope);
16855                 return handler.apply(scope, arguments);
16856             };
16857         }
16858
16859         return {
16860             isEvent: true,
16861
16862             constructor: function(observable, name) {
16863                 this.name = name;
16864                 this.observable = observable;
16865                 this.listeners = [];
16866             },
16867
16868             addListener: function(fn, scope, options) {
16869                 var me = this,
16870                     listener;
16871                     scope = scope || me.observable;
16872
16873
16874                 if (!me.isListening(fn, scope)) {
16875                     listener = me.createListener(fn, scope, options);
16876                     if (me.firing) {
16877                         // if we are currently firing this event, don't disturb the listener loop
16878                         me.listeners = me.listeners.slice(0);
16879                     }
16880                     me.listeners.push(listener);
16881                 }
16882             },
16883
16884             createListener: function(fn, scope, o) {
16885                 o = o || {};
16886                 scope = scope || this.observable;
16887
16888                 var listener = {
16889                         fn: fn,
16890                         scope: scope,
16891                         o: o,
16892                         ev: this
16893                     },
16894                     handler = fn;
16895
16896                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
16897                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
16898                 if (o.single) {
16899                     handler = createSingle(handler, listener, o, scope);
16900                 }
16901                 if (o.delay) {
16902                     handler = createDelayed(handler, listener, o, scope);
16903                 }
16904                 if (o.buffer) {
16905                     handler = createBuffered(handler, listener, o, scope);
16906                 }
16907
16908                 listener.fireFn = handler;
16909                 return listener;
16910             },
16911
16912             findListener: function(fn, scope) {
16913                 var listeners = this.listeners,
16914                 i = listeners.length,
16915                 listener,
16916                 s;
16917
16918                 while (i--) {
16919                     listener = listeners[i];
16920                     if (listener) {
16921                         s = listener.scope;
16922                         if (listener.fn == fn && (s == scope || s == this.observable)) {
16923                             return i;
16924                         }
16925                     }
16926                 }
16927
16928                 return - 1;
16929             },
16930
16931             isListening: function(fn, scope) {
16932                 return this.findListener(fn, scope) !== -1;
16933             },
16934
16935             removeListener: function(fn, scope) {
16936                 var me = this,
16937                     index,
16938                     listener,
16939                     k;
16940                 index = me.findListener(fn, scope);
16941                 if (index != -1) {
16942                     listener = me.listeners[index];
16943
16944                     if (me.firing) {
16945                         me.listeners = me.listeners.slice(0);
16946                     }
16947
16948                     // cancel and remove a buffered handler that hasn't fired yet
16949                     if (listener.task) {
16950                         listener.task.cancel();
16951                         delete listener.task;
16952                     }
16953
16954                     // cancel and remove all delayed handlers that haven't fired yet
16955                     k = listener.tasks && listener.tasks.length;
16956                     if (k) {
16957                         while (k--) {
16958                             listener.tasks[k].cancel();
16959                         }
16960                         delete listener.tasks;
16961                     }
16962
16963                     // remove this listener from the listeners array
16964                     Ext.Array.erase(me.listeners, index, 1);
16965                     return true;
16966                 }
16967
16968                 return false;
16969             },
16970
16971             // Iterate to stop any buffered/delayed events
16972             clearListeners: function() {
16973                 var listeners = this.listeners,
16974                     i = listeners.length;
16975
16976                 while (i--) {
16977                     this.removeListener(listeners[i].fn, listeners[i].scope);
16978                 }
16979             },
16980
16981             fire: function() {
16982                 var me = this,
16983                     listeners = me.listeners,
16984                     count = listeners.length,
16985                     i,
16986                     args,
16987                     listener;
16988
16989                 if (count > 0) {
16990                     me.firing = true;
16991                     for (i = 0; i < count; i++) {
16992                         listener = listeners[i];
16993                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
16994                         if (listener.o) {
16995                             args.push(listener.o);
16996                         }
16997                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
16998                             return (me.firing = false);
16999                         }
17000                     }
17001                 }
17002                 me.firing = false;
17003                 return true;
17004             }
17005         };
17006     })());
17007 });
17008
17009 /**
17010  * @class Ext.EventManager
17011  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
17012  * several useful events directly.
17013  * See {@link Ext.EventObject} for more details on normalized event objects.
17014  * @singleton
17015  */
17016 Ext.EventManager = {
17017
17018     // --------------------- onReady ---------------------
17019
17020     /**
17021      * Check if we have bound our global onReady listener
17022      * @private
17023      */
17024     hasBoundOnReady: false,
17025
17026     /**
17027      * Check if fireDocReady has been called
17028      * @private
17029      */
17030     hasFiredReady: false,
17031
17032     /**
17033      * Timer for the document ready event in old IE versions
17034      * @private
17035      */
17036     readyTimeout: null,
17037
17038     /**
17039      * Checks if we have bound an onreadystatechange event
17040      * @private
17041      */
17042     hasOnReadyStateChange: false,
17043
17044     /**
17045      * Holds references to any onReady functions
17046      * @private
17047      */
17048     readyEvent: new Ext.util.Event(),
17049
17050     /**
17051      * Check the ready state for old IE versions
17052      * @private
17053      * @return {Boolean} True if the document is ready
17054      */
17055     checkReadyState: function(){
17056         var me = Ext.EventManager;
17057
17058         if(window.attachEvent){
17059             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
17060             // licensed courtesy of http://developer.yahoo.com/yui/license.html
17061             if (window != top) {
17062                 return false;
17063             }
17064             try{
17065                 document.documentElement.doScroll('left');
17066             }catch(e){
17067                 return false;
17068             }
17069             me.fireDocReady();
17070             return true;
17071         }
17072         if (document.readyState == 'complete') {
17073             me.fireDocReady();
17074             return true;
17075         }
17076         me.readyTimeout = setTimeout(arguments.callee, 2);
17077         return false;
17078     },
17079
17080     /**
17081      * Binds the appropriate browser event for checking if the DOM has loaded.
17082      * @private
17083      */
17084     bindReadyEvent: function(){
17085         var me = Ext.EventManager;
17086         if (me.hasBoundOnReady) {
17087             return;
17088         }
17089
17090         if (document.addEventListener) {
17091             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
17092             // fallback, load will ~always~ fire
17093             window.addEventListener('load', me.fireDocReady, false);
17094         } else {
17095             // check if the document is ready, this will also kick off the scroll checking timer
17096             if (!me.checkReadyState()) {
17097                 document.attachEvent('onreadystatechange', me.checkReadyState);
17098                 me.hasOnReadyStateChange = true;
17099             }
17100             // fallback, onload will ~always~ fire
17101             window.attachEvent('onload', me.fireDocReady, false);
17102         }
17103         me.hasBoundOnReady = true;
17104     },
17105
17106     /**
17107      * We know the document is loaded, so trigger any onReady events.
17108      * @private
17109      */
17110     fireDocReady: function(){
17111         var me = Ext.EventManager;
17112
17113         // only unbind these events once
17114         if (!me.hasFiredReady) {
17115             me.hasFiredReady = true;
17116
17117             if (document.addEventListener) {
17118                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
17119                 window.removeEventListener('load', me.fireDocReady, false);
17120             } else {
17121                 if (me.readyTimeout !== null) {
17122                     clearTimeout(me.readyTimeout);
17123                 }
17124                 if (me.hasOnReadyStateChange) {
17125                     document.detachEvent('onreadystatechange', me.checkReadyState);
17126                 }
17127                 window.detachEvent('onload', me.fireDocReady);
17128             }
17129             Ext.supports.init();
17130         }
17131         if (!Ext.isReady) {
17132             Ext.isReady = true;
17133             me.onWindowUnload();
17134             me.readyEvent.fire();
17135         }
17136     },
17137
17138     /**
17139      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
17140      * accessed shorthanded as Ext.onReady().
17141      * @param {Function} fn The method the event invokes.
17142      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17143      * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}.
17144      */
17145     onDocumentReady: function(fn, scope, options){
17146         options = options || {};
17147         var me = Ext.EventManager,
17148             readyEvent = me.readyEvent;
17149
17150         // force single to be true so our event is only ever fired once.
17151         options.single = true;
17152
17153         // Document already loaded, let's just fire it
17154         if (Ext.isReady) {
17155             readyEvent.addListener(fn, scope, options);
17156             readyEvent.fire();
17157         } else {
17158             options.delay = options.delay || 1;
17159             readyEvent.addListener(fn, scope, options);
17160             me.bindReadyEvent();
17161         }
17162     },
17163
17164
17165     // --------------------- event binding ---------------------
17166
17167     /**
17168      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
17169      * @private
17170      */
17171     stoppedMouseDownEvent: new Ext.util.Event(),
17172
17173     /**
17174      * Options to parse for the 4th argument to addListener.
17175      * @private
17176      */
17177     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
17178
17179     /**
17180      * Get the id of the element. If one has not been assigned, automatically assign it.
17181      * @param {HTMLElement/Ext.Element} element The element to get the id for.
17182      * @return {String} id
17183      */
17184     getId : function(element) {
17185         var skipGarbageCollection = false,
17186             id;
17187
17188         element = Ext.getDom(element);
17189
17190         if (element === document || element === window) {
17191             id = element === document ? Ext.documentId : Ext.windowId;
17192         }
17193         else {
17194             id = Ext.id(element);
17195         }
17196         // skip garbage collection for special elements (window, document, iframes)
17197         if (element && (element.getElementById || element.navigator)) {
17198             skipGarbageCollection = true;
17199         }
17200
17201         if (!Ext.cache[id]){
17202             Ext.Element.addToCache(new Ext.Element(element), id);
17203             if (skipGarbageCollection) {
17204                 Ext.cache[id].skipGarbageCollection = true;
17205             }
17206         }
17207         return id;
17208     },
17209
17210     /**
17211      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
17212      * @private
17213      * @param {Object} element The element the event is for
17214      * @param {Object} event The event configuration
17215      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
17216      */
17217     prepareListenerConfig: function(element, config, isRemove){
17218         var me = this,
17219             propRe = me.propRe,
17220             key, value, args;
17221
17222         // loop over all the keys in the object
17223         for (key in config) {
17224             if (config.hasOwnProperty(key)) {
17225                 // if the key is something else then an event option
17226                 if (!propRe.test(key)) {
17227                     value = config[key];
17228                     // if the value is a function it must be something like click: function(){}, scope: this
17229                     // which means that there might be multiple event listeners with shared options
17230                     if (Ext.isFunction(value)) {
17231                         // shared options
17232                         args = [element, key, value, config.scope, config];
17233                     } else {
17234                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
17235                         args = [element, key, value.fn, value.scope, value];
17236                     }
17237
17238                     if (isRemove === true) {
17239                         me.removeListener.apply(this, args);
17240                     } else {
17241                         me.addListener.apply(me, args);
17242                     }
17243                 }
17244             }
17245         }
17246     },
17247
17248     /**
17249      * Normalize cross browser event differences
17250      * @private
17251      * @param {Object} eventName The event name
17252      * @param {Object} fn The function to execute
17253      * @return {Object} The new event name/function
17254      */
17255     normalizeEvent: function(eventName, fn){
17256         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
17257             if (fn) {
17258                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
17259             }
17260             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
17261         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
17262             eventName = 'DOMMouseScroll';
17263         }
17264         return {
17265             eventName: eventName,
17266             fn: fn
17267         };
17268     },
17269
17270     /**
17271      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
17272      * @private
17273      * @param {Object} event
17274      */
17275     contains: function(event){
17276         var parent = event.browserEvent.currentTarget,
17277             child = this.getRelatedTarget(event);
17278
17279         if (parent && parent.firstChild) {
17280             while (child) {
17281                 if (child === parent) {
17282                     return false;
17283                 }
17284                 child = child.parentNode;
17285                 if (child && (child.nodeType != 1)) {
17286                     child = null;
17287                 }
17288             }
17289         }
17290         return true;
17291     },
17292
17293     /**
17294     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
17295     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
17296     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
17297     * @param {String} eventName The name of the event to listen for.
17298     * @param {Function} handler The handler function the event invokes. This function is passed
17299     * the following parameters:<ul>
17300     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
17301     * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
17302     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
17303     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
17304     * </ul>
17305     * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
17306     * @param {Object} options (optional) An object containing handler configuration properties.
17307     * This may contain any of the following properties:<ul>
17308     * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
17309     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
17310     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
17311     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
17312     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
17313     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
17314     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
17315     * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
17316     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
17317     * by the specified number of milliseconds. If the event fires again within that time, the original
17318     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
17319     * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
17320     * </ul><br>
17321     * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
17322     */
17323     addListener: function(element, eventName, fn, scope, options){
17324         // Check if we've been passed a "config style" event.
17325         if (typeof eventName !== 'string') {
17326             this.prepareListenerConfig(element, eventName);
17327             return;
17328         }
17329
17330         var dom = Ext.getDom(element),
17331             bind,
17332             wrap;
17333
17334
17335         // create the wrapper function
17336         options = options || {};
17337
17338         bind = this.normalizeEvent(eventName, fn);
17339         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
17340
17341
17342         if (dom.attachEvent) {
17343             dom.attachEvent('on' + bind.eventName, wrap);
17344         } else {
17345             dom.addEventListener(bind.eventName, wrap, options.capture || false);
17346         }
17347
17348         if (dom == document && eventName == 'mousedown') {
17349             this.stoppedMouseDownEvent.addListener(wrap);
17350         }
17351
17352         // add all required data into the event cache
17353         this.getEventListenerCache(dom, eventName).push({
17354             fn: fn,
17355             wrap: wrap,
17356             scope: scope
17357         });
17358     },
17359
17360     /**
17361     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
17362     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
17363     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
17364     * @param {String} eventName The name of the event.
17365     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
17366     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
17367     * then this must refer to the same object.
17368     */
17369     removeListener : function(element, eventName, fn, scope) {
17370         // handle our listener config object syntax
17371         if (typeof eventName !== 'string') {
17372             this.prepareListenerConfig(element, eventName, true);
17373             return;
17374         }
17375
17376         var dom = Ext.getDom(element),
17377             cache = this.getEventListenerCache(dom, eventName),
17378             bindName = this.normalizeEvent(eventName).eventName,
17379             i = cache.length, j,
17380             listener, wrap, tasks;
17381
17382
17383         while (i--) {
17384             listener = cache[i];
17385
17386             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
17387                 wrap = listener.wrap;
17388
17389                 // clear buffered calls
17390                 if (wrap.task) {
17391                     clearTimeout(wrap.task);
17392                     delete wrap.task;
17393                 }
17394
17395                 // clear delayed calls
17396                 j = wrap.tasks && wrap.tasks.length;
17397                 if (j) {
17398                     while (j--) {
17399                         clearTimeout(wrap.tasks[j]);
17400                     }
17401                     delete wrap.tasks;
17402                 }
17403
17404                 if (dom.detachEvent) {
17405                     dom.detachEvent('on' + bindName, wrap);
17406                 } else {
17407                     dom.removeEventListener(bindName, wrap, false);
17408                 }
17409
17410                 if (wrap && dom == document && eventName == 'mousedown') {
17411                     this.stoppedMouseDownEvent.removeListener(wrap);
17412                 }
17413
17414                 // remove listener from cache
17415                 Ext.Array.erase(cache, i, 1);
17416             }
17417         }
17418     },
17419
17420     /**
17421     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
17422     * directly on an Element in favor of calling this version.
17423     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17424     */
17425     removeAll : function(element){
17426         var dom = Ext.getDom(element),
17427             cache, ev;
17428         if (!dom) {
17429             return;
17430         }
17431         cache = this.getElementEventCache(dom);
17432
17433         for (ev in cache) {
17434             if (cache.hasOwnProperty(ev)) {
17435                 this.removeListener(dom, ev);
17436             }
17437         }
17438         Ext.cache[dom.id].events = {};
17439     },
17440
17441     /**
17442      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
17443      * directly on an Element in favor of calling this version.
17444      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17445      * @param {String} eventName (optional) The name of the event.
17446      */
17447     purgeElement : function(element, eventName) {
17448         var dom = Ext.getDom(element),
17449             i = 0, len;
17450
17451         if(eventName) {
17452             this.removeListener(dom, eventName);
17453         }
17454         else {
17455             this.removeAll(dom);
17456         }
17457
17458         if(dom && dom.childNodes) {
17459             for(len = element.childNodes.length; i < len; i++) {
17460                 this.purgeElement(element.childNodes[i], eventName);
17461             }
17462         }
17463     },
17464
17465     /**
17466      * Create the wrapper function for the event
17467      * @private
17468      * @param {HTMLElement} dom The dom element
17469      * @param {String} ename The event name
17470      * @param {Function} fn The function to execute
17471      * @param {Object} scope The scope to execute callback in
17472      * @param {Object} options The options
17473      * @return {Function} the wrapper function
17474      */
17475     createListenerWrap : function(dom, ename, fn, scope, options) {
17476         options = options || {};
17477
17478         var f, gen;
17479
17480         return function wrap(e, args) {
17481             // Compile the implementation upon first firing
17482             if (!gen) {
17483                 f = ['if(!Ext) {return;}'];
17484
17485                 if(options.buffer || options.delay || options.freezeEvent) {
17486                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
17487                 } else {
17488                     f.push('e = Ext.EventObject.setEvent(e);');
17489                 }
17490
17491                 if (options.delegate) {
17492                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
17493                     f.push('if(!t) {return;}');
17494                 } else {
17495                     f.push('var t = e.target;');
17496                 }
17497
17498                 if (options.target) {
17499                     f.push('if(e.target !== options.target) {return;}');
17500                 }
17501
17502                 if(options.stopEvent) {
17503                     f.push('e.stopEvent();');
17504                 } else {
17505                     if(options.preventDefault) {
17506                         f.push('e.preventDefault();');
17507                     }
17508                     if(options.stopPropagation) {
17509                         f.push('e.stopPropagation();');
17510                     }
17511                 }
17512
17513                 if(options.normalized === false) {
17514                     f.push('e = e.browserEvent;');
17515                 }
17516
17517                 if(options.buffer) {
17518                     f.push('(wrap.task && clearTimeout(wrap.task));');
17519                     f.push('wrap.task = setTimeout(function(){');
17520                 }
17521
17522                 if(options.delay) {
17523                     f.push('wrap.tasks = wrap.tasks || [];');
17524                     f.push('wrap.tasks.push(setTimeout(function(){');
17525                 }
17526
17527                 // finally call the actual handler fn
17528                 f.push('fn.call(scope || dom, e, t, options);');
17529
17530                 if(options.single) {
17531                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
17532                 }
17533
17534                 if(options.delay) {
17535                     f.push('}, ' + options.delay + '));');
17536                 }
17537
17538                 if(options.buffer) {
17539                     f.push('}, ' + options.buffer + ');');
17540                 }
17541
17542                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
17543             }
17544
17545             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
17546         };
17547     },
17548
17549     /**
17550      * Get the event cache for a particular element for a particular event
17551      * @private
17552      * @param {HTMLElement} element The element
17553      * @param {Object} eventName The event name
17554      * @return {Array} The events for the element
17555      */
17556     getEventListenerCache : function(element, eventName) {
17557         if (!element) {
17558             return [];
17559         }
17560
17561         var eventCache = this.getElementEventCache(element);
17562         return eventCache[eventName] || (eventCache[eventName] = []);
17563     },
17564
17565     /**
17566      * Gets the event cache for the object
17567      * @private
17568      * @param {HTMLElement} element The element
17569      * @return {Object} The event cache for the object
17570      */
17571     getElementEventCache : function(element) {
17572         if (!element) {
17573             return {};
17574         }
17575         var elementCache = Ext.cache[this.getId(element)];
17576         return elementCache.events || (elementCache.events = {});
17577     },
17578
17579     // --------------------- utility methods ---------------------
17580     mouseLeaveRe: /(mouseout|mouseleave)/,
17581     mouseEnterRe: /(mouseover|mouseenter)/,
17582
17583     /**
17584      * Stop the event (preventDefault and stopPropagation)
17585      * @param {Event} The event to stop
17586      */
17587     stopEvent: function(event) {
17588         this.stopPropagation(event);
17589         this.preventDefault(event);
17590     },
17591
17592     /**
17593      * Cancels bubbling of the event.
17594      * @param {Event} The event to stop bubbling.
17595      */
17596     stopPropagation: function(event) {
17597         event = event.browserEvent || event;
17598         if (event.stopPropagation) {
17599             event.stopPropagation();
17600         } else {
17601             event.cancelBubble = true;
17602         }
17603     },
17604
17605     /**
17606      * Prevents the browsers default handling of the event.
17607      * @param {Event} The event to prevent the default
17608      */
17609     preventDefault: function(event) {
17610         event = event.browserEvent || event;
17611         if (event.preventDefault) {
17612             event.preventDefault();
17613         } else {
17614             event.returnValue = false;
17615             // Some keys events require setting the keyCode to -1 to be prevented
17616             try {
17617               // all ctrl + X and F1 -> F12
17618               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
17619                   event.keyCode = -1;
17620               }
17621             } catch (e) {
17622                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
17623             }
17624         }
17625     },
17626
17627     /**
17628      * Gets the related target from the event.
17629      * @param {Object} event The event
17630      * @return {HTMLElement} The related target.
17631      */
17632     getRelatedTarget: function(event) {
17633         event = event.browserEvent || event;
17634         var target = event.relatedTarget;
17635         if (!target) {
17636             if (this.mouseLeaveRe.test(event.type)) {
17637                 target = event.toElement;
17638             } else if (this.mouseEnterRe.test(event.type)) {
17639                 target = event.fromElement;
17640             }
17641         }
17642         return this.resolveTextNode(target);
17643     },
17644
17645     /**
17646      * Gets the x coordinate from the event
17647      * @param {Object} event The event
17648      * @return {Number} The x coordinate
17649      */
17650     getPageX: function(event) {
17651         return this.getXY(event)[0];
17652     },
17653
17654     /**
17655      * Gets the y coordinate from the event
17656      * @param {Object} event The event
17657      * @return {Number} The y coordinate
17658      */
17659     getPageY: function(event) {
17660         return this.getXY(event)[1];
17661     },
17662
17663     /**
17664      * Gets the x & y coordinate from the event
17665      * @param {Object} event The event
17666      * @return {Number[]} The x/y coordinate
17667      */
17668     getPageXY: function(event) {
17669         event = event.browserEvent || event;
17670         var x = event.pageX,
17671             y = event.pageY,
17672             doc = document.documentElement,
17673             body = document.body;
17674
17675         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
17676         if (!x && x !== 0) {
17677             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
17678             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
17679         }
17680         return [x, y];
17681     },
17682
17683     /**
17684      * Gets the target of the event.
17685      * @param {Object} event The event
17686      * @return {HTMLElement} target
17687      */
17688     getTarget: function(event) {
17689         event = event.browserEvent || event;
17690         return this.resolveTextNode(event.target || event.srcElement);
17691     },
17692
17693     /**
17694      * Resolve any text nodes accounting for browser differences.
17695      * @private
17696      * @param {HTMLElement} node The node
17697      * @return {HTMLElement} The resolved node
17698      */
17699     // technically no need to browser sniff this, however it makes no sense to check this every time, for every event, whether the string is equal.
17700     resolveTextNode: Ext.isGecko ?
17701         function(node) {
17702             if (!node) {
17703                 return;
17704             }
17705             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
17706             var s = HTMLElement.prototype.toString.call(node);
17707             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
17708                 return;
17709             }
17710                 return node.nodeType == 3 ? node.parentNode: node;
17711             }: function(node) {
17712                 return node && node.nodeType == 3 ? node.parentNode: node;
17713             },
17714
17715     // --------------------- custom event binding ---------------------
17716
17717     // Keep track of the current width/height
17718     curWidth: 0,
17719     curHeight: 0,
17720
17721     /**
17722      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
17723      * passes new viewport width and height to handlers.
17724      * @param {Function} fn      The handler function the window resize event invokes.
17725      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17726      * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
17727      */
17728     onWindowResize: function(fn, scope, options){
17729         var resize = this.resizeEvent;
17730         if(!resize){
17731             this.resizeEvent = resize = new Ext.util.Event();
17732             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
17733         }
17734         resize.addListener(fn, scope, options);
17735     },
17736
17737     /**
17738      * Fire the resize event.
17739      * @private
17740      */
17741     fireResize: function(){
17742         var me = this,
17743             w = Ext.Element.getViewWidth(),
17744             h = Ext.Element.getViewHeight();
17745
17746          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
17747          if(me.curHeight != h || me.curWidth != w){
17748              me.curHeight = h;
17749              me.curWidth = w;
17750              me.resizeEvent.fire(w, h);
17751          }
17752     },
17753
17754     /**
17755      * Removes the passed window resize listener.
17756      * @param {Function} fn        The method the event invokes
17757      * @param {Object}   scope    The scope of handler
17758      */
17759     removeResizeListener: function(fn, scope){
17760         if (this.resizeEvent) {
17761             this.resizeEvent.removeListener(fn, scope);
17762         }
17763     },
17764
17765     onWindowUnload: function() {
17766         var unload = this.unloadEvent;
17767         if (!unload) {
17768             this.unloadEvent = unload = new Ext.util.Event();
17769             this.addListener(window, 'unload', this.fireUnload, this);
17770         }
17771     },
17772
17773     /**
17774      * Fires the unload event for items bound with onWindowUnload
17775      * @private
17776      */
17777     fireUnload: function() {
17778         // wrap in a try catch, could have some problems during unload
17779         try {
17780             this.removeUnloadListener();
17781             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
17782             if (Ext.isGecko3) {
17783                 var gridviews = Ext.ComponentQuery.query('gridview'),
17784                     i = 0,
17785                     ln = gridviews.length;
17786                 for (; i < ln; i++) {
17787                     gridviews[i].scrollToTop();
17788                 }
17789             }
17790             // Purge all elements in the cache
17791             var el,
17792                 cache = Ext.cache;
17793             for (el in cache) {
17794                 if (cache.hasOwnProperty(el)) {
17795                     Ext.EventManager.removeAll(el);
17796                 }
17797             }
17798         } catch(e) {
17799         }
17800     },
17801
17802     /**
17803      * Removes the passed window unload listener.
17804      * @param {Function} fn        The method the event invokes
17805      * @param {Object}   scope    The scope of handler
17806      */
17807     removeUnloadListener: function(){
17808         if (this.unloadEvent) {
17809             this.removeListener(window, 'unload', this.fireUnload);
17810         }
17811     },
17812
17813     /**
17814      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
17815      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
17816      * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
17817      * @private
17818      */
17819     useKeyDown: Ext.isWebKit ?
17820                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
17821                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
17822
17823     /**
17824      * Indicates which event to use for getting key presses.
17825      * @return {String} The appropriate event name.
17826      */
17827     getKeyEvent: function(){
17828         return this.useKeyDown ? 'keydown' : 'keypress';
17829     }
17830 };
17831
17832 /**
17833  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
17834  * @member Ext
17835  * @method onReady
17836  */
17837 Ext.onReady = function(fn, scope, options) {
17838     Ext.Loader.onReady(fn, scope, true, options);
17839 };
17840
17841 /**
17842  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
17843  * @member Ext
17844  * @method onDocumentReady
17845  */
17846 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
17847
17848 /**
17849  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
17850  * @member Ext.EventManager
17851  * @method on
17852  */
17853 Ext.EventManager.on = Ext.EventManager.addListener;
17854
17855 /**
17856  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
17857  * @member Ext.EventManager
17858  * @method un
17859  */
17860 Ext.EventManager.un = Ext.EventManager.removeListener;
17861
17862 (function(){
17863     var initExtCss = function() {
17864         // find the body element
17865         var bd = document.body || document.getElementsByTagName('body')[0],
17866             baseCSSPrefix = Ext.baseCSSPrefix,
17867             cls = [baseCSSPrefix + 'body'],
17868             htmlCls = [],
17869             html;
17870
17871         if (!bd) {
17872             return false;
17873         }
17874
17875         html = bd.parentNode;
17876
17877         function add (c) {
17878             cls.push(baseCSSPrefix + c);
17879         }
17880
17881         //Let's keep this human readable!
17882         if (Ext.isIE) {
17883             add('ie');
17884
17885             // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
17886             // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
17887             // additional classes:
17888             //
17889             //      x-ie7p      : IE7+      :  7 <= ieVer
17890             //      x-ie7m      : IE7-      :  ieVer <= 7
17891             //      x-ie8p      : IE8+      :  8 <= ieVer
17892             //      x-ie8m      : IE8-      :  ieVer <= 8
17893             //      x-ie9p      : IE9+      :  9 <= ieVer
17894             //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
17895             //
17896             if (Ext.isIE6) {
17897                 add('ie6');
17898             } else { // ignore pre-IE6 :)
17899                 add('ie7p');
17900
17901                 if (Ext.isIE7) {
17902                     add('ie7');
17903                 } else {
17904                     add('ie8p');
17905
17906                     if (Ext.isIE8) {
17907                         add('ie8');
17908                     } else {
17909                         add('ie9p');
17910
17911                         if (Ext.isIE9) {
17912                             add('ie9');
17913                         }
17914                     }
17915                 }
17916             }
17917
17918             if (Ext.isIE6 || Ext.isIE7) {
17919                 add('ie7m');
17920             }
17921             if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
17922                 add('ie8m');
17923             }
17924             if (Ext.isIE7 || Ext.isIE8) {
17925                 add('ie78');
17926             }
17927         }
17928         if (Ext.isGecko) {
17929             add('gecko');
17930             if (Ext.isGecko3) {
17931                 add('gecko3');
17932             }
17933             if (Ext.isGecko4) {
17934                 add('gecko4');
17935             }
17936             if (Ext.isGecko5) {
17937                 add('gecko5');
17938             }
17939         }
17940         if (Ext.isOpera) {
17941             add('opera');
17942         }
17943         if (Ext.isWebKit) {
17944             add('webkit');
17945         }
17946         if (Ext.isSafari) {
17947             add('safari');
17948             if (Ext.isSafari2) {
17949                 add('safari2');
17950             }
17951             if (Ext.isSafari3) {
17952                 add('safari3');
17953             }
17954             if (Ext.isSafari4) {
17955                 add('safari4');
17956             }
17957             if (Ext.isSafari5) {
17958                 add('safari5');
17959             }
17960         }
17961         if (Ext.isChrome) {
17962             add('chrome');
17963         }
17964         if (Ext.isMac) {
17965             add('mac');
17966         }
17967         if (Ext.isLinux) {
17968             add('linux');
17969         }
17970         if (!Ext.supports.CSS3BorderRadius) {
17971             add('nbr');
17972         }
17973         if (!Ext.supports.CSS3LinearGradient) {
17974             add('nlg');
17975         }
17976         if (!Ext.scopeResetCSS) {
17977             add('reset');
17978         }
17979
17980         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
17981         if (html) {
17982             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
17983                 Ext.isBorderBox = false;
17984             }
17985             else {
17986                 Ext.isBorderBox = true;
17987             }
17988
17989             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
17990             if (!Ext.isStrict) {
17991                 htmlCls.push(baseCSSPrefix + 'quirks');
17992             }
17993             Ext.fly(html, '_internal').addCls(htmlCls);
17994         }
17995
17996         Ext.fly(bd, '_internal').addCls(cls);
17997         return true;
17998     };
17999
18000     Ext.onReady(initExtCss);
18001 })();
18002
18003 /**
18004  * @class Ext.EventObject
18005
18006 Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
18007 wraps the browser's native event-object normalizing cross-browser differences,
18008 such as which mouse button is clicked, keys pressed, mechanisms to stop
18009 event-propagation along with a method to prevent default actions from taking place.
18010
18011 For example:
18012
18013     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
18014         e.preventDefault();
18015         var target = e.getTarget(); // same as t (the target HTMLElement)
18016         ...
18017     }
18018
18019     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.Element}
18020     myDiv.on(         // 'on' is shorthand for addListener
18021         "click",      // perform an action on click of myDiv
18022         handleClick   // reference to the action handler
18023     );
18024
18025     // other methods to do the same:
18026     Ext.EventManager.on("myDiv", 'click', handleClick);
18027     Ext.EventManager.addListener("myDiv", 'click', handleClick);
18028
18029  * @singleton
18030  * @markdown
18031  */
18032 Ext.define('Ext.EventObjectImpl', {
18033     uses: ['Ext.util.Point'],
18034
18035     /** Key constant @type Number */
18036     BACKSPACE: 8,
18037     /** Key constant @type Number */
18038     TAB: 9,
18039     /** Key constant @type Number */
18040     NUM_CENTER: 12,
18041     /** Key constant @type Number */
18042     ENTER: 13,
18043     /** Key constant @type Number */
18044     RETURN: 13,
18045     /** Key constant @type Number */
18046     SHIFT: 16,
18047     /** Key constant @type Number */
18048     CTRL: 17,
18049     /** Key constant @type Number */
18050     ALT: 18,
18051     /** Key constant @type Number */
18052     PAUSE: 19,
18053     /** Key constant @type Number */
18054     CAPS_LOCK: 20,
18055     /** Key constant @type Number */
18056     ESC: 27,
18057     /** Key constant @type Number */
18058     SPACE: 32,
18059     /** Key constant @type Number */
18060     PAGE_UP: 33,
18061     /** Key constant @type Number */
18062     PAGE_DOWN: 34,
18063     /** Key constant @type Number */
18064     END: 35,
18065     /** Key constant @type Number */
18066     HOME: 36,
18067     /** Key constant @type Number */
18068     LEFT: 37,
18069     /** Key constant @type Number */
18070     UP: 38,
18071     /** Key constant @type Number */
18072     RIGHT: 39,
18073     /** Key constant @type Number */
18074     DOWN: 40,
18075     /** Key constant @type Number */
18076     PRINT_SCREEN: 44,
18077     /** Key constant @type Number */
18078     INSERT: 45,
18079     /** Key constant @type Number */
18080     DELETE: 46,
18081     /** Key constant @type Number */
18082     ZERO: 48,
18083     /** Key constant @type Number */
18084     ONE: 49,
18085     /** Key constant @type Number */
18086     TWO: 50,
18087     /** Key constant @type Number */
18088     THREE: 51,
18089     /** Key constant @type Number */
18090     FOUR: 52,
18091     /** Key constant @type Number */
18092     FIVE: 53,
18093     /** Key constant @type Number */
18094     SIX: 54,
18095     /** Key constant @type Number */
18096     SEVEN: 55,
18097     /** Key constant @type Number */
18098     EIGHT: 56,
18099     /** Key constant @type Number */
18100     NINE: 57,
18101     /** Key constant @type Number */
18102     A: 65,
18103     /** Key constant @type Number */
18104     B: 66,
18105     /** Key constant @type Number */
18106     C: 67,
18107     /** Key constant @type Number */
18108     D: 68,
18109     /** Key constant @type Number */
18110     E: 69,
18111     /** Key constant @type Number */
18112     F: 70,
18113     /** Key constant @type Number */
18114     G: 71,
18115     /** Key constant @type Number */
18116     H: 72,
18117     /** Key constant @type Number */
18118     I: 73,
18119     /** Key constant @type Number */
18120     J: 74,
18121     /** Key constant @type Number */
18122     K: 75,
18123     /** Key constant @type Number */
18124     L: 76,
18125     /** Key constant @type Number */
18126     M: 77,
18127     /** Key constant @type Number */
18128     N: 78,
18129     /** Key constant @type Number */
18130     O: 79,
18131     /** Key constant @type Number */
18132     P: 80,
18133     /** Key constant @type Number */
18134     Q: 81,
18135     /** Key constant @type Number */
18136     R: 82,
18137     /** Key constant @type Number */
18138     S: 83,
18139     /** Key constant @type Number */
18140     T: 84,
18141     /** Key constant @type Number */
18142     U: 85,
18143     /** Key constant @type Number */
18144     V: 86,
18145     /** Key constant @type Number */
18146     W: 87,
18147     /** Key constant @type Number */
18148     X: 88,
18149     /** Key constant @type Number */
18150     Y: 89,
18151     /** Key constant @type Number */
18152     Z: 90,
18153     /** Key constant @type Number */
18154     CONTEXT_MENU: 93,
18155     /** Key constant @type Number */
18156     NUM_ZERO: 96,
18157     /** Key constant @type Number */
18158     NUM_ONE: 97,
18159     /** Key constant @type Number */
18160     NUM_TWO: 98,
18161     /** Key constant @type Number */
18162     NUM_THREE: 99,
18163     /** Key constant @type Number */
18164     NUM_FOUR: 100,
18165     /** Key constant @type Number */
18166     NUM_FIVE: 101,
18167     /** Key constant @type Number */
18168     NUM_SIX: 102,
18169     /** Key constant @type Number */
18170     NUM_SEVEN: 103,
18171     /** Key constant @type Number */
18172     NUM_EIGHT: 104,
18173     /** Key constant @type Number */
18174     NUM_NINE: 105,
18175     /** Key constant @type Number */
18176     NUM_MULTIPLY: 106,
18177     /** Key constant @type Number */
18178     NUM_PLUS: 107,
18179     /** Key constant @type Number */
18180     NUM_MINUS: 109,
18181     /** Key constant @type Number */
18182     NUM_PERIOD: 110,
18183     /** Key constant @type Number */
18184     NUM_DIVISION: 111,
18185     /** Key constant @type Number */
18186     F1: 112,
18187     /** Key constant @type Number */
18188     F2: 113,
18189     /** Key constant @type Number */
18190     F3: 114,
18191     /** Key constant @type Number */
18192     F4: 115,
18193     /** Key constant @type Number */
18194     F5: 116,
18195     /** Key constant @type Number */
18196     F6: 117,
18197     /** Key constant @type Number */
18198     F7: 118,
18199     /** Key constant @type Number */
18200     F8: 119,
18201     /** Key constant @type Number */
18202     F9: 120,
18203     /** Key constant @type Number */
18204     F10: 121,
18205     /** Key constant @type Number */
18206     F11: 122,
18207     /** Key constant @type Number */
18208     F12: 123,
18209     /**
18210      * The mouse wheel delta scaling factor. This value depends on browser version and OS and
18211      * attempts to produce a similar scrolling experience across all platforms and browsers.
18212      *
18213      * To change this value:
18214      *
18215      *      Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
18216      *
18217      * @type Number
18218      * @markdown
18219      */
18220     WHEEL_SCALE: (function () {
18221         var scale;
18222
18223         if (Ext.isGecko) {
18224             // Firefox uses 3 on all platforms
18225             scale = 3;
18226         } else if (Ext.isMac) {
18227             // Continuous scrolling devices have momentum and produce much more scroll than
18228             // discrete devices on the same OS and browser. To make things exciting, Safari
18229             // (and not Chrome) changed from small values to 120 (like IE).
18230
18231             if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
18232                 // Safari changed the scrolling factor to match IE (for details see
18233                 // https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
18234                 // change was introduced was 532.0
18235                 //      Detailed discussion:
18236                 //      https://bugs.webkit.org/show_bug.cgi?id=29601
18237                 //      http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
18238                 scale = 120;
18239             } else {
18240                 // MS optical wheel mouse produces multiples of 12 which is close enough
18241                 // to help tame the speed of the continuous mice...
18242                 scale = 12;
18243             }
18244
18245             // Momentum scrolling produces very fast scrolling, so increase the scale factor
18246             // to help produce similar results cross platform. This could be even larger and
18247             // it would help those mice, but other mice would become almost unusable as a
18248             // result (since we cannot tell which device type is in use).
18249             scale *= 3;
18250         } else {
18251             // IE, Opera and other Windows browsers use 120.
18252             scale = 120;
18253         }
18254
18255         return scale;
18256     })(),
18257
18258     /**
18259      * Simple click regex
18260      * @private
18261      */
18262     clickRe: /(dbl)?click/,
18263     // safari keypress events for special keys return bad keycodes
18264     safariKeys: {
18265         3: 13, // enter
18266         63234: 37, // left
18267         63235: 39, // right
18268         63232: 38, // up
18269         63233: 40, // down
18270         63276: 33, // page up
18271         63277: 34, // page down
18272         63272: 46, // delete
18273         63273: 36, // home
18274         63275: 35 // end
18275     },
18276     // normalize button clicks, don't see any way to feature detect this.
18277     btnMap: Ext.isIE ? {
18278         1: 0,
18279         4: 1,
18280         2: 2
18281     } : {
18282         0: 0,
18283         1: 1,
18284         2: 2
18285     },
18286
18287     constructor: function(event, freezeEvent){
18288         if (event) {
18289             this.setEvent(event.browserEvent || event, freezeEvent);
18290         }
18291     },
18292
18293     setEvent: function(event, freezeEvent){
18294         var me = this, button, options;
18295
18296         if (event == me || (event && event.browserEvent)) { // already wrapped
18297             return event;
18298         }
18299         me.browserEvent = event;
18300         if (event) {
18301             // normalize buttons
18302             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
18303             if (me.clickRe.test(event.type) && button == -1) {
18304                 button = 0;
18305             }
18306             options = {
18307                 type: event.type,
18308                 button: button,
18309                 shiftKey: event.shiftKey,
18310                 // mac metaKey behaves like ctrlKey
18311                 ctrlKey: event.ctrlKey || event.metaKey || false,
18312                 altKey: event.altKey,
18313                 // in getKey these will be normalized for the mac
18314                 keyCode: event.keyCode,
18315                 charCode: event.charCode,
18316                 // cache the targets for the delayed and or buffered events
18317                 target: Ext.EventManager.getTarget(event),
18318                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
18319                 currentTarget: event.currentTarget,
18320                 xy: (freezeEvent ? me.getXY() : null)
18321             };
18322         } else {
18323             options = {
18324                 button: -1,
18325                 shiftKey: false,
18326                 ctrlKey: false,
18327                 altKey: false,
18328                 keyCode: 0,
18329                 charCode: 0,
18330                 target: null,
18331                 xy: [0, 0]
18332             };
18333         }
18334         Ext.apply(me, options);
18335         return me;
18336     },
18337
18338     /**
18339      * Stop the event (preventDefault and stopPropagation)
18340      */
18341     stopEvent: function(){
18342         this.stopPropagation();
18343         this.preventDefault();
18344     },
18345
18346     /**
18347      * Prevents the browsers default handling of the event.
18348      */
18349     preventDefault: function(){
18350         if (this.browserEvent) {
18351             Ext.EventManager.preventDefault(this.browserEvent);
18352         }
18353     },
18354
18355     /**
18356      * Cancels bubbling of the event.
18357      */
18358     stopPropagation: function(){
18359         var browserEvent = this.browserEvent;
18360
18361         if (browserEvent) {
18362             if (browserEvent.type == 'mousedown') {
18363                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
18364             }
18365             Ext.EventManager.stopPropagation(browserEvent);
18366         }
18367     },
18368
18369     /**
18370      * Gets the character code for the event.
18371      * @return {Number}
18372      */
18373     getCharCode: function(){
18374         return this.charCode || this.keyCode;
18375     },
18376
18377     /**
18378      * Returns a normalized keyCode for the event.
18379      * @return {Number} The key code
18380      */
18381     getKey: function(){
18382         return this.normalizeKey(this.keyCode || this.charCode);
18383     },
18384
18385     /**
18386      * Normalize key codes across browsers
18387      * @private
18388      * @param {Number} key The key code
18389      * @return {Number} The normalized code
18390      */
18391     normalizeKey: function(key){
18392         // can't feature detect this
18393         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
18394     },
18395
18396     /**
18397      * Gets the x coordinate of the event.
18398      * @return {Number}
18399      * @deprecated 4.0 Replaced by {@link #getX}
18400      */
18401     getPageX: function(){
18402         return this.getX();
18403     },
18404
18405     /**
18406      * Gets the y coordinate of the event.
18407      * @return {Number}
18408      * @deprecated 4.0 Replaced by {@link #getY}
18409      */
18410     getPageY: function(){
18411         return this.getY();
18412     },
18413
18414     /**
18415      * Gets the x coordinate of the event.
18416      * @return {Number}
18417      */
18418     getX: function() {
18419         return this.getXY()[0];
18420     },
18421
18422     /**
18423      * Gets the y coordinate of the event.
18424      * @return {Number}
18425      */
18426     getY: function() {
18427         return this.getXY()[1];
18428     },
18429
18430     /**
18431      * Gets the page coordinates of the event.
18432      * @return {Number[]} The xy values like [x, y]
18433      */
18434     getXY: function() {
18435         if (!this.xy) {
18436             // same for XY
18437             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
18438         }
18439         return this.xy;
18440     },
18441
18442     /**
18443      * Gets the target for the event.
18444      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18445      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18446      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18447      * @return {HTMLElement}
18448      */
18449     getTarget : function(selector, maxDepth, returnEl){
18450         if (selector) {
18451             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
18452         }
18453         return returnEl ? Ext.get(this.target) : this.target;
18454     },
18455
18456     /**
18457      * Gets the related target.
18458      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18459      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18460      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18461      * @return {HTMLElement}
18462      */
18463     getRelatedTarget : function(selector, maxDepth, returnEl){
18464         if (selector) {
18465             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
18466         }
18467         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
18468     },
18469
18470     /**
18471      * Correctly scales a given wheel delta.
18472      * @param {Number} delta The delta value.
18473      */
18474     correctWheelDelta : function (delta) {
18475         var scale = this.WHEEL_SCALE,
18476             ret = Math.round(delta / scale);
18477
18478         if (!ret && delta) {
18479             ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
18480         }
18481
18482         return ret;
18483     },
18484
18485     /**
18486      * Returns the mouse wheel deltas for this event.
18487      * @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
18488      */
18489     getWheelDeltas : function () {
18490         var me = this,
18491             event = me.browserEvent,
18492             dx = 0, dy = 0; // the deltas
18493
18494         if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
18495             dx = event.wheelDeltaX;
18496             dy = event.wheelDeltaY;
18497         } else if (event.wheelDelta) { // old WebKit and IE
18498             dy = event.wheelDelta;
18499         } else if (event.detail) { // Gecko
18500             dy = -event.detail; // gecko is backwards
18501
18502             // Gecko sometimes returns really big values if the user changes settings to
18503             // scroll a whole page per scroll
18504             if (dy > 100) {
18505                 dy = 3;
18506             } else if (dy < -100) {
18507                 dy = -3;
18508             }
18509
18510             // Firefox 3.1 adds an axis field to the event to indicate direction of
18511             // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
18512             if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
18513                 dx = dy;
18514                 dy = 0;
18515             }
18516         }
18517
18518         return {
18519             x: me.correctWheelDelta(dx),
18520             y: me.correctWheelDelta(dy)
18521         };
18522     },
18523
18524     /**
18525      * Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
18526      * {@link #getWheelDeltas} instead.
18527      * @return {Number} The mouse wheel y-delta
18528      */
18529     getWheelDelta : function(){
18530         var deltas = this.getWheelDeltas();
18531
18532         return deltas.y;
18533     },
18534
18535     /**
18536      * Returns true if the target of this event is a child of el.  Unless the allowEl parameter is set, it will return false if if the target is el.
18537      * Example usage:<pre><code>
18538 // Handle click on any child of an element
18539 Ext.getBody().on('click', function(e){
18540     if(e.within('some-el')){
18541         alert('Clicked on a child of some-el!');
18542     }
18543 });
18544
18545 // Handle click directly on an element, ignoring clicks on child nodes
18546 Ext.getBody().on('click', function(e,t){
18547     if((t.id == 'some-el') && !e.within(t, true)){
18548         alert('Clicked directly on some-el!');
18549     }
18550 });
18551 </code></pre>
18552      * @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
18553      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
18554      * @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
18555      * @return {Boolean}
18556      */
18557     within : function(el, related, allowEl){
18558         if(el){
18559             var t = related ? this.getRelatedTarget() : this.getTarget(),
18560                 result;
18561
18562             if (t) {
18563                 result = Ext.fly(el).contains(t);
18564                 if (!result && allowEl) {
18565                     result = t == Ext.getDom(el);
18566                 }
18567                 return result;
18568             }
18569         }
18570         return false;
18571     },
18572
18573     /**
18574      * Checks if the key pressed was a "navigation" key
18575      * @return {Boolean} True if the press is a navigation keypress
18576      */
18577     isNavKeyPress : function(){
18578         var me = this,
18579             k = this.normalizeKey(me.keyCode);
18580
18581        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
18582        k == me.RETURN ||
18583        k == me.TAB ||
18584        k == me.ESC;
18585     },
18586
18587     /**
18588      * Checks if the key pressed was a "special" key
18589      * @return {Boolean} True if the press is a special keypress
18590      */
18591     isSpecialKey : function(){
18592         var k = this.normalizeKey(this.keyCode);
18593         return (this.type == 'keypress' && this.ctrlKey) ||
18594         this.isNavKeyPress() ||
18595         (k == this.BACKSPACE) || // Backspace
18596         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
18597         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
18598     },
18599
18600     /**
18601      * Returns a point object that consists of the object coordinates.
18602      * @return {Ext.util.Point} point
18603      */
18604     getPoint : function(){
18605         var xy = this.getXY();
18606         return Ext.create('Ext.util.Point', xy[0], xy[1]);
18607     },
18608
18609    /**
18610     * Returns true if the control, meta, shift or alt key was pressed during this event.
18611     * @return {Boolean}
18612     */
18613     hasModifier : function(){
18614         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
18615     },
18616
18617     /**
18618      * Injects a DOM event using the data in this object and (optionally) a new target.
18619      * This is a low-level technique and not likely to be used by application code. The
18620      * currently supported event types are:
18621      * <p><b>HTMLEvents</b></p>
18622      * <ul>
18623      * <li>load</li>
18624      * <li>unload</li>
18625      * <li>select</li>
18626      * <li>change</li>
18627      * <li>submit</li>
18628      * <li>reset</li>
18629      * <li>resize</li>
18630      * <li>scroll</li>
18631      * </ul>
18632      * <p><b>MouseEvents</b></p>
18633      * <ul>
18634      * <li>click</li>
18635      * <li>dblclick</li>
18636      * <li>mousedown</li>
18637      * <li>mouseup</li>
18638      * <li>mouseover</li>
18639      * <li>mousemove</li>
18640      * <li>mouseout</li>
18641      * </ul>
18642      * <p><b>UIEvents</b></p>
18643      * <ul>
18644      * <li>focusin</li>
18645      * <li>focusout</li>
18646      * <li>activate</li>
18647      * <li>focus</li>
18648      * <li>blur</li>
18649      * </ul>
18650      * @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
18651      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
18652      * is used to determine the target.
18653      */
18654     injectEvent: function () {
18655         var API,
18656             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
18657
18658         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
18659
18660         // IE9 has createEvent, but this code causes major problems with htmleditor (it
18661         // blocks all mouse events and maybe more). TODO
18662
18663         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
18664             API = {
18665                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
18666                     var event = doc.createEvent('HTMLEvents');
18667
18668                     event.initEvent(type, bubbles, cancelable);
18669                     return event;
18670                 },
18671
18672                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
18673                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
18674                                             button, relatedTarget) {
18675                     var event = doc.createEvent('MouseEvents'),
18676                         view = doc.defaultView || window;
18677
18678                     if (event.initMouseEvent) {
18679                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
18680                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
18681                                     shiftKey, metaKey, button, relatedTarget);
18682                     } else { // old Safari
18683                         event = doc.createEvent('UIEvents');
18684                         event.initEvent(type, bubbles, cancelable);
18685                         event.view = view;
18686                         event.detail = detail;
18687                         event.screenX = clientX;
18688                         event.screenY = clientY;
18689                         event.clientX = clientX;
18690                         event.clientY = clientY;
18691                         event.ctrlKey = ctrlKey;
18692                         event.altKey = altKey;
18693                         event.metaKey = metaKey;
18694                         event.shiftKey = shiftKey;
18695                         event.button = button;
18696                         event.relatedTarget = relatedTarget;
18697                     }
18698
18699                     return event;
18700                 },
18701
18702                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
18703                     var event = doc.createEvent('UIEvents'),
18704                         view = doc.defaultView || window;
18705
18706                     event.initUIEvent(type, bubbles, cancelable, view, detail);
18707                     return event;
18708                 },
18709
18710                 fireEvent: function (target, type, event) {
18711                     target.dispatchEvent(event);
18712                 },
18713
18714                 fixTarget: function (target) {
18715                     // Safari3 doesn't have window.dispatchEvent()
18716                     if (target == window && !target.dispatchEvent) {
18717                         return document;
18718                     }
18719
18720                     return target;
18721                 }
18722             };
18723         } else if (document.createEventObject) { // else if (IE)
18724             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
18725
18726             API = {
18727                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
18728                     var event = doc.createEventObject();
18729                     event.bubbles = bubbles;
18730                     event.cancelable = cancelable;
18731                     return event;
18732                 },
18733
18734                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
18735                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
18736                                             button, relatedTarget) {
18737                     var event = doc.createEventObject();
18738                     event.bubbles = bubbles;
18739                     event.cancelable = cancelable;
18740                     event.detail = detail;
18741                     event.screenX = clientX;
18742                     event.screenY = clientY;
18743                     event.clientX = clientX;
18744                     event.clientY = clientY;
18745                     event.ctrlKey = ctrlKey;
18746                     event.altKey = altKey;
18747                     event.shiftKey = shiftKey;
18748                     event.metaKey = metaKey;
18749                     event.button = crazyIEButtons[button] || button;
18750                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
18751                     return event;
18752                 },
18753
18754                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
18755                     var event = doc.createEventObject();
18756                     event.bubbles = bubbles;
18757                     event.cancelable = cancelable;
18758                     return event;
18759                 },
18760
18761                 fireEvent: function (target, type, event) {
18762                     target.fireEvent('on' + type, event);
18763                 },
18764
18765                 fixTarget: function (target) {
18766                     if (target == document) {
18767                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
18768                         // IE6,IE7 cannot properly call document.fireEvent()
18769                         return document.documentElement;
18770                     }
18771
18772                     return target;
18773                 }
18774             };
18775         }
18776
18777         //----------------
18778         // HTMLEvents
18779
18780         Ext.Object.each({
18781                 load:   [false, false],
18782                 unload: [false, false],
18783                 select: [true, false],
18784                 change: [true, false],
18785                 submit: [true, true],
18786                 reset:  [true, false],
18787                 resize: [true, false],
18788                 scroll: [true, false]
18789             },
18790             function (name, value) {
18791                 var bubbles = value[0], cancelable = value[1];
18792                 dispatchers[name] = function (targetEl, srcEvent) {
18793                     var e = API.createHtmlEvent(name, bubbles, cancelable);
18794                     API.fireEvent(targetEl, name, e);
18795                 };
18796             });
18797
18798         //----------------
18799         // MouseEvents
18800
18801         function createMouseEventDispatcher (type, detail) {
18802             var cancelable = (type != 'mousemove');
18803             return function (targetEl, srcEvent) {
18804                 var xy = srcEvent.getXY(),
18805                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
18806                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
18807                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
18808                                 srcEvent.relatedTarget);
18809                 API.fireEvent(targetEl, type, e);
18810             };
18811         }
18812
18813         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
18814             function (eventName) {
18815                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
18816             });
18817
18818         //----------------
18819         // UIEvents
18820
18821         Ext.Object.each({
18822                 focusin:  [true, false],
18823                 focusout: [true, false],
18824                 activate: [true, true],
18825                 focus:    [false, false],
18826                 blur:     [false, false]
18827             },
18828             function (name, value) {
18829                 var bubbles = value[0], cancelable = value[1];
18830                 dispatchers[name] = function (targetEl, srcEvent) {
18831                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
18832                     API.fireEvent(targetEl, name, e);
18833                 };
18834             });
18835
18836         //---------
18837         if (!API) {
18838             // not even sure what ancient browsers fall into this category...
18839
18840             dispatchers = {}; // never mind all those we just built :P
18841
18842             API = {
18843                 fixTarget: function (t) {
18844                     return t;
18845                 }
18846             };
18847         }
18848
18849         function cannotInject (target, srcEvent) {
18850         }
18851
18852         return function (target) {
18853             var me = this,
18854                 dispatcher = dispatchers[me.type] || cannotInject,
18855                 t = target ? (target.dom || target) : me.getTarget();
18856
18857             t = API.fixTarget(t);
18858             dispatcher(t, me);
18859         };
18860     }() // call to produce method
18861
18862 }, function() {
18863
18864 Ext.EventObject = new Ext.EventObjectImpl();
18865
18866 });
18867
18868
18869 /**
18870  * @class Ext.Element
18871  */
18872 (function(){
18873     var doc = document,
18874         activeElement = null,
18875         isCSS1 = doc.compatMode == "CSS1Compat",
18876         ELEMENT = Ext.Element,
18877         fly = function(el){
18878             if (!_fly) {
18879                 _fly = new Ext.Element.Flyweight();
18880             }
18881             _fly.dom = el;
18882             return _fly;
18883         }, _fly;
18884
18885     // If the browser does not support document.activeElement we need some assistance.
18886     // This covers old Safari 3.2 (4.0 added activeElement along with just about all
18887     // other browsers). We need this support to handle issues with old Safari.
18888     if (!('activeElement' in doc) && doc.addEventListener) {
18889         doc.addEventListener('focus',
18890             function (ev) {
18891                 if (ev && ev.target) {
18892                     activeElement = (ev.target == doc) ? null : ev.target;
18893                 }
18894             }, true);
18895     }
18896
18897     /*
18898      * Helper function to create the function that will restore the selection.
18899      */
18900     function makeSelectionRestoreFn (activeEl, start, end) {
18901         return function () {
18902             activeEl.selectionStart = start;
18903             activeEl.selectionEnd = end;
18904         };
18905     }
18906
18907     Ext.apply(ELEMENT, {
18908         isAncestor : function(p, c) {
18909             var ret = false;
18910
18911             p = Ext.getDom(p);
18912             c = Ext.getDom(c);
18913             if (p && c) {
18914                 if (p.contains) {
18915                     return p.contains(c);
18916                 } else if (p.compareDocumentPosition) {
18917                     return !!(p.compareDocumentPosition(c) & 16);
18918                 } else {
18919                     while ((c = c.parentNode)) {
18920                         ret = c == p || ret;
18921                     }
18922                 }
18923             }
18924             return ret;
18925         },
18926
18927         /**
18928          * Returns the active element in the DOM. If the browser supports activeElement
18929          * on the document, this is returned. If not, the focus is tracked and the active
18930          * element is maintained internally.
18931          * @return {HTMLElement} The active (focused) element in the document.
18932          */
18933         getActiveElement: function () {
18934             return doc.activeElement || activeElement;
18935         },
18936
18937         /**
18938          * Creates a function to call to clean up problems with the work-around for the
18939          * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
18940          * the element before calling getComputedStyle and then to restore its original
18941          * display value. The problem with this is that it corrupts the selection of an
18942          * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
18943          * To cleanup after this, we need to capture the selection of any such element and
18944          * then restore it after we have restored the display style.
18945          *
18946          * @param target {Element} The top-most element being adjusted.
18947          * @private
18948          */
18949         getRightMarginFixCleaner: function (target) {
18950             var supports = Ext.supports,
18951                 hasInputBug = supports.DisplayChangeInputSelectionBug,
18952                 hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug;
18953
18954             if (hasInputBug || hasTextAreaBug) {
18955                 var activeEl = doc.activeElement || activeElement, // save a call
18956                     tag = activeEl && activeEl.tagName,
18957                     start,
18958                     end;
18959
18960                 if ((hasTextAreaBug && tag == 'TEXTAREA') ||
18961                     (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
18962                     if (ELEMENT.isAncestor(target, activeEl)) {
18963                         start = activeEl.selectionStart;
18964                         end = activeEl.selectionEnd;
18965
18966                         if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
18967                             // We don't create the raw closure here inline because that
18968                             // will be costly even if we don't want to return it (nested
18969                             // function decls and exprs are often instantiated on entry
18970                             // regardless of whether execution ever reaches them):
18971                             return makeSelectionRestoreFn(activeEl, start, end);
18972                         }
18973                     }
18974                 }
18975             }
18976
18977             return Ext.emptyFn; // avoid special cases, just return a nop
18978         },
18979
18980         getViewWidth : function(full) {
18981             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
18982         },
18983
18984         getViewHeight : function(full) {
18985             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
18986         },
18987
18988         getDocumentHeight: function() {
18989             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
18990         },
18991
18992         getDocumentWidth: function() {
18993             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
18994         },
18995
18996         getViewportHeight: function(){
18997             return Ext.isIE ?
18998                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
18999                    self.innerHeight;
19000         },
19001
19002         getViewportWidth : function() {
19003             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
19004                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
19005         },
19006
19007         getY : function(el) {
19008             return ELEMENT.getXY(el)[1];
19009         },
19010
19011         getX : function(el) {
19012             return ELEMENT.getXY(el)[0];
19013         },
19014
19015         getOffsetParent: function (el) {
19016             el = Ext.getDom(el);
19017             try {
19018                 // accessing offsetParent can throw "Unspecified Error" in IE6-8 (not 9)
19019                 return el.offsetParent;
19020             } catch (e) {
19021                 var body = document.body; // safe bet, unless...
19022                 return (el == body) ? null : body;
19023             }
19024         },
19025
19026         getXY : function(el) {
19027             var p,
19028                 pe,
19029                 b,
19030                 bt,
19031                 bl,
19032                 dbd,
19033                 x = 0,
19034                 y = 0,
19035                 scroll,
19036                 hasAbsolute,
19037                 bd = (doc.body || doc.documentElement),
19038                 ret;
19039
19040             el = Ext.getDom(el);
19041
19042             if(el != bd){
19043                 hasAbsolute = fly(el).isStyle("position", "absolute");
19044
19045                 if (el.getBoundingClientRect) {
19046                     try {
19047                         b = el.getBoundingClientRect();
19048                         scroll = fly(document).getScroll();
19049                         ret = [ Math.round(b.left + scroll.left), Math.round(b.top + scroll.top) ];
19050                     } catch (e) {
19051                         // IE6-8 can also throw from getBoundingClientRect...
19052                     }
19053                 }
19054
19055                 if (!ret) {
19056                     for (p = el; p; p = ELEMENT.getOffsetParent(p)) {
19057                         pe = fly(p);
19058                         x += p.offsetLeft;
19059                         y += p.offsetTop;
19060
19061                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
19062
19063                         if (Ext.isGecko) {
19064                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
19065                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
19066
19067                             if (p != el && !pe.isStyle('overflow','visible')) {
19068                                 x += bl;
19069                                 y += bt;
19070                             }
19071                         }
19072                     }
19073
19074                     if (Ext.isSafari && hasAbsolute) {
19075                         x -= bd.offsetLeft;
19076                         y -= bd.offsetTop;
19077                     }
19078
19079                     if (Ext.isGecko && !hasAbsolute) {
19080                         dbd = fly(bd);
19081                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
19082                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
19083                     }
19084
19085                     p = el.parentNode;
19086                     while (p && p != bd) {
19087                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
19088                             x -= p.scrollLeft;
19089                             y -= p.scrollTop;
19090                         }
19091                         p = p.parentNode;
19092                     }
19093                     ret = [x,y];
19094                 }
19095             }
19096             return ret || [0,0];
19097         },
19098
19099         setXY : function(el, xy) {
19100             (el = Ext.fly(el, '_setXY')).position();
19101
19102             var pts = el.translatePoints(xy),
19103                 style = el.dom.style,
19104                 pos;
19105
19106             for (pos in pts) {
19107                 if (!isNaN(pts[pos])) {
19108                     style[pos] = pts[pos] + "px";
19109                 }
19110             }
19111         },
19112
19113         setX : function(el, x) {
19114             ELEMENT.setXY(el, [x, false]);
19115         },
19116
19117         setY : function(el, y) {
19118             ELEMENT.setXY(el, [false, y]);
19119         },
19120
19121         /**
19122          * Serializes a DOM form into a url encoded string
19123          * @param {Object} form The form
19124          * @return {String} The url encoded form
19125          */
19126         serializeForm: function(form) {
19127             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
19128                 hasSubmit = false,
19129                 encoder = encodeURIComponent,
19130                 name,
19131                 data = '',
19132                 type,
19133                 hasValue;
19134
19135             Ext.each(fElements, function(element){
19136                 name = element.name;
19137                 type = element.type;
19138
19139                 if (!element.disabled && name) {
19140                     if (/select-(one|multiple)/i.test(type)) {
19141                         Ext.each(element.options, function(opt){
19142                             if (opt.selected) {
19143                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
19144                                 data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
19145                             }
19146                         });
19147                     } else if (!(/file|undefined|reset|button/i.test(type))) {
19148                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
19149                             data += encoder(name) + '=' + encoder(element.value) + '&';
19150                             hasSubmit = /submit/i.test(type);
19151                         }
19152                     }
19153                 }
19154             });
19155             return data.substr(0, data.length - 1);
19156         }
19157     });
19158 })();
19159
19160 /**
19161  * @class Ext.Element
19162  */
19163
19164 Ext.Element.addMethods((function(){
19165     var focusRe = /button|input|textarea|select|object/;
19166     return {
19167         /**
19168          * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
19169          * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
19170          * back in, the function is not called.
19171          * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
19172          * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
19173          * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
19174          * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
19175 // Hide the menu if the mouse moves out for 250ms or more
19176 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
19177
19178 ...
19179 // Remove mouseleave monitor on menu destroy
19180 this.menuEl.un(this.mouseLeaveMonitor);
19181     </code></pre>
19182          */
19183         monitorMouseLeave: function(delay, handler, scope) {
19184             var me = this,
19185                 timer,
19186                 listeners = {
19187                     mouseleave: function(e) {
19188                         timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
19189                     },
19190                     mouseenter: function() {
19191                         clearTimeout(timer);
19192                     },
19193                     freezeEvent: true
19194                 };
19195
19196             me.on(listeners);
19197             return listeners;
19198         },
19199
19200         /**
19201          * Stops the specified event(s) from bubbling and optionally prevents the default action
19202          * @param {String/String[]} eventName an event / array of events to stop from bubbling
19203          * @param {Boolean} preventDefault (optional) true to prevent the default action too
19204          * @return {Ext.Element} this
19205          */
19206         swallowEvent : function(eventName, preventDefault) {
19207             var me = this;
19208             function fn(e) {
19209                 e.stopPropagation();
19210                 if (preventDefault) {
19211                     e.preventDefault();
19212                 }
19213             }
19214
19215             if (Ext.isArray(eventName)) {
19216                 Ext.each(eventName, function(e) {
19217                      me.on(e, fn);
19218                 });
19219                 return me;
19220             }
19221             me.on(eventName, fn);
19222             return me;
19223         },
19224
19225         /**
19226          * Create an event handler on this element such that when the event fires and is handled by this element,
19227          * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
19228          * @param {String} eventName The type of event to relay
19229          * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
19230          * for firing the relayed event
19231          */
19232         relayEvent : function(eventName, observable) {
19233             this.on(eventName, function(e) {
19234                 observable.fireEvent(eventName, e);
19235             });
19236         },
19237
19238         /**
19239          * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
19240          * @param {Boolean} forceReclean (optional) By default the element
19241          * keeps track if it has been cleaned already so
19242          * you can call this over and over. However, if you update the element and
19243          * need to force a reclean, you can pass true.
19244          */
19245         clean : function(forceReclean) {
19246             var me  = this,
19247                 dom = me.dom,
19248                 n   = dom.firstChild,
19249                 nx,
19250                 ni  = -1;
19251     
19252             if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
19253                 return me;
19254             }
19255
19256             while (n) {
19257                 nx = n.nextSibling;
19258                 if (n.nodeType == 3) {
19259                     // Remove empty/whitespace text nodes
19260                     if (!(/\S/.test(n.nodeValue))) {
19261                         dom.removeChild(n);
19262                     // Combine adjacent text nodes
19263                     } else if (nx && nx.nodeType == 3) {
19264                         n.appendData(Ext.String.trim(nx.data));
19265                         dom.removeChild(nx);
19266                         nx = n.nextSibling;
19267                         n.nodeIndex = ++ni;
19268                     }
19269                 } else {
19270                     // Recursively clean
19271                     Ext.fly(n).clean();
19272                     n.nodeIndex = ++ni;
19273                 }
19274                 n = nx;
19275             }
19276
19277             Ext.Element.data(dom, 'isCleaned', true);
19278             return me;
19279         },
19280
19281         /**
19282          * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
19283          * parameter as {@link Ext.ElementLoader#load}
19284          * @return {Ext.Element} this
19285          */
19286         load : function(options) {
19287             this.getLoader().load(options);
19288             return this;
19289         },
19290
19291         /**
19292         * Gets this element's {@link Ext.ElementLoader ElementLoader}
19293         * @return {Ext.ElementLoader} The loader
19294         */
19295         getLoader : function() {
19296             var dom = this.dom,
19297                 data = Ext.Element.data,
19298                 loader = data(dom, 'loader');
19299     
19300             if (!loader) {
19301                 loader = Ext.create('Ext.ElementLoader', {
19302                     target: this
19303                 });
19304                 data(dom, 'loader', loader);
19305             }
19306             return loader;
19307         },
19308
19309         /**
19310         * Update the innerHTML of this element, optionally searching for and processing scripts
19311         * @param {String} html The new HTML
19312         * @param {Boolean} [loadScripts=false] True to look for and process scripts
19313         * @param {Function} [callback] For async script loading you can be notified when the update completes
19314         * @return {Ext.Element} this
19315          */
19316         update : function(html, loadScripts, callback) {
19317             var me = this,
19318                 id,
19319                 dom,
19320                 interval;
19321
19322             if (!me.dom) {
19323                 return me;
19324             }
19325             html = html || '';
19326             dom = me.dom;
19327
19328             if (loadScripts !== true) {
19329                 dom.innerHTML = html;
19330                 Ext.callback(callback, me);
19331                 return me;
19332             }
19333
19334             id  = Ext.id();
19335             html += '<span id="' + id + '"></span>';
19336
19337             interval = setInterval(function(){
19338                 if (!document.getElementById(id)) {
19339                     return false;
19340                 }
19341                 clearInterval(interval);
19342                 var DOC    = document,
19343                     hd     = DOC.getElementsByTagName("head")[0],
19344                     re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
19345                     srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
19346                     typeRe = /\stype=([\'\"])(.*?)\1/i,
19347                     match,
19348                     attrs,
19349                     srcMatch,
19350                     typeMatch,
19351                     el,
19352                     s;
19353
19354                 while ((match = re.exec(html))) {
19355                     attrs = match[1];
19356                     srcMatch = attrs ? attrs.match(srcRe) : false;
19357                     if (srcMatch && srcMatch[2]) {
19358                        s = DOC.createElement("script");
19359                        s.src = srcMatch[2];
19360                        typeMatch = attrs.match(typeRe);
19361                        if (typeMatch && typeMatch[2]) {
19362                            s.type = typeMatch[2];
19363                        }
19364                        hd.appendChild(s);
19365                     } else if (match[2] && match[2].length > 0) {
19366                         if (window.execScript) {
19367                            window.execScript(match[2]);
19368                         } else {
19369                            window.eval(match[2]);
19370                         }
19371                     }
19372                 }
19373
19374                 el = DOC.getElementById(id);
19375                 if (el) {
19376                     Ext.removeNode(el);
19377                 }
19378                 Ext.callback(callback, me);
19379             }, 20);
19380             dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
19381             return me;
19382         },
19383
19384         // inherit docs, overridden so we can add removeAnchor
19385         removeAllListeners : function() {
19386             this.removeAnchor();
19387             Ext.EventManager.removeAll(this.dom);
19388             return this;
19389         },
19390     
19391         /**
19392          * Gets the parent node of the current element taking into account Ext.scopeResetCSS
19393          * @protected
19394          * @return {HTMLElement} The parent element
19395          */
19396         getScopeParent: function(){
19397             var parent = this.dom.parentNode;
19398             return Ext.scopeResetCSS ? parent.parentNode : parent;
19399         },
19400
19401         /**
19402          * Creates a proxy element of this element
19403          * @param {String/Object} config The class name of the proxy element or a DomHelper config object
19404          * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
19405          * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
19406          * @return {Ext.Element} The new proxy element
19407          */
19408         createProxy : function(config, renderTo, matchBox) {
19409             config = (typeof config == 'object') ? config : {tag : "div", cls: config};
19410
19411             var me = this,
19412                 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
19413                                    Ext.DomHelper.insertBefore(me.dom, config, true);
19414
19415             proxy.setVisibilityMode(Ext.Element.DISPLAY);
19416             proxy.hide();
19417             if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
19418                proxy.setBox(me.getBox());
19419             }
19420             return proxy;
19421         },
19422     
19423         /**
19424          * Checks whether this element can be focused.
19425          * @return {Boolean} True if the element is focusable
19426          */
19427         focusable: function(){
19428             var dom = this.dom,
19429                 nodeName = dom.nodeName.toLowerCase(),
19430                 canFocus = false,
19431                 hasTabIndex = !isNaN(dom.tabIndex);
19432             
19433             if (!dom.disabled) {
19434                 if (focusRe.test(nodeName)) {
19435                     canFocus = true;
19436                 } else {
19437                     canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
19438                 }
19439             }
19440             return canFocus && this.isVisible(true);
19441         }    
19442     };
19443 })());
19444 Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;
19445
19446 /**
19447  * @class Ext.Element
19448  */
19449 Ext.Element.addMethods({
19450     /**
19451      * Gets the x,y coordinates specified by the anchor position on the element.
19452      * @param {String} [anchor='c'] The specified anchor position.  See {@link #alignTo}
19453      * for details on supported anchor positions.
19454      * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
19455      * of page coordinates
19456      * @param {Object} [size] An object containing the size to use for calculating anchor position
19457      * {width: (target width), height: (target height)} (defaults to the element's current size)
19458      * @return {Number[]} [x, y] An array containing the element's x and y coordinates
19459      */
19460     getAnchorXY : function(anchor, local, s){
19461         //Passing a different size is useful for pre-calculating anchors,
19462         //especially for anchored animations that change the el size.
19463         anchor = (anchor || "tl").toLowerCase();
19464         s = s || {};
19465
19466         var me = this,
19467             vp = me.dom == document.body || me.dom == document,
19468             w = s.width || vp ? Ext.Element.getViewWidth() : me.getWidth(),
19469             h = s.height || vp ? Ext.Element.getViewHeight() : me.getHeight(),
19470             xy,
19471             r = Math.round,
19472             o = me.getXY(),
19473             scroll = me.getScroll(),
19474             extraX = vp ? scroll.left : !local ? o[0] : 0,
19475             extraY = vp ? scroll.top : !local ? o[1] : 0,
19476             hash = {
19477                 c  : [r(w * 0.5), r(h * 0.5)],
19478                 t  : [r(w * 0.5), 0],
19479                 l  : [0, r(h * 0.5)],
19480                 r  : [w, r(h * 0.5)],
19481                 b  : [r(w * 0.5), h],
19482                 tl : [0, 0],
19483                 bl : [0, h],
19484                 br : [w, h],
19485                 tr : [w, 0]
19486             };
19487
19488         xy = hash[anchor];
19489         return [xy[0] + extraX, xy[1] + extraY];
19490     },
19491
19492     /**
19493      * Anchors an element to another element and realigns it when the window is resized.
19494      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19495      * @param {String} position The position to align to.
19496      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19497      * @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
19498      * @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
19499      * is a number, it is used as the buffer delay (defaults to 50ms).
19500      * @param {Function} [callback] The function to call after the animation finishes
19501      * @return {Ext.Element} this
19502      */
19503     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
19504         var me = this,
19505             dom = me.dom,
19506             scroll = !Ext.isEmpty(monitorScroll),
19507             action = function(){
19508                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
19509                 Ext.callback(callback, Ext.fly(dom));
19510             },
19511             anchor = this.getAnchor();
19512
19513         // previous listener anchor, remove it
19514         this.removeAnchor();
19515         Ext.apply(anchor, {
19516             fn: action,
19517             scroll: scroll
19518         });
19519
19520         Ext.EventManager.onWindowResize(action, null);
19521
19522         if(scroll){
19523             Ext.EventManager.on(window, 'scroll', action, null,
19524                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
19525         }
19526         action.call(me); // align immediately
19527         return me;
19528     },
19529
19530     /**
19531      * Remove any anchor to this element. See {@link #anchorTo}.
19532      * @return {Ext.Element} this
19533      */
19534     removeAnchor : function(){
19535         var me = this,
19536             anchor = this.getAnchor();
19537
19538         if(anchor && anchor.fn){
19539             Ext.EventManager.removeResizeListener(anchor.fn);
19540             if(anchor.scroll){
19541                 Ext.EventManager.un(window, 'scroll', anchor.fn);
19542             }
19543             delete anchor.fn;
19544         }
19545         return me;
19546     },
19547
19548     // private
19549     getAnchor : function(){
19550         var data = Ext.Element.data,
19551             dom = this.dom;
19552             if (!dom) {
19553                 return;
19554             }
19555             var anchor = data(dom, '_anchor');
19556
19557         if(!anchor){
19558             anchor = data(dom, '_anchor', {});
19559         }
19560         return anchor;
19561     },
19562
19563     getAlignVector: function(el, spec, offset) {
19564         var me = this,
19565             side = {t:"top", l:"left", r:"right", b: "bottom"},
19566             thisRegion = me.getRegion(),
19567             elRegion;
19568
19569         el = Ext.get(el);
19570         if(!el || !el.dom){
19571         }
19572
19573         elRegion = el.getRegion();
19574     },
19575
19576     /**
19577      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
19578      * supported position values.
19579      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19580      * @param {String} [position="tl-bl?"] The position to align to (defaults to )
19581      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19582      * @return {Number[]} [x, y]
19583      */
19584     getAlignToXY : function(el, p, o){
19585         el = Ext.get(el);
19586
19587         if(!el || !el.dom){
19588         }
19589
19590         o = o || [0,0];
19591         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
19592
19593         var me = this,
19594             d = me.dom,
19595             a1,
19596             a2,
19597             x,
19598             y,
19599             //constrain the aligned el to viewport if necessary
19600             w,
19601             h,
19602             r,
19603             dw = Ext.Element.getViewWidth() -10, // 10px of margin for ie
19604             dh = Ext.Element.getViewHeight()-10, // 10px of margin for ie
19605             p1y,
19606             p1x,
19607             p2y,
19608             p2x,
19609             swapY,
19610             swapX,
19611             doc = document,
19612             docElement = doc.documentElement,
19613             docBody = doc.body,
19614             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
19615             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
19616             c = false, //constrain to viewport
19617             p1 = "",
19618             p2 = "",
19619             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
19620
19621         if(!m){
19622         }
19623
19624         p1 = m[1];
19625         p2 = m[2];
19626         c = !!m[3];
19627
19628         //Subtract the aligned el's internal xy from the target's offset xy
19629         //plus custom offset to get the aligned el's new offset xy
19630         a1 = me.getAnchorXY(p1, true);
19631         a2 = el.getAnchorXY(p2, false);
19632
19633         x = a2[0] - a1[0] + o[0];
19634         y = a2[1] - a1[1] + o[1];
19635
19636         if(c){
19637            w = me.getWidth();
19638            h = me.getHeight();
19639            r = el.getRegion();
19640            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
19641            //perpendicular to the vp border, allow the aligned el to slide on that border,
19642            //otherwise swap the aligned el to the opposite border of the target.
19643            p1y = p1.charAt(0);
19644            p1x = p1.charAt(p1.length-1);
19645            p2y = p2.charAt(0);
19646            p2x = p2.charAt(p2.length-1);
19647            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
19648            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
19649
19650
19651            if (x + w > dw + scrollX) {
19652                 x = swapX ? r.left-w : dw+scrollX-w;
19653            }
19654            if (x < scrollX) {
19655                x = swapX ? r.right : scrollX;
19656            }
19657            if (y + h > dh + scrollY) {
19658                 y = swapY ? r.top-h : dh+scrollY-h;
19659             }
19660            if (y < scrollY){
19661                y = swapY ? r.bottom : scrollY;
19662            }
19663         }
19664         return [x,y];
19665     },
19666
19667     /**
19668      * Aligns this element with another element relative to the specified anchor points. If the other element is the
19669      * document it aligns it to the viewport.
19670      * The position parameter is optional, and can be specified in any one of the following formats:
19671      * <ul>
19672      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
19673      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
19674      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
19675      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
19676      *   <li><b>Two anchors</b>: If two values from the table below are passed separated by a dash, the first value is used as the
19677      *       element's anchor point, and the second value is used as the target's anchor point.</li>
19678      * </ul>
19679      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
19680      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
19681      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
19682      * that specified in order to enforce the viewport constraints.
19683      * Following are all of the supported anchor positions:
19684 <pre>
19685 Value  Description
19686 -----  -----------------------------
19687 tl     The top left corner (default)
19688 t      The center of the top edge
19689 tr     The top right corner
19690 l      The center of the left edge
19691 c      In the center of the element
19692 r      The center of the right edge
19693 bl     The bottom left corner
19694 b      The center of the bottom edge
19695 br     The bottom right corner
19696 </pre>
19697 Example Usage:
19698 <pre><code>
19699 // align el to other-el using the default positioning ("tl-bl", non-constrained)
19700 el.alignTo("other-el");
19701
19702 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
19703 el.alignTo("other-el", "tr?");
19704
19705 // align the bottom right corner of el with the center left edge of other-el
19706 el.alignTo("other-el", "br-l?");
19707
19708 // align the center of el with the bottom left corner of other-el and
19709 // adjust the x position by -6 pixels (and the y position by 0)
19710 el.alignTo("other-el", "c-bl", [-6, 0]);
19711 </code></pre>
19712      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19713      * @param {String} [position="tl-bl?"] The position to align to
19714      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19715      * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
19716      * @return {Ext.Element} this
19717      */
19718     alignTo : function(element, position, offsets, animate){
19719         var me = this;
19720         return me.setXY(me.getAlignToXY(element, position, offsets),
19721                         me.anim && !!animate ? me.anim(animate) : false);
19722     },
19723
19724     // private ==>  used outside of core
19725     adjustForConstraints : function(xy, parent) {
19726         var vector = this.getConstrainVector(parent, xy);
19727         if (vector) {
19728             xy[0] += vector[0];
19729             xy[1] += vector[1];
19730         }
19731         return xy;
19732     },
19733
19734     /**
19735      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
19736      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
19737      * <p>Priority is given to constraining the top and left within the constraint.</p>
19738      * <p>The constraint may either be an existing element into which this element is to be constrained, or
19739      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
19740      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
19741      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
19742      * of using this Element's current position;
19743      * @returns {Number[]/Boolean} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
19744      * vector by which this element must be translated. Otherwise, <code>false</code>.
19745      */
19746     getConstrainVector: function(constrainTo, proposedPosition) {
19747         if (!(constrainTo instanceof Ext.util.Region)) {
19748             constrainTo = Ext.get(constrainTo).getViewRegion();
19749         }
19750         var thisRegion = this.getRegion(),
19751             vector = [0, 0],
19752             shadowSize = this.shadow && this.shadow.offset,
19753             overflowed = false;
19754
19755         // Shift this region to occupy the proposed position
19756         if (proposedPosition) {
19757             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
19758         }
19759
19760         // Reduce the constrain region to allow for shadow
19761         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
19762         if (shadowSize) {
19763             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
19764         }
19765
19766         // Constrain the X coordinate by however much this Element overflows
19767         if (thisRegion.right > constrainTo.right) {
19768             overflowed = true;
19769             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
19770         }
19771         if (thisRegion.left + vector[0] < constrainTo.left) {
19772             overflowed = true;
19773             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
19774         }
19775
19776         // Constrain the Y coordinate by however much this Element overflows
19777         if (thisRegion.bottom > constrainTo.bottom) {
19778             overflowed = true;
19779             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
19780         }
19781         if (thisRegion.top + vector[1] < constrainTo.top) {
19782             overflowed = true;
19783             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
19784         }
19785         return overflowed ? vector : false;
19786     },
19787
19788     /**
19789     * Calculates the x, y to center this element on the screen
19790     * @return {Number[]} The x, y values [x, y]
19791     */
19792     getCenterXY : function(){
19793         return this.getAlignToXY(document, 'c-c');
19794     },
19795
19796     /**
19797     * Centers the Element in either the viewport, or another Element.
19798     * @param {String/HTMLElement/Ext.Element} centerIn (optional) The element in which to center the element.
19799     */
19800     center : function(centerIn){
19801         return this.alignTo(centerIn || document, 'c-c');
19802     }
19803 });
19804
19805 /**
19806  * @class Ext.Element
19807  */
19808 (function(){
19809
19810 var ELEMENT = Ext.Element,
19811     LEFT = "left",
19812     RIGHT = "right",
19813     TOP = "top",
19814     BOTTOM = "bottom",
19815     POSITION = "position",
19816     STATIC = "static",
19817     RELATIVE = "relative",
19818     AUTO = "auto",
19819     ZINDEX = "z-index";
19820
19821 Ext.override(Ext.Element, {
19822     /**
19823       * Gets the current X position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19824       * @return {Number} The X position of the element
19825       */
19826     getX : function(){
19827         return ELEMENT.getX(this.dom);
19828     },
19829
19830     /**
19831       * Gets the current Y position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19832       * @return {Number} The Y position of the element
19833       */
19834     getY : function(){
19835         return ELEMENT.getY(this.dom);
19836     },
19837
19838     /**
19839       * Gets the current position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19840       * @return {Number[]} The XY position of the element
19841       */
19842     getXY : function(){
19843         return ELEMENT.getXY(this.dom);
19844     },
19845
19846     /**
19847       * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
19848       * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
19849       * @return {Number[]} The XY page offsets (e.g. [100, -200])
19850       */
19851     getOffsetsTo : function(el){
19852         var o = this.getXY(),
19853             e = Ext.fly(el, '_internal').getXY();
19854         return [o[0]-e[0],o[1]-e[1]];
19855     },
19856
19857     /**
19858      * Sets the X position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19859      * @param {Number} The X position of the element
19860      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19861      * @return {Ext.Element} this
19862      */
19863     setX : function(x, animate){
19864         return this.setXY([x, this.getY()], animate);
19865     },
19866
19867     /**
19868      * Sets the Y position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19869      * @param {Number} The Y position of the element
19870      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19871      * @return {Ext.Element} this
19872      */
19873     setY : function(y, animate){
19874         return this.setXY([this.getX(), y], animate);
19875     },
19876
19877     /**
19878      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
19879      * @param {String} left The left CSS property value
19880      * @return {Ext.Element} this
19881      */
19882     setLeft : function(left){
19883         this.setStyle(LEFT, this.addUnits(left));
19884         return this;
19885     },
19886
19887     /**
19888      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
19889      * @param {String} top The top CSS property value
19890      * @return {Ext.Element} this
19891      */
19892     setTop : function(top){
19893         this.setStyle(TOP, this.addUnits(top));
19894         return this;
19895     },
19896
19897     /**
19898      * Sets the element's CSS right style.
19899      * @param {String} right The right CSS property value
19900      * @return {Ext.Element} this
19901      */
19902     setRight : function(right){
19903         this.setStyle(RIGHT, this.addUnits(right));
19904         return this;
19905     },
19906
19907     /**
19908      * Sets the element's CSS bottom style.
19909      * @param {String} bottom The bottom CSS property value
19910      * @return {Ext.Element} this
19911      */
19912     setBottom : function(bottom){
19913         this.setStyle(BOTTOM, this.addUnits(bottom));
19914         return this;
19915     },
19916
19917     /**
19918      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19919      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19920      * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
19921      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19922      * @return {Ext.Element} this
19923      */
19924     setXY: function(pos, animate) {
19925         var me = this;
19926         if (!animate || !me.anim) {
19927             ELEMENT.setXY(me.dom, pos);
19928         }
19929         else {
19930             if (!Ext.isObject(animate)) {
19931                 animate = {};
19932             }
19933             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
19934         }
19935         return me;
19936     },
19937
19938     /**
19939      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19940      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19941      * @param {Number} x X value for new position (coordinates are page-based)
19942      * @param {Number} y Y value for new position (coordinates are page-based)
19943      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19944      * @return {Ext.Element} this
19945      */
19946     setLocation : function(x, y, animate){
19947         return this.setXY([x, y], animate);
19948     },
19949
19950     /**
19951      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19952      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19953      * @param {Number} x X value for new position (coordinates are page-based)
19954      * @param {Number} y Y value for new position (coordinates are page-based)
19955      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19956      * @return {Ext.Element} this
19957      */
19958     moveTo : function(x, y, animate){
19959         return this.setXY([x, y], animate);
19960     },
19961
19962     /**
19963      * Gets the left X coordinate
19964      * @param {Boolean} local True to get the local css position instead of page coordinate
19965      * @return {Number}
19966      */
19967     getLeft : function(local){
19968         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
19969     },
19970
19971     /**
19972      * Gets the right X coordinate of the element (element X position + element width)
19973      * @param {Boolean} local True to get the local css position instead of page coordinate
19974      * @return {Number}
19975      */
19976     getRight : function(local){
19977         var me = this;
19978         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
19979     },
19980
19981     /**
19982      * Gets the top Y coordinate
19983      * @param {Boolean} local True to get the local css position instead of page coordinate
19984      * @return {Number}
19985      */
19986     getTop : function(local) {
19987         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
19988     },
19989
19990     /**
19991      * Gets the bottom Y coordinate of the element (element Y position + element height)
19992      * @param {Boolean} local True to get the local css position instead of page coordinate
19993      * @return {Number}
19994      */
19995     getBottom : function(local){
19996         var me = this;
19997         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
19998     },
19999
20000     /**
20001     * Initializes positioning on this element. If a desired position is not passed, it will make the
20002     * the element positioned relative IF it is not already positioned.
20003     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
20004     * @param {Number} zIndex (optional) The zIndex to apply
20005     * @param {Number} x (optional) Set the page X position
20006     * @param {Number} y (optional) Set the page Y position
20007     */
20008     position : function(pos, zIndex, x, y) {
20009         var me = this;
20010
20011         if (!pos && me.isStyle(POSITION, STATIC)){
20012             me.setStyle(POSITION, RELATIVE);
20013         } else if(pos) {
20014             me.setStyle(POSITION, pos);
20015         }
20016         if (zIndex){
20017             me.setStyle(ZINDEX, zIndex);
20018         }
20019         if (x || y) {
20020             me.setXY([x || false, y || false]);
20021         }
20022     },
20023
20024     /**
20025     * Clear positioning back to the default when the document was loaded
20026     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
20027     * @return {Ext.Element} this
20028      */
20029     clearPositioning : function(value){
20030         value = value || '';
20031         this.setStyle({
20032             left : value,
20033             right : value,
20034             top : value,
20035             bottom : value,
20036             "z-index" : "",
20037             position : STATIC
20038         });
20039         return this;
20040     },
20041
20042     /**
20043     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
20044     * snapshot before performing an update and then restoring the element.
20045     * @return {Object}
20046     */
20047     getPositioning : function(){
20048         var l = this.getStyle(LEFT);
20049         var t = this.getStyle(TOP);
20050         return {
20051             "position" : this.getStyle(POSITION),
20052             "left" : l,
20053             "right" : l ? "" : this.getStyle(RIGHT),
20054             "top" : t,
20055             "bottom" : t ? "" : this.getStyle(BOTTOM),
20056             "z-index" : this.getStyle(ZINDEX)
20057         };
20058     },
20059
20060     /**
20061     * Set positioning with an object returned by getPositioning().
20062     * @param {Object} posCfg
20063     * @return {Ext.Element} this
20064      */
20065     setPositioning : function(pc){
20066         var me = this,
20067             style = me.dom.style;
20068
20069         me.setStyle(pc);
20070
20071         if(pc.right == AUTO){
20072             style.right = "";
20073         }
20074         if(pc.bottom == AUTO){
20075             style.bottom = "";
20076         }
20077
20078         return me;
20079     },
20080
20081     /**
20082      * Translates the passed page coordinates into left/top css values for this element
20083      * @param {Number/Number[]} x The page x or an array containing [x, y]
20084      * @param {Number} y (optional) The page y, required if x is not an array
20085      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
20086      */
20087     translatePoints: function(x, y) {
20088         if (Ext.isArray(x)) {
20089              y = x[1];
20090              x = x[0];
20091         }
20092         var me = this,
20093             relative = me.isStyle(POSITION, RELATIVE),
20094             o = me.getXY(),
20095             left = parseInt(me.getStyle(LEFT), 10),
20096             top = parseInt(me.getStyle(TOP), 10);
20097
20098         if (!Ext.isNumber(left)) {
20099             left = relative ? 0 : me.dom.offsetLeft;
20100         }
20101         if (!Ext.isNumber(top)) {
20102             top = relative ? 0 : me.dom.offsetTop;
20103         }
20104         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
20105         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
20106         return {
20107             left: left,
20108             top: top
20109         };
20110     },
20111
20112     /**
20113      * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
20114      * @param {Object} box The box to fill {x, y, width, height}
20115      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
20116      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20117      * @return {Ext.Element} this
20118      */
20119     setBox: function(box, adjust, animate) {
20120         var me = this,
20121             w = box.width,
20122             h = box.height;
20123         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
20124             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
20125             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
20126         }
20127         me.setBounds(box.x, box.y, w, h, animate);
20128         return me;
20129     },
20130
20131     /**
20132      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20133      * set another Element's size/location to match this element.
20134      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
20135      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
20136      * @return {Object} box An object in the format<pre><code>
20137 {
20138     x: &lt;Element's X position>,
20139     y: &lt;Element's Y position>,
20140     width: &lt;Element's width>,
20141     height: &lt;Element's height>,
20142     bottom: &lt;Element's lower bound>,
20143     right: &lt;Element's rightmost bound>
20144 }
20145 </code></pre>
20146      * The returned object may also be addressed as an Array where index 0 contains the X position
20147      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20148      */
20149     getBox: function(contentBox, local) {
20150         var me = this,
20151             xy,
20152             left,
20153             top,
20154             getBorderWidth = me.getBorderWidth,
20155             getPadding = me.getPadding,
20156             l, r, t, b, w, h, bx;
20157         if (!local) {
20158             xy = me.getXY();
20159         } else {
20160             left = parseInt(me.getStyle("left"), 10) || 0;
20161             top = parseInt(me.getStyle("top"), 10) || 0;
20162             xy = [left, top];
20163         }
20164         w = me.getWidth();
20165         h = me.getHeight();
20166         if (!contentBox) {
20167             bx = {
20168                 x: xy[0],
20169                 y: xy[1],
20170                 0: xy[0],
20171                 1: xy[1],
20172                 width: w,
20173                 height: h
20174             };
20175         } else {
20176             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
20177             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
20178             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
20179             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
20180             bx = {
20181                 x: xy[0] + l,
20182                 y: xy[1] + t,
20183                 0: xy[0] + l,
20184                 1: xy[1] + t,
20185                 width: w - (l + r),
20186                 height: h - (t + b)
20187             };
20188         }
20189         bx.right = bx.x + bx.width;
20190         bx.bottom = bx.y + bx.height;
20191         return bx;
20192     },
20193
20194     /**
20195      * Move this element relative to its current position.
20196      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20197      * @param {Number} distance How far to move the element in pixels
20198      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20199      */
20200     move: function(direction, distance, animate) {
20201         var me = this,
20202             xy = me.getXY(),
20203             x = xy[0],
20204             y = xy[1],
20205             left = [x - distance, y],
20206             right = [x + distance, y],
20207             top = [x, y - distance],
20208             bottom = [x, y + distance],
20209             hash = {
20210                 l: left,
20211                 left: left,
20212                 r: right,
20213                 right: right,
20214                 t: top,
20215                 top: top,
20216                 up: top,
20217                 b: bottom,
20218                 bottom: bottom,
20219                 down: bottom
20220             };
20221
20222         direction = direction.toLowerCase();
20223         me.moveTo(hash[direction][0], hash[direction][1], animate);
20224     },
20225
20226     /**
20227      * Quick set left and top adding default units
20228      * @param {String} left The left CSS property value
20229      * @param {String} top The top CSS property value
20230      * @return {Ext.Element} this
20231      */
20232     setLeftTop: function(left, top) {
20233         var me = this,
20234             style = me.dom.style;
20235         style.left = me.addUnits(left);
20236         style.top = me.addUnits(top);
20237         return me;
20238     },
20239
20240     /**
20241      * Returns the region of this element.
20242      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
20243      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20244      */
20245     getRegion: function() {
20246         return this.getPageBox(true);
20247     },
20248
20249     /**
20250      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
20251      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20252      */
20253     getViewRegion: function() {
20254         var me = this,
20255             isBody = me.dom === document.body,
20256             scroll, pos, top, left, width, height;
20257
20258         // For the body we want to do some special logic
20259         if (isBody) {
20260             scroll = me.getScroll();
20261             left = scroll.left;
20262             top = scroll.top;
20263             width = Ext.Element.getViewportWidth();
20264             height = Ext.Element.getViewportHeight();
20265         }
20266         else {
20267             pos = me.getXY();
20268             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
20269             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
20270             width = me.getWidth(true);
20271             height = me.getHeight(true);
20272         }
20273
20274         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
20275     },
20276
20277     /**
20278      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20279      * set another Element's size/location to match this element.
20280      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
20281      * @return {Object} box An object in the format<pre><code>
20282 {
20283     x: &lt;Element's X position>,
20284     y: &lt;Element's Y position>,
20285     width: &lt;Element's width>,
20286     height: &lt;Element's height>,
20287     bottom: &lt;Element's lower bound>,
20288     right: &lt;Element's rightmost bound>
20289 }
20290 </code></pre>
20291      * The returned object may also be addressed as an Array where index 0 contains the X position
20292      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20293      */
20294     getPageBox : function(getRegion) {
20295         var me = this,
20296             el = me.dom,
20297             isDoc = el === document.body,
20298             w = isDoc ? Ext.Element.getViewWidth()  : el.offsetWidth,
20299             h = isDoc ? Ext.Element.getViewHeight() : el.offsetHeight,
20300             xy = me.getXY(),
20301             t = xy[1],
20302             r = xy[0] + w,
20303             b = xy[1] + h,
20304             l = xy[0];
20305
20306         if (getRegion) {
20307             return Ext.create('Ext.util.Region', t, r, b, l);
20308         }
20309         else {
20310             return {
20311                 left: l,
20312                 top: t,
20313                 width: w,
20314                 height: h,
20315                 right: r,
20316                 bottom: b
20317             };
20318         }
20319     },
20320
20321     /**
20322      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
20323      * @param {Number} x X value for new position (coordinates are page-based)
20324      * @param {Number} y Y value for new position (coordinates are page-based)
20325      * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
20326      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20327      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
20328      * </ul></div>
20329      * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
20330      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20331      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
20332      * </ul></div>
20333      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20334      * @return {Ext.Element} this
20335      */
20336     setBounds: function(x, y, width, height, animate) {
20337         var me = this;
20338         if (!animate || !me.anim) {
20339             me.setSize(width, height);
20340             me.setLocation(x, y);
20341         } else {
20342             if (!Ext.isObject(animate)) {
20343                 animate = {};
20344             }
20345             me.animate(Ext.applyIf({
20346                 to: {
20347                     x: x,
20348                     y: y,
20349                     width: me.adjustWidth(width),
20350                     height: me.adjustHeight(height)
20351                 }
20352             }, animate));
20353         }
20354         return me;
20355     },
20356
20357     /**
20358      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
20359      * @param {Ext.util.Region} region The region to fill
20360      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20361      * @return {Ext.Element} this
20362      */
20363     setRegion: function(region, animate) {
20364         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
20365     }
20366 });
20367 })();
20368
20369 /**
20370  * @class Ext.Element
20371  */
20372 Ext.override(Ext.Element, {
20373     /**
20374      * Returns true if this element is scrollable.
20375      * @return {Boolean}
20376      */
20377     isScrollable : function(){
20378         var dom = this.dom;
20379         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
20380     },
20381
20382     /**
20383      * Returns the current scroll position of the element.
20384      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
20385      */
20386     getScroll : function() {
20387         var d = this.dom, 
20388             doc = document,
20389             body = doc.body,
20390             docElement = doc.documentElement,
20391             l,
20392             t,
20393             ret;
20394
20395         if (d == doc || d == body) {
20396             if (Ext.isIE && Ext.isStrict) {
20397                 l = docElement.scrollLeft; 
20398                 t = docElement.scrollTop;
20399             } else {
20400                 l = window.pageXOffset;
20401                 t = window.pageYOffset;
20402             }
20403             ret = {
20404                 left: l || (body ? body.scrollLeft : 0), 
20405                 top : t || (body ? body.scrollTop : 0)
20406             };
20407         } else {
20408             ret = {
20409                 left: d.scrollLeft, 
20410                 top : d.scrollTop
20411             };
20412         }
20413         
20414         return ret;
20415     },
20416     
20417     /**
20418      * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
20419      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
20420      * @param {Number} value The new scroll value
20421      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20422      * @return {Ext.Element} this
20423      */
20424     scrollTo : function(side, value, animate) {
20425         //check if we're scrolling top or left
20426         var top = /top/i.test(side),
20427             me = this,
20428             dom = me.dom,
20429             obj = {},
20430             prop;
20431         if (!animate || !me.anim) {
20432             // just setting the value, so grab the direction
20433             prop = 'scroll' + (top ? 'Top' : 'Left');
20434             dom[prop] = value;
20435         }
20436         else {
20437             if (!Ext.isObject(animate)) {
20438                 animate = {};
20439             }
20440             obj['scroll' + (top ? 'Top' : 'Left')] = value;
20441             me.animate(Ext.applyIf({
20442                 to: obj
20443             }, animate));
20444         }
20445         return me;
20446     },
20447
20448     /**
20449      * Scrolls this element into view within the passed container.
20450      * @param {String/HTMLElement/Ext.Element} container (optional) The container element to scroll (defaults to document.body).  Should be a
20451      * string (id), dom node, or Ext.Element.
20452      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
20453      * @return {Ext.Element} this
20454      */
20455     scrollIntoView : function(container, hscroll) {
20456         container = Ext.getDom(container) || Ext.getBody().dom;
20457         var el = this.dom,
20458             offsets = this.getOffsetsTo(container),
20459             // el's box
20460             left = offsets[0] + container.scrollLeft,
20461             top = offsets[1] + container.scrollTop,
20462             bottom = top + el.offsetHeight,
20463             right = left + el.offsetWidth,
20464             // ct's box
20465             ctClientHeight = container.clientHeight,
20466             ctScrollTop = parseInt(container.scrollTop, 10),
20467             ctScrollLeft = parseInt(container.scrollLeft, 10),
20468             ctBottom = ctScrollTop + ctClientHeight,
20469             ctRight = ctScrollLeft + container.clientWidth;
20470
20471         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
20472             container.scrollTop = top;
20473         } else if (bottom > ctBottom) {
20474             container.scrollTop = bottom - ctClientHeight;
20475         }
20476         // corrects IE, other browsers will ignore
20477         container.scrollTop = container.scrollTop;
20478
20479         if (hscroll !== false) {
20480             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
20481                 container.scrollLeft = left;
20482             }
20483             else if (right > ctRight) {
20484                 container.scrollLeft = right - container.clientWidth;
20485             }
20486             container.scrollLeft = container.scrollLeft;
20487         }
20488         return this;
20489     },
20490
20491     // private
20492     scrollChildIntoView : function(child, hscroll) {
20493         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
20494     },
20495
20496     /**
20497      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
20498      * within this element's scrollable range.
20499      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20500      * @param {Number} distance How far to scroll the element in pixels
20501      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20502      * @return {Boolean} Returns true if a scroll was triggered or false if the element
20503      * was scrolled as far as it could go.
20504      */
20505      scroll : function(direction, distance, animate) {
20506         if (!this.isScrollable()) {
20507             return false;
20508         }
20509         var el = this.dom,
20510             l = el.scrollLeft, t = el.scrollTop,
20511             w = el.scrollWidth, h = el.scrollHeight,
20512             cw = el.clientWidth, ch = el.clientHeight,
20513             scrolled = false, v,
20514             hash = {
20515                 l: Math.min(l + distance, w-cw),
20516                 r: v = Math.max(l - distance, 0),
20517                 t: Math.max(t - distance, 0),
20518                 b: Math.min(t + distance, h-ch)
20519             };
20520             hash.d = hash.b;
20521             hash.u = hash.t;
20522
20523         direction = direction.substr(0, 1);
20524         if ((v = hash[direction]) > -1) {
20525             scrolled = true;
20526             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
20527         }
20528         return scrolled;
20529     }
20530 });
20531 /**
20532  * @class Ext.Element
20533  */
20534 Ext.Element.addMethods(
20535     function() {
20536         var VISIBILITY      = "visibility",
20537             DISPLAY         = "display",
20538             HIDDEN          = "hidden",
20539             NONE            = "none",
20540             XMASKED         = Ext.baseCSSPrefix + "masked",
20541             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
20542             data            = Ext.Element.data;
20543
20544         return {
20545             /**
20546              * Checks whether the element is currently visible using both visibility and display properties.
20547              * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden
20548              * @return {Boolean} True if the element is currently visible, else false
20549              */
20550             isVisible : function(deep) {
20551                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
20552                     p   = this.dom.parentNode;
20553
20554                 if (deep !== true || !vis) {
20555                     return vis;
20556                 }
20557
20558                 while (p && !(/^body/i.test(p.tagName))) {
20559                     if (!Ext.fly(p, '_isVisible').isVisible()) {
20560                         return false;
20561                     }
20562                     p = p.parentNode;
20563                 }
20564                 return true;
20565             },
20566
20567             /**
20568              * Returns true if display is not "none"
20569              * @return {Boolean}
20570              */
20571             isDisplayed : function() {
20572                 return !this.isStyle(DISPLAY, NONE);
20573             },
20574
20575             /**
20576              * Convenience method for setVisibilityMode(Element.DISPLAY)
20577              * @param {String} display (optional) What to set display to when visible
20578              * @return {Ext.Element} this
20579              */
20580             enableDisplayMode : function(display) {
20581                 this.setVisibilityMode(Ext.Element.DISPLAY);
20582
20583                 if (!Ext.isEmpty(display)) {
20584                     data(this.dom, 'originalDisplay', display);
20585                 }
20586
20587                 return this;
20588             },
20589
20590             /**
20591              * Puts a mask over this element to disable user interaction. Requires core.css.
20592              * This method can only be applied to elements which accept child nodes.
20593              * @param {String} msg (optional) A message to display in the mask
20594              * @param {String} msgCls (optional) A css class to apply to the msg element
20595              * @return {Ext.Element} The mask element
20596              */
20597             mask : function(msg, msgCls) {
20598                 var me  = this,
20599                     dom = me.dom,
20600                     setExpression = dom.style.setExpression,
20601                     dh  = Ext.DomHelper,
20602                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
20603                     el,
20604                     mask;
20605
20606                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
20607                     me.addCls(XMASKEDRELATIVE);
20608                 }
20609                 el = data(dom, 'maskMsg');
20610                 if (el) {
20611                     el.remove();
20612                 }
20613                 el = data(dom, 'mask');
20614                 if (el) {
20615                     el.remove();
20616                 }
20617
20618                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
20619                 data(dom, 'mask', mask);
20620
20621                 me.addCls(XMASKED);
20622                 mask.setDisplayed(true);
20623
20624                 if (typeof msg == 'string') {
20625                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
20626                     data(dom, 'maskMsg', mm);
20627                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
20628                     mm.dom.firstChild.innerHTML = msg;
20629                     mm.setDisplayed(true);
20630                     mm.center(me);
20631                 }
20632                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
20633                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
20634                 // In normal use cases an element will be masked for a limited period of time.
20635                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
20636                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
20637                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
20638                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
20639                 }
20640
20641                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
20642                 // Different versions from those which make the same error for width!
20643                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
20644                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
20645                 }
20646                 // ie will not expand full height automatically
20647                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
20648                     mask.setSize(undefined, me.getHeight());
20649                 }
20650                 return mask;
20651             },
20652
20653             /**
20654              * Removes a previously applied mask.
20655              */
20656             unmask : function() {
20657                 var me      = this,
20658                     dom     = me.dom,
20659                     mask    = data(dom, 'mask'),
20660                     maskMsg = data(dom, 'maskMsg');
20661
20662                 if (mask) {
20663                     // Remove resource-intensive CSS expressions as soon as they are not required.
20664                     if (mask.dom.style.clearExpression) {
20665                         mask.dom.style.clearExpression('width');
20666                         mask.dom.style.clearExpression('height');
20667                     }
20668                     if (maskMsg) {
20669                         maskMsg.remove();
20670                         data(dom, 'maskMsg', undefined);
20671                     }
20672
20673                     mask.remove();
20674                     data(dom, 'mask', undefined);
20675                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
20676                 }
20677             },
20678             /**
20679              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
20680              * @return {Boolean}
20681              */
20682             isMasked : function() {
20683                 var me = this,
20684                     mask = data(me.dom, 'mask'),
20685                     maskMsg = data(me.dom, 'maskMsg');
20686
20687                 if (mask && mask.isVisible()) {
20688                     if (maskMsg) {
20689                         maskMsg.center(me);
20690                     }
20691                     return true;
20692                 }
20693                 return false;
20694             },
20695
20696             /**
20697              * Creates an iframe shim for this element to keep selects and other windowed objects from
20698              * showing through.
20699              * @return {Ext.Element} The new shim element
20700              */
20701             createShim : function() {
20702                 var el = document.createElement('iframe'),
20703                     shim;
20704
20705                 el.frameBorder = '0';
20706                 el.className = Ext.baseCSSPrefix + 'shim';
20707                 el.src = Ext.SSL_SECURE_URL;
20708                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
20709                 shim.autoBoxAdjust = false;
20710                 return shim;
20711             }
20712         };
20713     }()
20714 );
20715 /**
20716  * @class Ext.Element
20717  */
20718 Ext.Element.addMethods({
20719     /**
20720      * Convenience method for constructing a KeyMap
20721      * @param {String/Number/Number[]/Object} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
20722      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
20723      * @param {Function} fn The function to call
20724      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
20725      * @return {Ext.util.KeyMap} The KeyMap created
20726      */
20727     addKeyListener : function(key, fn, scope){
20728         var config;
20729         if(typeof key != 'object' || Ext.isArray(key)){
20730             config = {
20731                 key: key,
20732                 fn: fn,
20733                 scope: scope
20734             };
20735         }else{
20736             config = {
20737                 key : key.key,
20738                 shift : key.shift,
20739                 ctrl : key.ctrl,
20740                 alt : key.alt,
20741                 fn: fn,
20742                 scope: scope
20743             };
20744         }
20745         return Ext.create('Ext.util.KeyMap', this, config);
20746     },
20747
20748     /**
20749      * Creates a KeyMap for this element
20750      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
20751      * @return {Ext.util.KeyMap} The KeyMap created
20752      */
20753     addKeyMap : function(config){
20754         return Ext.create('Ext.util.KeyMap', this, config);
20755     }
20756 });
20757
20758 //Import the newly-added Ext.Element functions into CompositeElementLite. We call this here because
20759 //Element.keys.js is the last extra Ext.Element include in the ext-all.js build
20760 Ext.CompositeElementLite.importElementMethods();
20761
20762 /**
20763  * @class Ext.CompositeElementLite
20764  */
20765 Ext.apply(Ext.CompositeElementLite.prototype, {
20766     addElements : function(els, root){
20767         if(!els){
20768             return this;
20769         }
20770         if(typeof els == "string"){
20771             els = Ext.Element.selectorFunction(els, root);
20772         }
20773         var yels = this.elements;
20774         Ext.each(els, function(e) {
20775             yels.push(Ext.get(e));
20776         });
20777         return this;
20778     },
20779
20780     /**
20781      * Returns the first Element
20782      * @return {Ext.Element}
20783      */
20784     first : function(){
20785         return this.item(0);
20786     },
20787
20788     /**
20789      * Returns the last Element
20790      * @return {Ext.Element}
20791      */
20792     last : function(){
20793         return this.item(this.getCount()-1);
20794     },
20795
20796     /**
20797      * Returns true if this composite contains the passed element
20798      * @param el {String/HTMLElement/Ext.Element/Number} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
20799      * @return Boolean
20800      */
20801     contains : function(el){
20802         return this.indexOf(el) != -1;
20803     },
20804
20805     /**
20806     * Removes the specified element(s).
20807     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
20808     * or an array of any of those.
20809     * @param {Boolean} removeDom (optional) True to also remove the element from the document
20810     * @return {Ext.CompositeElement} this
20811     */
20812     removeElement : function(keys, removeDom){
20813         var me = this,
20814             els = this.elements,
20815             el;
20816         Ext.each(keys, function(val){
20817             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
20818                 if(removeDom){
20819                     if(el.dom){
20820                         el.remove();
20821                     }else{
20822                         Ext.removeNode(el);
20823                     }
20824                 }
20825                 Ext.Array.erase(els, val, 1);
20826             }
20827         });
20828         return this;
20829     }
20830 });
20831
20832 /**
20833  * @class Ext.CompositeElement
20834  * @extends Ext.CompositeElementLite
20835  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
20836  * members, or to perform collective actions upon the whole set.</p>
20837  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
20838  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
20839  * <p>All methods return <i>this</i> and can be chained.</p>
20840  * Usage:
20841 <pre><code>
20842 var els = Ext.select("#some-el div.some-class", true);
20843 // or select directly from an existing element
20844 var el = Ext.get('some-el');
20845 el.select('div.some-class', true);
20846
20847 els.setWidth(100); // all elements become 100 width
20848 els.hide(true); // all elements fade out and hide
20849 // or
20850 els.setWidth(100).hide(true);
20851 </code></pre>
20852  */
20853 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
20854
20855     constructor : function(els, root){
20856         this.elements = [];
20857         this.add(els, root);
20858     },
20859
20860     // private
20861     getElement : function(el){
20862         // In this case just return it, since we already have a reference to it
20863         return el;
20864     },
20865
20866     // private
20867     transformElement : function(el){
20868         return Ext.get(el);
20869     }
20870 });
20871
20872 /**
20873  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
20874  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
20875  * {@link Ext.CompositeElementLite CompositeElementLite} object.
20876  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
20877  * @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
20878  * @param {HTMLElement/String} [root] The root element of the query or id of the root
20879  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
20880  * @member Ext.Element
20881  * @method select
20882  */
20883 Ext.Element.select = function(selector, unique, root){
20884     var els;
20885     if(typeof selector == "string"){
20886         els = Ext.Element.selectorFunction(selector, root);
20887     }else if(selector.length !== undefined){
20888         els = selector;
20889     }else{
20890     }
20891     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
20892 };
20893
20894 /**
20895  * Shorthand of {@link Ext.Element#select}.
20896  * @member Ext
20897  * @method select
20898  * @alias Ext.Element#select
20899  */
20900 Ext.select = Ext.Element.select;
20901
20902
20903 /*
20904
20905 This file is part of Ext JS 4
20906
20907 Copyright (c) 2011 Sencha Inc
20908
20909 Contact:  http://www.sencha.com/contact
20910
20911 GNU General Public License Usage
20912 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
20913
20914 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
20915
20916 */
20917 /**
20918  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
20919  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
20920  *
20921  * For example:
20922  *
20923  *     Ext.define('Employee', {
20924  *         extend: 'Ext.util.Observable',
20925  *         constructor: function(config){
20926  *             this.name = config.name;
20927  *             this.addEvents({
20928  *                 "fired" : true,
20929  *                 "quit" : true
20930  *             });
20931  *
20932  *             // Copy configured listeners into *this* object so that the base class's
20933  *             // constructor will add them.
20934  *             this.listeners = config.listeners;
20935  *
20936  *             // Call our superclass constructor to complete construction process.
20937  *             this.callParent(arguments)
20938  *         }
20939  *     });
20940  *
20941  * This could then be used like this:
20942  *
20943  *     var newEmployee = new Employee({
20944  *         name: employeeName,
20945  *         listeners: {
20946  *             quit: function() {
20947  *                 // By default, "this" will be the object that fired the event.
20948  *                 alert(this.name + " has quit!");
20949  *             }
20950  *         }
20951  *     });
20952  */
20953 Ext.define('Ext.util.Observable', {
20954
20955     /* Begin Definitions */
20956
20957     requires: ['Ext.util.Event'],
20958
20959     statics: {
20960         /**
20961          * Removes **all** added captures from the Observable.
20962          *
20963          * @param {Ext.util.Observable} o The Observable to release
20964          * @static
20965          */
20966         releaseCapture: function(o) {
20967             o.fireEvent = this.prototype.fireEvent;
20968         },
20969
20970         /**
20971          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
20972          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
20973          * the event will not fire.
20974          *
20975          * @param {Ext.util.Observable} o The Observable to capture events from.
20976          * @param {Function} fn The function to call when an event is fired.
20977          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
20978          * the Observable firing the event.
20979          * @static
20980          */
20981         capture: function(o, fn, scope) {
20982             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
20983         },
20984
20985         /**
20986          * Sets observability on the passed class constructor.
20987          *
20988          * This makes any event fired on any instance of the passed class also fire a single event through
20989          * the **class** allowing for central handling of events on many instances at once.
20990          *
20991          * Usage:
20992          *
20993          *     Ext.util.Observable.observe(Ext.data.Connection);
20994          *     Ext.data.Connection.on('beforerequest', function(con, options) {
20995          *         console.log('Ajax request made to ' + options.url);
20996          *     });
20997          *
20998          * @param {Function} c The class constructor to make observable.
20999          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
21000          * @static
21001          */
21002         observe: function(cls, listeners) {
21003             if (cls) {
21004                 if (!cls.isObservable) {
21005                     Ext.applyIf(cls, new this());
21006                     this.capture(cls.prototype, cls.fireEvent, cls);
21007                 }
21008                 if (Ext.isObject(listeners)) {
21009                     cls.on(listeners);
21010                 }
21011                 return cls;
21012             }
21013         }
21014     },
21015
21016     /* End Definitions */
21017
21018     /**
21019      * @cfg {Object} listeners
21020      *
21021      * A config object containing one or more event handlers to be added to this object during initialization. This
21022      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
21023      * handlers at once.
21024      *
21025      * **DOM events from Ext JS {@link Ext.Component Components}**
21026      *
21027      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
21028      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
21029      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
21030      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
21031      * DOM listener to:
21032      *
21033      *     new Ext.panel.Panel({
21034      *         width: 400,
21035      *         height: 200,
21036      *         dockedItems: [{
21037      *             xtype: 'toolbar'
21038      *         }],
21039      *         listeners: {
21040      *             click: {
21041      *                 element: 'el', //bind to the underlying el property on the panel
21042      *                 fn: function(){ console.log('click el'); }
21043      *             },
21044      *             dblclick: {
21045      *                 element: 'body', //bind to the underlying body property on the panel
21046      *                 fn: function(){ console.log('dblclick body'); }
21047      *             }
21048      *         }
21049      *     });
21050      */
21051     // @private
21052     isObservable: true,
21053
21054     constructor: function(config) {
21055         var me = this;
21056
21057         Ext.apply(me, config);
21058         if (me.listeners) {
21059             me.on(me.listeners);
21060             delete me.listeners;
21061         }
21062         me.events = me.events || {};
21063
21064         if (me.bubbleEvents) {
21065             me.enableBubble(me.bubbleEvents);
21066         }
21067     },
21068
21069     // @private
21070     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
21071
21072     /**
21073      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
21074      * destroyed.
21075      *
21076      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
21077      * @param {Object/String} ename The event name, or an object containing event name properties.
21078      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21079      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21080      * in which the handler function is executed.
21081      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
21082      * {@link Ext.util.Observable#addListener addListener} options.
21083      */
21084     addManagedListener : function(item, ename, fn, scope, options) {
21085         var me = this,
21086             managedListeners = me.managedListeners = me.managedListeners || [],
21087             config;
21088
21089         if (typeof ename !== 'string') {
21090             options = ename;
21091             for (ename in options) {
21092                 if (options.hasOwnProperty(ename)) {
21093                     config = options[ename];
21094                     if (!me.eventOptionsRe.test(ename)) {
21095                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21096                     }
21097                 }
21098             }
21099         }
21100         else {
21101             managedListeners.push({
21102                 item: item,
21103                 ename: ename,
21104                 fn: fn,
21105                 scope: scope,
21106                 options: options
21107             });
21108
21109             item.on(ename, fn, scope, options);
21110         }
21111     },
21112
21113     /**
21114      * Removes listeners that were added by the {@link #mon} method.
21115      *
21116      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
21117      * @param {Object/String} ename The event name, or an object containing event name properties.
21118      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21119      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21120      * in which the handler function is executed.
21121      */
21122     removeManagedListener : function(item, ename, fn, scope) {
21123         var me = this,
21124             options,
21125             config,
21126             managedListeners,
21127             length,
21128             i;
21129
21130         if (typeof ename !== 'string') {
21131             options = ename;
21132             for (ename in options) {
21133                 if (options.hasOwnProperty(ename)) {
21134                     config = options[ename];
21135                     if (!me.eventOptionsRe.test(ename)) {
21136                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
21137                     }
21138                 }
21139             }
21140         }
21141
21142         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
21143
21144         for (i = 0, length = managedListeners.length; i < length; i++) {
21145             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
21146         }
21147     },
21148
21149     /**
21150      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
21151      * to {@link #addListener}).
21152      *
21153      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
21154      * calling {@link #enableBubble}.
21155      *
21156      * @param {String} eventName The name of the event to fire.
21157      * @param {Object...} args Variable number of parameters are passed to handlers.
21158      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
21159      */
21160     fireEvent: function(eventName) {
21161         var name = eventName.toLowerCase(),
21162             events = this.events,
21163             event = events && events[name],
21164             bubbles = event && event.bubble;
21165
21166         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
21167     },
21168
21169     /**
21170      * Continue to fire event.
21171      * @private
21172      *
21173      * @param {String} eventName
21174      * @param {Array} args
21175      * @param {Boolean} bubbles
21176      */
21177     continueFireEvent: function(eventName, args, bubbles) {
21178         var target = this,
21179             queue, event,
21180             ret = true;
21181
21182         do {
21183             if (target.eventsSuspended === true) {
21184                 if ((queue = target.eventQueue)) {
21185                     queue.push([eventName, args, bubbles]);
21186                 }
21187                 return ret;
21188             } else {
21189                 event = target.events[eventName];
21190                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
21191                 // configure to bubble.
21192                 if (event && event != true) {
21193                     if ((ret = event.fire.apply(event, args)) === false) {
21194                         break;
21195                     }
21196                 }
21197             }
21198         } while (bubbles && (target = target.getBubbleParent()));
21199         return ret;
21200     },
21201
21202     /**
21203      * Gets the bubbling parent for an Observable
21204      * @private
21205      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
21206      */
21207     getBubbleParent: function(){
21208         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
21209         if (parent && parent.isObservable) {
21210             return parent;
21211         }
21212         return null;
21213     },
21214
21215     /**
21216      * Appends an event handler to this object.
21217      *
21218      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
21219      * event names.
21220      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
21221      * {@link #fireEvent} plus the `options` parameter described below.
21222      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
21223      * omitted, defaults to the object which fired the event.**
21224      * @param {Object} [options] An object containing handler configuration.
21225      *
21226      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
21227      *
21228      * This object may contain any of the following properties:
21229      *
21230      * - **scope** : Object
21231      *
21232      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
21233      *   which fired the event.**
21234      *
21235      * - **delay** : Number
21236      *
21237      *   The number of milliseconds to delay the invocation of the handler after the event fires.
21238      *
21239      * - **single** : Boolean
21240      *
21241      *   True to add a handler to handle just the next firing of the event, and then remove itself.
21242      *
21243      * - **buffer** : Number
21244      *
21245      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
21246      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
21247      *   handler is scheduled in its place.
21248      *
21249      * - **target** : Observable
21250      *
21251      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
21252      *   child Observable.
21253      *
21254      * - **element** : String
21255      *
21256      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
21257      *   property which references an element to add a listener to.
21258      *
21259      *   This option is useful during Component construction to add DOM event listeners to elements of
21260      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
21261      *   For example, to add a click listener to a Panel's body:
21262      *
21263      *       new Ext.panel.Panel({
21264      *           title: 'The title',
21265      *           listeners: {
21266      *               click: this.handlePanelClick,
21267      *               element: 'body'
21268      *           }
21269      *       });
21270      *
21271      * **Combining Options**
21272      *
21273      * Using the options argument, it is possible to combine different types of listeners:
21274      *
21275      * A delayed, one-time listener.
21276      *
21277      *     myPanel.on('hide', this.handleClick, this, {
21278      *         single: true,
21279      *         delay: 100
21280      *     });
21281      *
21282      * **Attaching multiple handlers in 1 call**
21283      *
21284      * The method also allows for a single argument to be passed which is a config object containing properties which
21285      * specify multiple events. For example:
21286      *
21287      *     myGridPanel.on({
21288      *         cellClick: this.onCellClick,
21289      *         mouseover: this.onMouseOver,
21290      *         mouseout: this.onMouseOut,
21291      *         scope: this // Important. Ensure "this" is correct during handler execution
21292      *     });
21293      *
21294      * One can also specify options for each event handler separately:
21295      *
21296      *     myGridPanel.on({
21297      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
21298      *         mouseover: {fn: panel.onMouseOver, scope: panel}
21299      *     });
21300      *
21301      */
21302     addListener: function(ename, fn, scope, options) {
21303         var me = this,
21304             config,
21305             event;
21306
21307         if (typeof ename !== 'string') {
21308             options = ename;
21309             for (ename in options) {
21310                 if (options.hasOwnProperty(ename)) {
21311                     config = options[ename];
21312                     if (!me.eventOptionsRe.test(ename)) {
21313                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21314                     }
21315                 }
21316             }
21317         }
21318         else {
21319             ename = ename.toLowerCase();
21320             me.events[ename] = me.events[ename] || true;
21321             event = me.events[ename] || true;
21322             if (Ext.isBoolean(event)) {
21323                 me.events[ename] = event = new Ext.util.Event(me, ename);
21324             }
21325             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
21326         }
21327     },
21328
21329     /**
21330      * Removes an event handler.
21331      *
21332      * @param {String} eventName The type of event the handler was associated with.
21333      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
21334      * {@link #addListener} call.**
21335      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
21336      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
21337      */
21338     removeListener: function(ename, fn, scope) {
21339         var me = this,
21340             config,
21341             event,
21342             options;
21343
21344         if (typeof ename !== 'string') {
21345             options = ename;
21346             for (ename in options) {
21347                 if (options.hasOwnProperty(ename)) {
21348                     config = options[ename];
21349                     if (!me.eventOptionsRe.test(ename)) {
21350                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
21351                     }
21352                 }
21353             }
21354         } else {
21355             ename = ename.toLowerCase();
21356             event = me.events[ename];
21357             if (event && event.isEvent) {
21358                 event.removeListener(fn, scope);
21359             }
21360         }
21361     },
21362
21363     /**
21364      * Removes all listeners for this object including the managed listeners
21365      */
21366     clearListeners: function() {
21367         var events = this.events,
21368             event,
21369             key;
21370
21371         for (key in events) {
21372             if (events.hasOwnProperty(key)) {
21373                 event = events[key];
21374                 if (event.isEvent) {
21375                     event.clearListeners();
21376                 }
21377             }
21378         }
21379
21380         this.clearManagedListeners();
21381     },
21382
21383
21384     /**
21385      * Removes all managed listeners for this object.
21386      */
21387     clearManagedListeners : function() {
21388         var managedListeners = this.managedListeners || [],
21389             i = 0,
21390             len = managedListeners.length;
21391
21392         for (; i < len; i++) {
21393             this.removeManagedListenerItem(true, managedListeners[i]);
21394         }
21395
21396         this.managedListeners = [];
21397     },
21398
21399     /**
21400      * Remove a single managed listener item
21401      * @private
21402      * @param {Boolean} isClear True if this is being called during a clear
21403      * @param {Object} managedListener The managed listener item
21404      * See removeManagedListener for other args
21405      */
21406     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
21407         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
21408             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
21409             if (!isClear) {
21410                 Ext.Array.remove(this.managedListeners, managedListener);
21411             }
21412         }
21413     },
21414
21415
21416     /**
21417      * Adds the specified events to the list of events which this Observable may fire.
21418      *
21419      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
21420      * event name string if multiple event names are being passed as separate parameters. Usage:
21421      *
21422      *     this.addEvents({
21423      *         storeloaded: true,
21424      *         storecleared: true
21425      *     });
21426      *
21427      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
21428      * parameters. Usage:
21429      *
21430      *     this.addEvents('storeloaded', 'storecleared');
21431      *
21432      */
21433     addEvents: function(o) {
21434         var me = this,
21435             args,
21436             len,
21437             i;
21438
21439             me.events = me.events || {};
21440         if (Ext.isString(o)) {
21441             args = arguments;
21442             i = args.length;
21443
21444             while (i--) {
21445                 me.events[args[i]] = me.events[args[i]] || true;
21446             }
21447         } else {
21448             Ext.applyIf(me.events, o);
21449         }
21450     },
21451
21452     /**
21453      * Checks to see if this object has any listeners for a specified event
21454      *
21455      * @param {String} eventName The name of the event to check for
21456      * @return {Boolean} True if the event is being listened for, else false
21457      */
21458     hasListener: function(ename) {
21459         var event = this.events[ename.toLowerCase()];
21460         return event && event.isEvent === true && event.listeners.length > 0;
21461     },
21462
21463     /**
21464      * Suspends the firing of all events. (see {@link #resumeEvents})
21465      *
21466      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
21467      * after the {@link #resumeEvents} call instead of discarding all suspended events.
21468      */
21469     suspendEvents: function(queueSuspended) {
21470         this.eventsSuspended = true;
21471         if (queueSuspended && !this.eventQueue) {
21472             this.eventQueue = [];
21473         }
21474     },
21475
21476     /**
21477      * Resumes firing events (see {@link #suspendEvents}).
21478      *
21479      * If events were suspended using the `queueSuspended` parameter, then all events fired
21480      * during event suspension will be sent to any listeners now.
21481      */
21482     resumeEvents: function() {
21483         var me = this,
21484             queued = me.eventQueue;
21485
21486         me.eventsSuspended = false;
21487         delete me.eventQueue;
21488
21489         if (queued) {
21490             Ext.each(queued, function(e) {
21491                 me.continueFireEvent.apply(me, e);
21492             });
21493         }
21494     },
21495
21496     /**
21497      * Relays selected events from the specified Observable as if the events were fired by `this`.
21498      *
21499      * @param {Object} origin The Observable whose events this object is to relay.
21500      * @param {String[]} events Array of event names to relay.
21501      * @param {String} prefix
21502      */
21503     relayEvents : function(origin, events, prefix) {
21504         prefix = prefix || '';
21505         var me = this,
21506             len = events.length,
21507             i = 0,
21508             oldName,
21509             newName;
21510
21511         for (; i < len; i++) {
21512             oldName = events[i].substr(prefix.length);
21513             newName = prefix + oldName;
21514             me.events[newName] = me.events[newName] || true;
21515             origin.on(oldName, me.createRelayer(newName));
21516         }
21517     },
21518
21519     /**
21520      * @private
21521      * Creates an event handling function which refires the event from this object as the passed event name.
21522      * @param newName
21523      * @returns {Function}
21524      */
21525     createRelayer: function(newName){
21526         var me = this;
21527         return function(){
21528             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
21529         };
21530     },
21531
21532     /**
21533      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
21534      * present. There is no implementation in the Observable base class.
21535      *
21536      * This is commonly used by Ext.Components to bubble events to owner Containers.
21537      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
21538      * Component's immediate owner. But if a known target is required, this can be overridden to access the
21539      * required target more quickly.
21540      *
21541      * Example:
21542      *
21543      *     Ext.override(Ext.form.field.Base, {
21544      *         //  Add functionality to Field's initComponent to enable the change event to bubble
21545      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
21546      *             this.enableBubble('change');
21547      *         }),
21548      *
21549      *         //  We know that we want Field's events to bubble directly to the FormPanel.
21550      *         getBubbleTarget : function() {
21551      *             if (!this.formPanel) {
21552      *                 this.formPanel = this.findParentByType('form');
21553      *             }
21554      *             return this.formPanel;
21555      *         }
21556      *     });
21557      *
21558      *     var myForm = new Ext.formPanel({
21559      *         title: 'User Details',
21560      *         items: [{
21561      *             ...
21562      *         }],
21563      *         listeners: {
21564      *             change: function() {
21565      *                 // Title goes red if form has been modified.
21566      *                 myForm.header.setStyle('color', 'red');
21567      *             }
21568      *         }
21569      *     });
21570      *
21571      * @param {String/String[]} events The event name to bubble, or an Array of event names.
21572      */
21573     enableBubble: function(events) {
21574         var me = this;
21575         if (!Ext.isEmpty(events)) {
21576             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
21577             Ext.each(events,
21578             function(ename) {
21579                 ename = ename.toLowerCase();
21580                 var ce = me.events[ename] || true;
21581                 if (Ext.isBoolean(ce)) {
21582                     ce = new Ext.util.Event(me, ename);
21583                     me.events[ename] = ce;
21584                 }
21585                 ce.bubble = true;
21586             });
21587         }
21588     }
21589 }, function() {
21590
21591     this.createAlias({
21592         /**
21593          * @method
21594          * Shorthand for {@link #addListener}.
21595          * @alias Ext.util.Observable#addListener
21596          */
21597         on: 'addListener',
21598         /**
21599          * @method
21600          * Shorthand for {@link #removeListener}.
21601          * @alias Ext.util.Observable#removeListener
21602          */
21603         un: 'removeListener',
21604         /**
21605          * @method
21606          * Shorthand for {@link #addManagedListener}.
21607          * @alias Ext.util.Observable#addManagedListener
21608          */
21609         mon: 'addManagedListener',
21610         /**
21611          * @method
21612          * Shorthand for {@link #removeManagedListener}.
21613          * @alias Ext.util.Observable#removeManagedListener
21614          */
21615         mun: 'removeManagedListener'
21616     });
21617
21618     //deprecated, will be removed in 5.0
21619     this.observeClass = this.observe;
21620
21621     Ext.apply(Ext.util.Observable.prototype, function(){
21622         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
21623         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
21624         // private
21625         function getMethodEvent(method){
21626             var e = (this.methodEvents = this.methodEvents || {})[method],
21627                 returnValue,
21628                 v,
21629                 cancel,
21630                 obj = this;
21631
21632             if (!e) {
21633                 this.methodEvents[method] = e = {};
21634                 e.originalFn = this[method];
21635                 e.methodName = method;
21636                 e.before = [];
21637                 e.after = [];
21638
21639                 var makeCall = function(fn, scope, args){
21640                     if((v = fn.apply(scope || obj, args)) !== undefined){
21641                         if (typeof v == 'object') {
21642                             if(v.returnValue !== undefined){
21643                                 returnValue = v.returnValue;
21644                             }else{
21645                                 returnValue = v;
21646                             }
21647                             cancel = !!v.cancel;
21648                         }
21649                         else
21650                             if (v === false) {
21651                                 cancel = true;
21652                             }
21653                             else {
21654                                 returnValue = v;
21655                             }
21656                     }
21657                 };
21658
21659                 this[method] = function(){
21660                     var args = Array.prototype.slice.call(arguments, 0),
21661                         b, i, len;
21662                     returnValue = v = undefined;
21663                     cancel = false;
21664
21665                     for(i = 0, len = e.before.length; i < len; i++){
21666                         b = e.before[i];
21667                         makeCall(b.fn, b.scope, args);
21668                         if (cancel) {
21669                             return returnValue;
21670                         }
21671                     }
21672
21673                     if((v = e.originalFn.apply(obj, args)) !== undefined){
21674                         returnValue = v;
21675                     }
21676
21677                     for(i = 0, len = e.after.length; i < len; i++){
21678                         b = e.after[i];
21679                         makeCall(b.fn, b.scope, args);
21680                         if (cancel) {
21681                             return returnValue;
21682                         }
21683                     }
21684                     return returnValue;
21685                 };
21686             }
21687             return e;
21688         }
21689
21690         return {
21691             // these are considered experimental
21692             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
21693             // adds an 'interceptor' called before the original method
21694             beforeMethod : function(method, fn, scope){
21695                 getMethodEvent.call(this, method).before.push({
21696                     fn: fn,
21697                     scope: scope
21698                 });
21699             },
21700
21701             // adds a 'sequence' called after the original method
21702             afterMethod : function(method, fn, scope){
21703                 getMethodEvent.call(this, method).after.push({
21704                     fn: fn,
21705                     scope: scope
21706                 });
21707             },
21708
21709             removeMethodListener: function(method, fn, scope){
21710                 var e = this.getMethodEvent(method),
21711                     i, len;
21712                 for(i = 0, len = e.before.length; i < len; i++){
21713                     if(e.before[i].fn == fn && e.before[i].scope == scope){
21714                         Ext.Array.erase(e.before, i, 1);
21715                         return;
21716                     }
21717                 }
21718                 for(i = 0, len = e.after.length; i < len; i++){
21719                     if(e.after[i].fn == fn && e.after[i].scope == scope){
21720                         Ext.Array.erase(e.after, i, 1);
21721                         return;
21722                     }
21723                 }
21724             },
21725
21726             toggleEventLogging: function(toggle) {
21727                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
21728                     if (Ext.isDefined(Ext.global.console)) {
21729                         Ext.global.console.log(en, arguments);
21730                     }
21731                 });
21732             }
21733         };
21734     }());
21735 });
21736
21737 /**
21738  * @class Ext.util.Animate
21739  * This animation class is a mixin.
21740  * 
21741  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
21742  * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement}, 
21743  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
21744  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
21745  * opacity (color, paddings, and margins can not be animated).
21746  * 
21747  * ## Animation Basics
21748  * 
21749  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
21750  * you wish to animate. Easing and duration are defaulted values specified below.
21751  * Easing describes how the intermediate values used during a transition will be calculated. 
21752  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
21753  * You may use the defaults for easing and duration, but you must always set a 
21754  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
21755  * 
21756  * Popular element 'to' configurations are:
21757  * 
21758  *  - opacity
21759  *  - x
21760  *  - y
21761  *  - color
21762  *  - height
21763  *  - width 
21764  * 
21765  * Popular sprite 'to' configurations are:
21766  * 
21767  *  - translation
21768  *  - path
21769  *  - scale
21770  *  - stroke
21771  *  - rotation
21772  * 
21773  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
21774  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
21775  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
21776  * 
21777  * For example, a simple animation to fade out an element with a default easing and duration:
21778  * 
21779  *     var p1 = Ext.get('myElementId');
21780  * 
21781  *     p1.animate({
21782  *         to: {
21783  *             opacity: 0
21784  *         }
21785  *     });
21786  * 
21787  * To make this animation fade out in a tenth of a second:
21788  * 
21789  *     var p1 = Ext.get('myElementId');
21790  * 
21791  *     p1.animate({
21792  *        duration: 100,
21793  *         to: {
21794  *             opacity: 0
21795  *         }
21796  *     });
21797  * 
21798  * ## Animation Queues
21799  * 
21800  * By default all animations are added to a queue which allows for animation via a chain-style API.
21801  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
21802  * 
21803  *     p1.animate({
21804  *         to: {
21805  *             x: 500
21806  *         }
21807  *     }).animate({
21808  *         to: {
21809  *             y: 150
21810  *         }
21811  *     }).animate({
21812  *         to: {
21813  *             backgroundColor: '#f00'  //red
21814  *         }
21815  *     }).animate({
21816  *         to: {
21817  *             opacity: 0
21818  *         }
21819  *     });
21820  * 
21821  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
21822  * subsequent animations for the specified target will be run concurrently (at the same time).
21823  * 
21824  *     p1.syncFx();  //this will make all animations run at the same time
21825  * 
21826  *     p1.animate({
21827  *         to: {
21828  *             x: 500
21829  *         }
21830  *     }).animate({
21831  *         to: {
21832  *             y: 150
21833  *         }
21834  *     }).animate({
21835  *         to: {
21836  *             backgroundColor: '#f00'  //red
21837  *         }
21838  *     }).animate({
21839  *         to: {
21840  *             opacity: 0
21841  *         }
21842  *     });
21843  * 
21844  * This works the same as:
21845  * 
21846  *     p1.animate({
21847  *         to: {
21848  *             x: 500,
21849  *             y: 150,
21850  *             backgroundColor: '#f00'  //red
21851  *             opacity: 0
21852  *         }
21853  *     });
21854  * 
21855  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
21856  * currently running animations and clear any queued animations. 
21857  * 
21858  * ## Animation Keyframes
21859  *
21860  * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the 
21861  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
21862  * The previous example can be written with the following syntax:
21863  * 
21864  *     p1.animate({
21865  *         duration: 1000,  //one second total
21866  *         keyframes: {
21867  *             25: {     //from 0 to 250ms (25%)
21868  *                 x: 0
21869  *             },
21870  *             50: {   //from 250ms to 500ms (50%)
21871  *                 y: 0
21872  *             },
21873  *             75: {  //from 500ms to 750ms (75%)
21874  *                 backgroundColor: '#f00'  //red
21875  *             },
21876  *             100: {  //from 750ms to 1sec
21877  *                 opacity: 0
21878  *             }
21879  *         }
21880  *     });
21881  * 
21882  * ## Animation Events
21883  * 
21884  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate}, 
21885  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
21886  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
21887  * fires for each keyframe in your animation.
21888  * 
21889  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
21890  *    
21891  *     startAnimate: function() {
21892  *         var p1 = Ext.get('myElementId');
21893  *         p1.animate({
21894  *            duration: 100,
21895  *             to: {
21896  *                 opacity: 0
21897  *             },
21898  *             listeners: {
21899  *                 beforeanimate:  function() {
21900  *                     // Execute my custom method before the animation
21901  *                     this.myBeforeAnimateFn();
21902  *                 },
21903  *                 afteranimate: function() {
21904  *                     // Execute my custom method after the animation
21905  *                     this.myAfterAnimateFn();
21906  *                 },
21907  *                 scope: this
21908  *         });
21909  *     },
21910  *     myBeforeAnimateFn: function() {
21911  *       // My custom logic
21912  *     },
21913  *     myAfterAnimateFn: function() {
21914  *       // My custom logic
21915  *     }
21916  * 
21917  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
21918  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
21919  * method.  This method will return false if there are no active animations or return the currently 
21920  * running {@link Ext.fx.Anim} instance.
21921  * 
21922  * In this example, we're going to wait for the current animation to finish, then stop any other 
21923  * queued animations before we fade our element's opacity to 0:
21924  * 
21925  *     var curAnim = p1.getActiveAnimation();
21926  *     if (curAnim) {
21927  *         curAnim.on('afteranimate', function() {
21928  *             p1.stopAnimation();
21929  *             p1.animate({
21930  *                 to: {
21931  *                     opacity: 0
21932  *                 }
21933  *             });
21934  *         });
21935  *     }
21936  * 
21937  * @docauthor Jamie Avins <jamie@sencha.com>
21938  */
21939 Ext.define('Ext.util.Animate', {
21940
21941     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
21942
21943     /**
21944      * <p>Perform custom animation on this object.<p>
21945      * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
21946      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
21947      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
21948      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
21949      * <p>Properties include<ul>
21950      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
21951      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
21952      * ths object being animated. See the sections below on Element and Component animation.<div></li>
21953      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
21954      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
21955      * <li><code>easing</code> <div class="sub-desc">A string value describing an easing type to modify the rate of change from the default linear to non-linear. Values may be one of:<code><ul>
21956      * <li>ease</li>
21957      * <li>easeIn</li>
21958      * <li>easeOut</li>
21959      * <li>easeInOut</li>
21960      * <li>backIn</li>
21961      * <li>backOut</li>
21962      * <li>elasticIn</li>
21963      * <li>elasticOut</li>
21964      * <li>bounceIn</li>
21965      * <li>bounceOut</li>
21966      * </ul></code></div></li>
21967      * <li><code>keyframes</code> <div class="sub-desc">This is an object which describes the state of animated properties at certain points along the timeline.
21968      * it is an object containing properties who's names are the percentage along the timeline being described and who's values specify the animation state at that point.</div></li>
21969      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
21970      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
21971      * </ul></p>
21972      * <h3>Animating an {@link Ext.Element Element}</h3>
21973      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21974      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
21975      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
21976      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
21977      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
21978      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
21979      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
21980      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21981      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21982      * <li><code>opacity</code> <div class="sub-desc">The element's <code>opacity</code> value. This must be a value between <code>0</code> and <code>1</code>.</div></li>
21983      * </ul>
21984      * <p><b>Be aware than animating an Element which is being used by an Ext Component without in some way informing the Component about the changed element state
21985      * will result in incorrect Component behaviour. This is because the Component will be using the old state of the element. To avoid this problem, it is now possible to
21986      * directly animate certain properties of Components.</b></p>
21987      * <h3>Animating a {@link Ext.Component Component}</h3>
21988      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21989      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
21990      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
21991      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
21992      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
21993      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21994      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21995      * <li><code>dynamic</code> <div class="sub-desc">Specify as true to update the Component's layout (if it is a Container) at every frame
21996      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
21997      * </ul>
21998      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
21999      * <pre><code>
22000 myWindow = Ext.create('Ext.window.Window', {
22001     title: 'Test Component animation',
22002     width: 500,
22003     height: 300,
22004     layout: {
22005         type: 'hbox',
22006         align: 'stretch'
22007     },
22008     items: [{
22009         title: 'Left: 33%',
22010         margins: '5 0 5 5',
22011         flex: 1
22012     }, {
22013         title: 'Left: 66%',
22014         margins: '5 5 5 5',
22015         flex: 2
22016     }]
22017 });
22018 myWindow.show();
22019 myWindow.header.el.on('click', function() {
22020     myWindow.animate({
22021         to: {
22022             width: (myWindow.getWidth() == 500) ? 700 : 500,
22023             height: (myWindow.getHeight() == 300) ? 400 : 300,
22024         }
22025     });
22026 });
22027 </code></pre>
22028      * <p>For performance reasons, by default, the internal layout is only updated when the Window reaches its final <code>"to"</code> size. If dynamic updating of the Window's child
22029      * Components is required, then configure the animation with <code>dynamic: true</code> and the two child items will maintain their proportions during the animation.</p>
22030      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
22031      * @return {Object} this
22032      */
22033     animate: function(animObj) {
22034         var me = this;
22035         if (Ext.fx.Manager.hasFxBlock(me.id)) {
22036             return me;
22037         }
22038         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
22039         return this;
22040     },
22041
22042     // @private - process the passed fx configuration.
22043     anim: function(config) {
22044         if (!Ext.isObject(config)) {
22045             return (config) ? {} : false;
22046         }
22047
22048         var me = this;
22049
22050         if (config.stopAnimation) {
22051             me.stopAnimation();
22052         }
22053
22054         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
22055
22056         return Ext.apply({
22057             target: me,
22058             paused: true
22059         }, config);
22060     },
22061
22062     /**
22063      * @deprecated 4.0 Replaced by {@link #stopAnimation}
22064      * Stops any running effects and clears this object's internal effects queue if it contains
22065      * any additional effects that haven't started yet.
22066      * @return {Ext.Element} The Element
22067      * @method
22068      */
22069     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
22070
22071     /**
22072      * Stops any running effects and clears this object's internal effects queue if it contains
22073      * any additional effects that haven't started yet.
22074      * @return {Ext.Element} The Element
22075      */
22076     stopAnimation: function() {
22077         Ext.fx.Manager.stopAnimation(this.id);
22078         return this;
22079     },
22080
22081     /**
22082      * Ensures that all effects queued after syncFx is called on this object are
22083      * run concurrently.  This is the opposite of {@link #sequenceFx}.
22084      * @return {Object} this
22085      */
22086     syncFx: function() {
22087         Ext.fx.Manager.setFxDefaults(this.id, {
22088             concurrent: true
22089         });
22090         return this;
22091     },
22092
22093     /**
22094      * Ensures that all effects queued after sequenceFx is called on this object are
22095      * run in sequence.  This is the opposite of {@link #syncFx}.
22096      * @return {Object} this
22097      */
22098     sequenceFx: function() {
22099         Ext.fx.Manager.setFxDefaults(this.id, {
22100             concurrent: false
22101         });
22102         return this;
22103     },
22104
22105     /**
22106      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
22107      * @alias Ext.util.Animate#getActiveAnimation
22108      * @method
22109      */
22110     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
22111
22112     /**
22113      * Returns the current animation if this object has any effects actively running or queued, else returns false.
22114      * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
22115      */
22116     getActiveAnimation: function() {
22117         return Ext.fx.Manager.getActiveAnimation(this.id);
22118     }
22119 }, function(){
22120     // Apply Animate mixin manually until Element is defined in the proper 4.x way
22121     Ext.applyIf(Ext.Element.prototype, this.prototype);
22122     // We need to call this again so the animation methods get copied over to CE
22123     Ext.CompositeElementLite.importElementMethods();
22124 });
22125 /**
22126  * @class Ext.state.Provider
22127  * <p>Abstract base class for state provider implementations. The provider is responsible
22128  * for setting values  and extracting values to/from the underlying storage source. The 
22129  * storage source can vary and the details should be implemented in a subclass. For example
22130  * a provider could use a server side database or the browser localstorage where supported.</p>
22131  *
22132  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
22133  * dates and defines the Provider interface. By default these methods put the value and the
22134  * type information into a delimited string that can be stored. These should be overridden in 
22135  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
22136  */
22137 Ext.define('Ext.state.Provider', {
22138     mixins: {
22139         observable: 'Ext.util.Observable'
22140     },
22141     
22142     /**
22143      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
22144      * Defaults to <tt>'ext-'</tt>
22145      */
22146     prefix: 'ext-',
22147     
22148     constructor : function(config){
22149         config = config || {};
22150         var me = this;
22151         Ext.apply(me, config);
22152         /**
22153          * @event statechange
22154          * Fires when a state change occurs.
22155          * @param {Ext.state.Provider} this This state provider
22156          * @param {String} key The state key which was changed
22157          * @param {String} value The encoded value for the state
22158          */
22159         me.addEvents("statechange");
22160         me.state = {};
22161         me.mixins.observable.constructor.call(me);
22162     },
22163     
22164     /**
22165      * Returns the current value for a key
22166      * @param {String} name The key name
22167      * @param {Object} defaultValue A default value to return if the key's value is not found
22168      * @return {Object} The state data
22169      */
22170     get : function(name, defaultValue){
22171         return typeof this.state[name] == "undefined" ?
22172             defaultValue : this.state[name];
22173     },
22174
22175     /**
22176      * Clears a value from the state
22177      * @param {String} name The key name
22178      */
22179     clear : function(name){
22180         var me = this;
22181         delete me.state[name];
22182         me.fireEvent("statechange", me, name, null);
22183     },
22184
22185     /**
22186      * Sets the value for a key
22187      * @param {String} name The key name
22188      * @param {Object} value The value to set
22189      */
22190     set : function(name, value){
22191         var me = this;
22192         me.state[name] = value;
22193         me.fireEvent("statechange", me, name, value);
22194     },
22195
22196     /**
22197      * Decodes a string previously encoded with {@link #encodeValue}.
22198      * @param {String} value The value to decode
22199      * @return {Object} The decoded value
22200      */
22201     decodeValue : function(value){
22202
22203         // a -> Array
22204         // n -> Number
22205         // d -> Date
22206         // b -> Boolean
22207         // s -> String
22208         // o -> Object
22209         // -> Empty (null)
22210
22211         var me = this,
22212             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
22213             matches = re.exec(unescape(value)),
22214             all,
22215             type,
22216             value,
22217             keyValue;
22218             
22219         if(!matches || !matches[1]){
22220             return; // non state
22221         }
22222         
22223         type = matches[1];
22224         value = matches[2];
22225         switch (type) {
22226             case 'e':
22227                 return null;
22228             case 'n':
22229                 return parseFloat(value);
22230             case 'd':
22231                 return new Date(Date.parse(value));
22232             case 'b':
22233                 return (value == '1');
22234             case 'a':
22235                 all = [];
22236                 if(value != ''){
22237                     Ext.each(value.split('^'), function(val){
22238                         all.push(me.decodeValue(val));
22239                     }, me);
22240                 }
22241                 return all;
22242            case 'o':
22243                 all = {};
22244                 if(value != ''){
22245                     Ext.each(value.split('^'), function(val){
22246                         keyValue = val.split('=');
22247                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
22248                     }, me);
22249                 }
22250                 return all;
22251            default:
22252                 return value;
22253         }
22254     },
22255
22256     /**
22257      * Encodes a value including type information.  Decode with {@link #decodeValue}.
22258      * @param {Object} value The value to encode
22259      * @return {String} The encoded value
22260      */
22261     encodeValue : function(value){
22262         var flat = '',
22263             i = 0,
22264             enc,
22265             len,
22266             key;
22267             
22268         if (value == null) {
22269             return 'e:1';    
22270         } else if(typeof value == 'number') {
22271             enc = 'n:' + value;
22272         } else if(typeof value == 'boolean') {
22273             enc = 'b:' + (value ? '1' : '0');
22274         } else if(Ext.isDate(value)) {
22275             enc = 'd:' + value.toGMTString();
22276         } else if(Ext.isArray(value)) {
22277             for (len = value.length; i < len; i++) {
22278                 flat += this.encodeValue(value[i]);
22279                 if (i != len - 1) {
22280                     flat += '^';
22281                 }
22282             }
22283             enc = 'a:' + flat;
22284         } else if (typeof value == 'object') {
22285             for (key in value) {
22286                 if (typeof value[key] != 'function' && value[key] !== undefined) {
22287                     flat += key + '=' + this.encodeValue(value[key]) + '^';
22288                 }
22289             }
22290             enc = 'o:' + flat.substring(0, flat.length-1);
22291         } else {
22292             enc = 's:' + value;
22293         }
22294         return escape(enc);
22295     }
22296 });
22297 /**
22298  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
22299  * Ext.container.Container on the document with a similar syntax to a CSS selector.
22300  *
22301  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
22302  *
22303  * - `component` or `.component`
22304  * - `gridpanel` or `.gridpanel`
22305  *
22306  * An itemId or id must be prefixed with a #
22307  *
22308  * - `#myContainer`
22309  *
22310  * Attributes must be wrapped in brackets
22311  *
22312  * - `component[autoScroll]`
22313  * - `panel[title="Test"]`
22314  *
22315  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
22316  * the candidate Component will be included in the query:
22317  *
22318  *     var disabledFields = myFormPanel.query("{isDisabled()}");
22319  *
22320  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
22321  *
22322  *     // Function receives array and returns a filtered array.
22323  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
22324  *         var i = 0, l = items.length, c, result = [];
22325  *         for (; i < l; i++) {
22326  *             if (!(c = items[i]).isValid()) {
22327  *                 result.push(c);
22328  *             }
22329  *         }
22330  *         return result;
22331  *     };
22332  *      
22333  *     var invalidFields = myFormPanel.query('field:invalid');
22334  *     if (invalidFields.length) {
22335  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
22336  *         for (var i = 0, l = invalidFields.length; i < l; i++) {
22337  *             invalidFields[i].getEl().frame("red");
22338  *         }
22339  *     }
22340  *
22341  * Default pseudos include:
22342  *
22343  * - not
22344  * - last
22345  *
22346  * Queries return an array of components.
22347  * Here are some example queries.
22348  *
22349  *     // retrieve all Ext.Panels in the document by xtype
22350  *     var panelsArray = Ext.ComponentQuery.query('panel');
22351  *
22352  *     // retrieve all Ext.Panels within the container with an id myCt
22353  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
22354  *
22355  *     // retrieve all direct children which are Ext.Panels within myCt
22356  *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
22357  *
22358  *     // retrieve all grids and trees
22359  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
22360  *
22361  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
22362  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
22363  * {@link Ext.Component#up}.
22364  */
22365 Ext.define('Ext.ComponentQuery', {
22366     singleton: true,
22367     uses: ['Ext.ComponentManager']
22368 }, function() {
22369
22370     var cq = this,
22371
22372         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
22373         // as a member on each item in the passed array.
22374         filterFnPattern = [
22375             'var r = [],',
22376                 'i = 0,',
22377                 'it = items,',
22378                 'l = it.length,',
22379                 'c;',
22380             'for (; i < l; i++) {',
22381                 'c = it[i];',
22382                 'if (c.{0}) {',
22383                    'r.push(c);',
22384                 '}',
22385             '}',
22386             'return r;'
22387         ].join(''),
22388
22389         filterItems = function(items, operation) {
22390             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
22391             // The operation's method loops over each item in the candidate array and
22392             // returns an array of items which match its criteria
22393             return operation.method.apply(this, [ items ].concat(operation.args));
22394         },
22395
22396         getItems = function(items, mode) {
22397             var result = [],
22398                 i = 0,
22399                 length = items.length,
22400                 candidate,
22401                 deep = mode !== '>';
22402                 
22403             for (; i < length; i++) {
22404                 candidate = items[i];
22405                 if (candidate.getRefItems) {
22406                     result = result.concat(candidate.getRefItems(deep));
22407                 }
22408             }
22409             return result;
22410         },
22411
22412         getAncestors = function(items) {
22413             var result = [],
22414                 i = 0,
22415                 length = items.length,
22416                 candidate;
22417             for (; i < length; i++) {
22418                 candidate = items[i];
22419                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
22420                     result.push(candidate);
22421                 }
22422             }
22423             return result;
22424         },
22425
22426         // Filters the passed candidate array and returns only items which match the passed xtype
22427         filterByXType = function(items, xtype, shallow) {
22428             if (xtype === '*') {
22429                 return items.slice();
22430             }
22431             else {
22432                 var result = [],
22433                     i = 0,
22434                     length = items.length,
22435                     candidate;
22436                 for (; i < length; i++) {
22437                     candidate = items[i];
22438                     if (candidate.isXType(xtype, shallow)) {
22439                         result.push(candidate);
22440                     }
22441                 }
22442                 return result;
22443             }
22444         },
22445
22446         // Filters the passed candidate array and returns only items which have the passed className
22447         filterByClassName = function(items, className) {
22448             var EA = Ext.Array,
22449                 result = [],
22450                 i = 0,
22451                 length = items.length,
22452                 candidate;
22453             for (; i < length; i++) {
22454                 candidate = items[i];
22455                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
22456                     result.push(candidate);
22457                 }
22458             }
22459             return result;
22460         },
22461
22462         // Filters the passed candidate array and returns only items which have the specified property match
22463         filterByAttribute = function(items, property, operator, value) {
22464             var result = [],
22465                 i = 0,
22466                 length = items.length,
22467                 candidate;
22468             for (; i < length; i++) {
22469                 candidate = items[i];
22470                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
22471                     result.push(candidate);
22472                 }
22473             }
22474             return result;
22475         },
22476
22477         // Filters the passed candidate array and returns only items which have the specified itemId or id
22478         filterById = function(items, id) {
22479             var result = [],
22480                 i = 0,
22481                 length = items.length,
22482                 candidate;
22483             for (; i < length; i++) {
22484                 candidate = items[i];
22485                 if (candidate.getItemId() === id) {
22486                     result.push(candidate);
22487                 }
22488             }
22489             return result;
22490         },
22491
22492         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
22493         filterByPseudo = function(items, name, value) {
22494             return cq.pseudos[name](items, value);
22495         },
22496
22497         // Determines leading mode
22498         // > for direct child, and ^ to switch to ownerCt axis
22499         modeRe = /^(\s?([>\^])\s?|\s|$)/,
22500
22501         // Matches a token with possibly (true|false) appended for the "shallow" parameter
22502         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
22503
22504         matchers = [{
22505             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
22506             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
22507             method: filterByXType
22508         },{
22509             // checks for [attribute=value]
22510             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
22511             method: filterByAttribute
22512         }, {
22513             // checks for #cmpItemId
22514             re: /^#([\w\-]+)/,
22515             method: filterById
22516         }, {
22517             // checks for :<pseudo_class>(<selector>)
22518             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
22519             method: filterByPseudo
22520         }, {
22521             // checks for {<member_expression>}
22522             re: /^(?:\{([^\}]+)\})/,
22523             method: filterFnPattern
22524         }];
22525
22526     // @class Ext.ComponentQuery.Query
22527     // This internal class is completely hidden in documentation.
22528     cq.Query = Ext.extend(Object, {
22529         constructor: function(cfg) {
22530             cfg = cfg || {};
22531             Ext.apply(this, cfg);
22532         },
22533
22534         // Executes this Query upon the selected root.
22535         // The root provides the initial source of candidate Component matches which are progressively
22536         // filtered by iterating through this Query's operations cache.
22537         // If no root is provided, all registered Components are searched via the ComponentManager.
22538         // root may be a Container who's descendant Components are filtered
22539         // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
22540         // docked items within a Panel.
22541         // root may be an array of candidate Components to filter using this Query.
22542         execute : function(root) {
22543             var operations = this.operations,
22544                 i = 0,
22545                 length = operations.length,
22546                 operation,
22547                 workingItems;
22548
22549             // no root, use all Components in the document
22550             if (!root) {
22551                 workingItems = Ext.ComponentManager.all.getArray();
22552             }
22553             // Root is a candidate Array
22554             else if (Ext.isArray(root)) {
22555                 workingItems = root;
22556             }
22557
22558             // We are going to loop over our operations and take care of them
22559             // one by one.
22560             for (; i < length; i++) {
22561                 operation = operations[i];
22562
22563                 // The mode operation requires some custom handling.
22564                 // All other operations essentially filter down our current
22565                 // working items, while mode replaces our current working
22566                 // items by getting children from each one of our current
22567                 // working items. The type of mode determines the type of
22568                 // children we get. (e.g. > only gets direct children)
22569                 if (operation.mode === '^') {
22570                     workingItems = getAncestors(workingItems || [root]);
22571                 }
22572                 else if (operation.mode) {
22573                     workingItems = getItems(workingItems || [root], operation.mode);
22574                 }
22575                 else {
22576                     workingItems = filterItems(workingItems || getItems([root]), operation);
22577                 }
22578
22579                 // If this is the last operation, it means our current working
22580                 // items are the final matched items. Thus return them!
22581                 if (i === length -1) {
22582                     return workingItems;
22583                 }
22584             }
22585             return [];
22586         },
22587
22588         is: function(component) {
22589             var operations = this.operations,
22590                 components = Ext.isArray(component) ? component : [component],
22591                 originalLength = components.length,
22592                 lastOperation = operations[operations.length-1],
22593                 ln, i;
22594
22595             components = filterItems(components, lastOperation);
22596             if (components.length === originalLength) {
22597                 if (operations.length > 1) {
22598                     for (i = 0, ln = components.length; i < ln; i++) {
22599                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
22600                             return false;
22601                         }
22602                     }
22603                 }
22604                 return true;
22605             }
22606             return false;
22607         }
22608     });
22609
22610     Ext.apply(this, {
22611
22612         // private cache of selectors and matching ComponentQuery.Query objects
22613         cache: {},
22614
22615         // private cache of pseudo class filter functions
22616         pseudos: {
22617             not: function(components, selector){
22618                 var CQ = Ext.ComponentQuery,
22619                     i = 0,
22620                     length = components.length,
22621                     results = [],
22622                     index = -1,
22623                     component;
22624                 
22625                 for(; i < length; ++i) {
22626                     component = components[i];
22627                     if (!CQ.is(component, selector)) {
22628                         results[++index] = component;
22629                     }
22630                 }
22631                 return results;
22632             },
22633             last: function(components) {
22634                 return components[components.length - 1];
22635             }
22636         },
22637
22638         /**
22639          * Returns an array of matched Components from within the passed root object.
22640          *
22641          * This method filters returned Components in a similar way to how CSS selector based DOM
22642          * queries work using a textual selector string.
22643          *
22644          * See class summary for details.
22645          *
22646          * @param {String} selector The selector string to filter returned Components
22647          * @param {Ext.container.Container} root The Container within which to perform the query.
22648          * If omitted, all Components within the document are included in the search.
22649          * 
22650          * This parameter may also be an array of Components to filter according to the selector.</p>
22651          * @returns {Ext.Component[]} The matched Components.
22652          * 
22653          * @member Ext.ComponentQuery
22654          */
22655         query: function(selector, root) {
22656             var selectors = selector.split(','),
22657                 length = selectors.length,
22658                 i = 0,
22659                 results = [],
22660                 noDupResults = [], 
22661                 dupMatcher = {}, 
22662                 query, resultsLn, cmp;
22663
22664             for (; i < length; i++) {
22665                 selector = Ext.String.trim(selectors[i]);
22666                 query = this.cache[selector];
22667                 if (!query) {
22668                     this.cache[selector] = query = this.parse(selector);
22669                 }
22670                 results = results.concat(query.execute(root));
22671             }
22672
22673             // multiple selectors, potential to find duplicates
22674             // lets filter them out.
22675             if (length > 1) {
22676                 resultsLn = results.length;
22677                 for (i = 0; i < resultsLn; i++) {
22678                     cmp = results[i];
22679                     if (!dupMatcher[cmp.id]) {
22680                         noDupResults.push(cmp);
22681                         dupMatcher[cmp.id] = true;
22682                     }
22683                 }
22684                 results = noDupResults;
22685             }
22686             return results;
22687         },
22688
22689         /**
22690          * Tests whether the passed Component matches the selector string.
22691          * @param {Ext.Component} component The Component to test
22692          * @param {String} selector The selector string to test against.
22693          * @return {Boolean} True if the Component matches the selector.
22694          * @member Ext.ComponentQuery
22695          */
22696         is: function(component, selector) {
22697             if (!selector) {
22698                 return true;
22699             }
22700             var query = this.cache[selector];
22701             if (!query) {
22702                 this.cache[selector] = query = this.parse(selector);
22703             }
22704             return query.is(component);
22705         },
22706
22707         parse: function(selector) {
22708             var operations = [],
22709                 length = matchers.length,
22710                 lastSelector,
22711                 tokenMatch,
22712                 matchedChar,
22713                 modeMatch,
22714                 selectorMatch,
22715                 i, matcher, method;
22716
22717             // We are going to parse the beginning of the selector over and
22718             // over again, slicing off the selector any portions we converted into an
22719             // operation, until it is an empty string.
22720             while (selector && lastSelector !== selector) {
22721                 lastSelector = selector;
22722
22723                 // First we check if we are dealing with a token like #, * or an xtype
22724                 tokenMatch = selector.match(tokenRe);
22725
22726                 if (tokenMatch) {
22727                     matchedChar = tokenMatch[1];
22728
22729                     // If the token is prefixed with a # we push a filterById operation to our stack
22730                     if (matchedChar === '#') {
22731                         operations.push({
22732                             method: filterById,
22733                             args: [Ext.String.trim(tokenMatch[2])]
22734                         });
22735                     }
22736                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
22737                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
22738                     else if (matchedChar === '.') {
22739                         operations.push({
22740                             method: filterByClassName,
22741                             args: [Ext.String.trim(tokenMatch[2])]
22742                         });
22743                     }
22744                     // If the token is a * or an xtype string, we push a filterByXType
22745                     // operation to the stack.
22746                     else {
22747                         operations.push({
22748                             method: filterByXType,
22749                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
22750                         });
22751                     }
22752
22753                     // Now we slice of the part we just converted into an operation
22754                     selector = selector.replace(tokenMatch[0], '');
22755                 }
22756
22757                 // If the next part of the query is not a space or > or ^, it means we
22758                 // are going to check for more things that our current selection
22759                 // has to comply to.
22760                 while (!(modeMatch = selector.match(modeRe))) {
22761                     // Lets loop over each type of matcher and execute it
22762                     // on our current selector.
22763                     for (i = 0; selector && i < length; i++) {
22764                         matcher = matchers[i];
22765                         selectorMatch = selector.match(matcher.re);
22766                         method = matcher.method;
22767
22768                         // If we have a match, add an operation with the method
22769                         // associated with this matcher, and pass the regular
22770                         // expression matches are arguments to the operation.
22771                         if (selectorMatch) {
22772                             operations.push({
22773                                 method: Ext.isString(matcher.method)
22774                                     // Turn a string method into a function by formatting the string with our selector matche expression
22775                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
22776                                     // Every expression may be different in different selectors.
22777                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
22778                                     : matcher.method,
22779                                 args: selectorMatch.slice(1)
22780                             });
22781                             selector = selector.replace(selectorMatch[0], '');
22782                             break; // Break on match
22783                         }
22784                     }
22785                 }
22786
22787                 // Now we are going to check for a mode change. This means a space
22788                 // or a > to determine if we are going to select all the children
22789                 // of the currently matched items, or a ^ if we are going to use the
22790                 // ownerCt axis as the candidate source.
22791                 if (modeMatch[1]) { // Assignment, and test for truthiness!
22792                     operations.push({
22793                         mode: modeMatch[2]||modeMatch[1]
22794                     });
22795                     selector = selector.replace(modeMatch[0], '');
22796                 }
22797             }
22798
22799             //  Now that we have all our operations in an array, we are going
22800             // to create a new Query using these operations.
22801             return new cq.Query({
22802                 operations: operations
22803             });
22804         }
22805     });
22806 });
22807 /**
22808  * @class Ext.util.HashMap
22809  * <p>
22810  * Represents a collection of a set of key and value pairs. Each key in the HashMap
22811  * must be unique, the same key cannot exist twice. Access to items is provided via
22812  * the key only. Sample usage:
22813  * <pre><code>
22814 var map = new Ext.util.HashMap();
22815 map.add('key1', 1);
22816 map.add('key2', 2);
22817 map.add('key3', 3);
22818
22819 map.each(function(key, value, length){
22820     console.log(key, value, length);
22821 });
22822  * </code></pre>
22823  * </p>
22824  *
22825  * <p>The HashMap is an unordered class,
22826  * there is no guarantee when iterating over the items that they will be in any particular
22827  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
22828  * </p>
22829  */
22830 Ext.define('Ext.util.HashMap', {
22831     mixins: {
22832         observable: 'Ext.util.Observable'
22833     },
22834
22835     /**
22836      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
22837      * A default is provided that returns the <b>id</b> property on the object. This function is only used
22838      * if the add method is called with a single argument.
22839      */
22840
22841     /**
22842      * Creates new HashMap.
22843      * @param {Object} config (optional) Config object.
22844      */
22845     constructor: function(config) {
22846         config = config || {};
22847         
22848         var me = this,
22849             keyFn = config.keyFn;
22850
22851         me.addEvents(
22852             /**
22853              * @event add
22854              * Fires when a new item is added to the hash
22855              * @param {Ext.util.HashMap} this.
22856              * @param {String} key The key of the added item.
22857              * @param {Object} value The value of the added item.
22858              */
22859             'add',
22860             /**
22861              * @event clear
22862              * Fires when the hash is cleared.
22863              * @param {Ext.util.HashMap} this.
22864              */
22865             'clear',
22866             /**
22867              * @event remove
22868              * Fires when an item is removed from the hash.
22869              * @param {Ext.util.HashMap} this.
22870              * @param {String} key The key of the removed item.
22871              * @param {Object} value The value of the removed item.
22872              */
22873             'remove',
22874             /**
22875              * @event replace
22876              * Fires when an item is replaced in the hash.
22877              * @param {Ext.util.HashMap} this.
22878              * @param {String} key The key of the replaced item.
22879              * @param {Object} value The new value for the item.
22880              * @param {Object} old The old value for the item.
22881              */
22882             'replace'
22883         );
22884
22885         me.mixins.observable.constructor.call(me, config);
22886         me.clear(true);
22887         
22888         if (keyFn) {
22889             me.getKey = keyFn;
22890         }
22891     },
22892
22893     /**
22894      * Gets the number of items in the hash.
22895      * @return {Number} The number of items in the hash.
22896      */
22897     getCount: function() {
22898         return this.length;
22899     },
22900
22901     /**
22902      * Implementation for being able to extract the key from an object if only
22903      * a single argument is passed.
22904      * @private
22905      * @param {String} key The key
22906      * @param {Object} value The value
22907      * @return {Array} [key, value]
22908      */
22909     getData: function(key, value) {
22910         // if we have no value, it means we need to get the key from the object
22911         if (value === undefined) {
22912             value = key;
22913             key = this.getKey(value);
22914         }
22915
22916         return [key, value];
22917     },
22918
22919     /**
22920      * Extracts the key from an object. This is a default implementation, it may be overridden
22921      * @param {Object} o The object to get the key from
22922      * @return {String} The key to use.
22923      */
22924     getKey: function(o) {
22925         return o.id;
22926     },
22927
22928     /**
22929      * Adds an item to the collection. Fires the {@link #add} event when complete.
22930      * @param {String} key <p>The key to associate with the item, or the new item.</p>
22931      * <p>If a {@link #getKey} implementation was specified for this HashMap,
22932      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
22933      * the HashMap will be able to <i>derive</i> the key for the new item.
22934      * In this case just pass the new item in this parameter.</p>
22935      * @param {Object} o The item to add.
22936      * @return {Object} The item added.
22937      */
22938     add: function(key, value) {
22939         var me = this,
22940             data;
22941
22942         if (arguments.length === 1) {
22943             value = key;
22944             key = me.getKey(value);
22945         }
22946
22947         if (me.containsKey(key)) {
22948             return me.replace(key, value);
22949         }
22950
22951         data = me.getData(key, value);
22952         key = data[0];
22953         value = data[1];
22954         me.map[key] = value;
22955         ++me.length;
22956         me.fireEvent('add', me, key, value);
22957         return value;
22958     },
22959
22960     /**
22961      * Replaces an item in the hash. If the key doesn't exist, the
22962      * {@link #add} method will be used.
22963      * @param {String} key The key of the item.
22964      * @param {Object} value The new value for the item.
22965      * @return {Object} The new value of the item.
22966      */
22967     replace: function(key, value) {
22968         var me = this,
22969             map = me.map,
22970             old;
22971
22972         if (!me.containsKey(key)) {
22973             me.add(key, value);
22974         }
22975         old = map[key];
22976         map[key] = value;
22977         me.fireEvent('replace', me, key, value, old);
22978         return value;
22979     },
22980
22981     /**
22982      * Remove an item from the hash.
22983      * @param {Object} o The value of the item to remove.
22984      * @return {Boolean} True if the item was successfully removed.
22985      */
22986     remove: function(o) {
22987         var key = this.findKey(o);
22988         if (key !== undefined) {
22989             return this.removeAtKey(key);
22990         }
22991         return false;
22992     },
22993
22994     /**
22995      * Remove an item from the hash.
22996      * @param {String} key The key to remove.
22997      * @return {Boolean} True if the item was successfully removed.
22998      */
22999     removeAtKey: function(key) {
23000         var me = this,
23001             value;
23002
23003         if (me.containsKey(key)) {
23004             value = me.map[key];
23005             delete me.map[key];
23006             --me.length;
23007             me.fireEvent('remove', me, key, value);
23008             return true;
23009         }
23010         return false;
23011     },
23012
23013     /**
23014      * Retrieves an item with a particular key.
23015      * @param {String} key The key to lookup.
23016      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
23017      */
23018     get: function(key) {
23019         return this.map[key];
23020     },
23021
23022     /**
23023      * Removes all items from the hash.
23024      * @return {Ext.util.HashMap} this
23025      */
23026     clear: function(/* private */ initial) {
23027         var me = this;
23028         me.map = {};
23029         me.length = 0;
23030         if (initial !== true) {
23031             me.fireEvent('clear', me);
23032         }
23033         return me;
23034     },
23035
23036     /**
23037      * Checks whether a key exists in the hash.
23038      * @param {String} key The key to check for.
23039      * @return {Boolean} True if they key exists in the hash.
23040      */
23041     containsKey: function(key) {
23042         return this.map[key] !== undefined;
23043     },
23044
23045     /**
23046      * Checks whether a value exists in the hash.
23047      * @param {Object} value The value to check for.
23048      * @return {Boolean} True if the value exists in the dictionary.
23049      */
23050     contains: function(value) {
23051         return this.containsKey(this.findKey(value));
23052     },
23053
23054     /**
23055      * Return all of the keys in the hash.
23056      * @return {Array} An array of keys.
23057      */
23058     getKeys: function() {
23059         return this.getArray(true);
23060     },
23061
23062     /**
23063      * Return all of the values in the hash.
23064      * @return {Array} An array of values.
23065      */
23066     getValues: function() {
23067         return this.getArray(false);
23068     },
23069
23070     /**
23071      * Gets either the keys/values in an array from the hash.
23072      * @private
23073      * @param {Boolean} isKey True to extract the keys, otherwise, the value
23074      * @return {Array} An array of either keys/values from the hash.
23075      */
23076     getArray: function(isKey) {
23077         var arr = [],
23078             key,
23079             map = this.map;
23080         for (key in map) {
23081             if (map.hasOwnProperty(key)) {
23082                 arr.push(isKey ? key: map[key]);
23083             }
23084         }
23085         return arr;
23086     },
23087
23088     /**
23089      * Executes the specified function once for each item in the hash.
23090      * Returning false from the function will cease iteration.
23091      *
23092      * The paramaters passed to the function are:
23093      * <div class="mdetail-params"><ul>
23094      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
23095      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
23096      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
23097      * </ul></div>
23098      * @param {Function} fn The function to execute.
23099      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
23100      * @return {Ext.util.HashMap} this
23101      */
23102     each: function(fn, scope) {
23103         // copy items so they may be removed during iteration.
23104         var items = Ext.apply({}, this.map),
23105             key,
23106             length = this.length;
23107
23108         scope = scope || this;
23109         for (key in items) {
23110             if (items.hasOwnProperty(key)) {
23111                 if (fn.call(scope, key, items[key], length) === false) {
23112                     break;
23113                 }
23114             }
23115         }
23116         return this;
23117     },
23118
23119     /**
23120      * Performs a shallow copy on this hash.
23121      * @return {Ext.util.HashMap} The new hash object.
23122      */
23123     clone: function() {
23124         var hash = new this.self(),
23125             map = this.map,
23126             key;
23127
23128         hash.suspendEvents();
23129         for (key in map) {
23130             if (map.hasOwnProperty(key)) {
23131                 hash.add(key, map[key]);
23132             }
23133         }
23134         hash.resumeEvents();
23135         return hash;
23136     },
23137
23138     /**
23139      * @private
23140      * Find the key for a value.
23141      * @param {Object} value The value to find.
23142      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
23143      */
23144     findKey: function(value) {
23145         var key,
23146             map = this.map;
23147
23148         for (key in map) {
23149             if (map.hasOwnProperty(key) && map[key] === value) {
23150                 return key;
23151             }
23152         }
23153         return undefined;
23154     }
23155 });
23156
23157 /**
23158  * @class Ext.state.Manager
23159  * This is the global state manager. By default all components that are "state aware" check this class
23160  * for state information if you don't pass them a custom state provider. In order for this class
23161  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23162  <pre><code>
23163 // in your initialization function
23164 init : function(){
23165    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23166    var win = new Window(...);
23167    win.restoreState();
23168 }
23169  </code></pre>
23170  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23171  * there is a common interface that can be used without needing to refer to a specific provider instance
23172  * in every component.
23173  * @singleton
23174  * @docauthor Evan Trimboli <evan@sencha.com>
23175  */
23176 Ext.define('Ext.state.Manager', {
23177     singleton: true,
23178     requires: ['Ext.state.Provider'],
23179     constructor: function() {
23180         this.provider = Ext.create('Ext.state.Provider');
23181     },
23182     
23183     
23184     /**
23185      * Configures the default state provider for your application
23186      * @param {Ext.state.Provider} stateProvider The state provider to set
23187      */
23188     setProvider : function(stateProvider){
23189         this.provider = stateProvider;
23190     },
23191
23192     /**
23193      * Returns the current value for a key
23194      * @param {String} name The key name
23195      * @param {Object} defaultValue The default value to return if the key lookup does not match
23196      * @return {Object} The state data
23197      */
23198     get : function(key, defaultValue){
23199         return this.provider.get(key, defaultValue);
23200     },
23201
23202     /**
23203      * Sets the value for a key
23204      * @param {String} name The key name
23205      * @param {Object} value The state data
23206      */
23207      set : function(key, value){
23208         this.provider.set(key, value);
23209     },
23210
23211     /**
23212      * Clears a value from the state
23213      * @param {String} name The key name
23214      */
23215     clear : function(key){
23216         this.provider.clear(key);
23217     },
23218
23219     /**
23220      * Gets the currently configured state provider
23221      * @return {Ext.state.Provider} The state provider
23222      */
23223     getProvider : function(){
23224         return this.provider;
23225     }
23226 });
23227 /**
23228  * @class Ext.state.Stateful
23229  * A mixin for being able to save the state of an object to an underlying
23230  * {@link Ext.state.Provider}.
23231  */
23232 Ext.define('Ext.state.Stateful', {
23233
23234     /* Begin Definitions */
23235
23236    mixins: {
23237         observable: 'Ext.util.Observable'
23238     },
23239
23240     requires: ['Ext.state.Manager'],
23241
23242     /* End Definitions */
23243
23244     /**
23245      * @cfg {Boolean} stateful
23246      * <p>A flag which causes the object to attempt to restore the state of
23247      * internal properties from a saved state on startup. The object must have
23248      * a <code>{@link #stateId}</code> for state to be managed.
23249      * Auto-generated ids are not guaranteed to be stable across page loads and
23250      * cannot be relied upon to save and restore the same state for a object.<p>
23251      * <p>For state saving to work, the state manager's provider must have been
23252      * set to an implementation of {@link Ext.state.Provider} which overrides the
23253      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23254      * methods to save and recall name/value pairs. A built-in implementation,
23255      * {@link Ext.state.CookieProvider} is available.</p>
23256      * <p>To set the state provider for the current page:</p>
23257      * <pre><code>
23258 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23259     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23260 }));
23261      * </code></pre>
23262      * <p>A stateful object attempts to save state when one of the events
23263      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23264      * <p>To save state, a stateful object first serializes its state by
23265      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23266      * nothing. The developer must provide an implementation which returns an
23267      * object hash which represents the restorable state of the object.</p>
23268      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23269      * which uses the configured {@link Ext.state.Provider} to save the object
23270      * keyed by the <code>{@link #stateId}</code>.</p>
23271      * <p>During construction, a stateful object attempts to <i>restore</i>
23272      * its state by calling {@link Ext.state.Manager#get} passing the
23273      * <code>{@link #stateId}</code></p>
23274      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23275      * The default implementation of <code>{@link #applyState}</code> simply copies
23276      * properties into the object, but a developer may override this to support
23277      * more behaviour.</p>
23278      * <p>You can perform extra processing on state save and restore by attaching
23279      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23280      * {@link #beforestatesave} and {@link #statesave} events.</p>
23281      */
23282     stateful: true,
23283
23284     /**
23285      * @cfg {String} stateId
23286      * The unique id for this object to use for state management purposes.
23287      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23288      */
23289
23290     /**
23291      * @cfg {String[]} stateEvents
23292      * <p>An array of events that, when fired, should trigger this object to
23293      * save its state. Defaults to none. <code>stateEvents</code> may be any type
23294      * of event supported by this object, including browser or custom events
23295      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23296      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23297      * restoring object state.</p>
23298      */
23299
23300     /**
23301      * @cfg {Number} saveDelay
23302      * A buffer to be applied if many state events are fired within a short period.
23303      */
23304     saveDelay: 100,
23305
23306     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23307
23308     constructor: function(config) {
23309         var me = this;
23310
23311         config = config || {};
23312         if (Ext.isDefined(config.stateful)) {
23313             me.stateful = config.stateful;
23314         }
23315         if (Ext.isDefined(config.saveDelay)) {
23316             me.saveDelay = config.saveDelay;
23317         }
23318         me.stateId = me.stateId || config.stateId;
23319
23320         if (!me.stateEvents) {
23321             me.stateEvents = [];
23322         }
23323         if (config.stateEvents) {
23324             me.stateEvents.concat(config.stateEvents);
23325         }
23326         this.addEvents(
23327             /**
23328              * @event beforestaterestore
23329              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23330              * @param {Ext.state.Stateful} this
23331              * @param {Object} state The hash of state values returned from the StateProvider. If this
23332              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23333              * that simply copies property values into this object. The method maybe overriden to
23334              * provide custom state restoration.
23335              */
23336             'beforestaterestore',
23337
23338             /**
23339              * @event staterestore
23340              * Fires after the state of the object is restored.
23341              * @param {Ext.state.Stateful} this
23342              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23343              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23344              * object. The method maybe overriden to provide custom state restoration.
23345              */
23346             'staterestore',
23347
23348             /**
23349              * @event beforestatesave
23350              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23351              * @param {Ext.state.Stateful} this
23352              * @param {Object} state The hash of state values. This is determined by calling
23353              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23354              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23355              * has a null implementation.
23356              */
23357             'beforestatesave',
23358
23359             /**
23360              * @event statesave
23361              * Fires after the state of the object is saved to the configured state provider.
23362              * @param {Ext.state.Stateful} this
23363              * @param {Object} state The hash of state values. This is determined by calling
23364              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23365              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23366              * has a null implementation.
23367              */
23368             'statesave'
23369         );
23370         me.mixins.observable.constructor.call(me);
23371         if (me.stateful !== false) {
23372             me.initStateEvents();
23373             me.initState();
23374         }
23375     },
23376
23377     /**
23378      * Initializes any state events for this object.
23379      * @private
23380      */
23381     initStateEvents: function() {
23382         this.addStateEvents(this.stateEvents);
23383     },
23384
23385     /**
23386      * Add events that will trigger the state to be saved.
23387      * @param {String/String[]} events The event name or an array of event names.
23388      */
23389     addStateEvents: function(events){
23390         if (!Ext.isArray(events)) {
23391             events = [events];
23392         }
23393
23394         var me = this,
23395             i = 0,
23396             len = events.length;
23397
23398         for (; i < len; ++i) {
23399             me.on(events[i], me.onStateChange, me);
23400         }
23401     },
23402
23403     /**
23404      * This method is called when any of the {@link #stateEvents} are fired.
23405      * @private
23406      */
23407     onStateChange: function(){
23408         var me = this,
23409             delay = me.saveDelay;
23410
23411         if (delay > 0) {
23412             if (!me.stateTask) {
23413                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23414             }
23415             me.stateTask.delay(me.saveDelay);
23416         } else {
23417             me.saveState();
23418         }
23419     },
23420
23421     /**
23422      * Saves the state of the object to the persistence store.
23423      * @private
23424      */
23425     saveState: function() {
23426         var me = this,
23427             id,
23428             state;
23429
23430         if (me.stateful !== false) {
23431             id = me.getStateId();
23432             if (id) {
23433                 state = me.getState();
23434                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23435                     Ext.state.Manager.set(id, state);
23436                     me.fireEvent('statesave', me, state);
23437                 }
23438             }
23439         }
23440     },
23441
23442     /**
23443      * Gets the current state of the object. By default this function returns null,
23444      * it should be overridden in subclasses to implement methods for getting the state.
23445      * @return {Object} The current state
23446      */
23447     getState: function(){
23448         return null;
23449     },
23450
23451     /**
23452      * Applies the state to the object. This should be overridden in subclasses to do
23453      * more complex state operations. By default it applies the state properties onto
23454      * the current object.
23455      * @param {Object} state The state
23456      */
23457     applyState: function(state) {
23458         if (state) {
23459             Ext.apply(this, state);
23460         }
23461     },
23462
23463     /**
23464      * Gets the state id for this object.
23465      * @return {String} The state id, null if not found.
23466      */
23467     getStateId: function() {
23468         var me = this,
23469             id = me.stateId;
23470
23471         if (!id) {
23472             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
23473         }
23474         return id;
23475     },
23476
23477     /**
23478      * Initializes the state of the object upon construction.
23479      * @private
23480      */
23481     initState: function(){
23482         var me = this,
23483             id = me.getStateId(),
23484             state;
23485
23486         if (me.stateful !== false) {
23487             if (id) {
23488                 state = Ext.state.Manager.get(id);
23489                 if (state) {
23490                     state = Ext.apply({}, state);
23491                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
23492                         me.applyState(state);
23493                         me.fireEvent('staterestore', me, state);
23494                     }
23495                 }
23496             }
23497         }
23498     },
23499
23500     /**
23501      * Conditionally saves a single property from this object to the given state object.
23502      * The idea is to only save state which has changed from the initial state so that
23503      * current software settings do not override future software settings. Only those
23504      * values that are user-changed state should be saved.
23505      *
23506      * @param {String} propName The name of the property to save.
23507      * @param {Object} state The state object in to which to save the property.
23508      * @param {String} stateName (optional) The name to use for the property in state.
23509      * @return {Boolean} True if the property was saved, false if not.
23510      */
23511     savePropToState: function (propName, state, stateName) {
23512         var me = this,
23513             value = me[propName],
23514             config = me.initialConfig;
23515
23516         if (me.hasOwnProperty(propName)) {
23517             if (!config || config[propName] !== value) {
23518                 if (state) {
23519                     state[stateName || propName] = value;
23520                 }
23521                 return true;
23522             }
23523         }
23524         return false;
23525     },
23526
23527     savePropsToState: function (propNames, state) {
23528         var me = this;
23529         Ext.each(propNames, function (propName) {
23530             me.savePropToState(propName, state);
23531         });
23532         return state;
23533     },
23534
23535     /**
23536      * Destroys this stateful object.
23537      */
23538     destroy: function(){
23539         var task = this.stateTask;
23540         if (task) {
23541             task.cancel();
23542         }
23543         this.clearListeners();
23544
23545     }
23546
23547 });
23548
23549 /**
23550  * Base Manager class
23551  */
23552 Ext.define('Ext.AbstractManager', {
23553
23554     /* Begin Definitions */
23555
23556     requires: ['Ext.util.HashMap'],
23557
23558     /* End Definitions */
23559
23560     typeName: 'type',
23561
23562     constructor: function(config) {
23563         Ext.apply(this, config || {});
23564
23565         /**
23566          * @property {Ext.util.HashMap} all
23567          * Contains all of the items currently managed
23568          */
23569         this.all = Ext.create('Ext.util.HashMap');
23570
23571         this.types = {};
23572     },
23573
23574     /**
23575      * Returns an item by id.
23576      * For additional details see {@link Ext.util.HashMap#get}.
23577      * @param {String} id The id of the item
23578      * @return {Object} The item, undefined if not found.
23579      */
23580     get : function(id) {
23581         return this.all.get(id);
23582     },
23583
23584     /**
23585      * Registers an item to be managed
23586      * @param {Object} item The item to register
23587      */
23588     register: function(item) {
23589         this.all.add(item);
23590     },
23591
23592     /**
23593      * Unregisters an item by removing it from this manager
23594      * @param {Object} item The item to unregister
23595      */
23596     unregister: function(item) {
23597         this.all.remove(item);
23598     },
23599
23600     /**
23601      * Registers a new item constructor, keyed by a type key.
23602      * @param {String} type The mnemonic string by which the class may be looked up.
23603      * @param {Function} cls The new instance class.
23604      */
23605     registerType : function(type, cls) {
23606         this.types[type] = cls;
23607         cls[this.typeName] = type;
23608     },
23609
23610     /**
23611      * Checks if an item type is registered.
23612      * @param {String} type The mnemonic string by which the class may be looked up
23613      * @return {Boolean} Whether the type is registered.
23614      */
23615     isRegistered : function(type){
23616         return this.types[type] !== undefined;
23617     },
23618
23619     /**
23620      * Creates and returns an instance of whatever this manager manages, based on the supplied type and
23621      * config object.
23622      * @param {Object} config The config object
23623      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
23624      * @return {Object} The instance of whatever this manager is managing
23625      */
23626     create: function(config, defaultType) {
23627         var type        = config[this.typeName] || config.type || defaultType,
23628             Constructor = this.types[type];
23629
23630
23631         return new Constructor(config);
23632     },
23633
23634     /**
23635      * Registers a function that will be called when an item with the specified id is added to the manager.
23636      * This will happen on instantiation.
23637      * @param {String} id The item id
23638      * @param {Function} fn The callback function. Called with a single parameter, the item.
23639      * @param {Object} scope The scope (this reference) in which the callback is executed.
23640      * Defaults to the item.
23641      */
23642     onAvailable : function(id, fn, scope){
23643         var all = this.all,
23644             item;
23645         
23646         if (all.containsKey(id)) {
23647             item = all.get(id);
23648             fn.call(scope || item, item);
23649         } else {
23650             all.on('add', function(map, key, item){
23651                 if (key == id) {
23652                     fn.call(scope || item, item);
23653                     all.un('add', fn, scope);
23654                 }
23655             });
23656         }
23657     },
23658     
23659     /**
23660      * Executes the specified function once for each item in the collection.
23661      * @param {Function} fn The function to execute.
23662      * @param {String} fn.key The key of the item
23663      * @param {Number} fn.value The value of the item
23664      * @param {Number} fn.length The total number of items in the collection
23665      * @param {Boolean} fn.return False to cease iteration.
23666      * @param {Object} scope The scope to execute in. Defaults to `this`.
23667      */
23668     each: function(fn, scope){
23669         this.all.each(fn, scope || this);    
23670     },
23671     
23672     /**
23673      * Gets the number of items in the collection.
23674      * @return {Number} The number of items in the collection.
23675      */
23676     getCount: function(){
23677         return this.all.getCount();
23678     }
23679 });
23680
23681 /**
23682  * @class Ext.ComponentManager
23683  * @extends Ext.AbstractManager
23684  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
23685  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
23686  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
23687  * <p>This object also provides a registry of available Component <i>classes</i>
23688  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
23689  * The <code>xtype</code> provides a way to avoid instantiating child Components
23690  * when creating a full, nested config object for a complete Ext page.</p>
23691  * <p>A child Component may be specified simply as a <i>config object</i>
23692  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
23693  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
23694  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
23695  * @singleton
23696  */
23697 Ext.define('Ext.ComponentManager', {
23698     extend: 'Ext.AbstractManager',
23699     alternateClassName: 'Ext.ComponentMgr',
23700     
23701     singleton: true,
23702     
23703     typeName: 'xtype',
23704     
23705     /**
23706      * Creates a new Component from the specified config object using the
23707      * config object's xtype to determine the class to instantiate.
23708      * @param {Object} config A configuration object for the Component you wish to create.
23709      * @param {Function} defaultType (optional) The constructor to provide the default Component type if
23710      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
23711      * @return {Ext.Component} The newly instantiated Component.
23712      */
23713     create: function(component, defaultType){
23714         if (component instanceof Ext.AbstractComponent) {
23715             return component;
23716         }
23717         else if (Ext.isString(component)) {
23718             return Ext.createByAlias('widget.' + component);
23719         }
23720         else {
23721             var type = component.xtype || defaultType,
23722                 config = component;
23723             
23724             return Ext.createByAlias('widget.' + type, config);
23725         }
23726     },
23727
23728     registerType: function(type, cls) {
23729         this.types[type] = cls;
23730         cls[this.typeName] = type;
23731         cls.prototype[this.typeName] = type;
23732     }
23733 });
23734 /**
23735  * An abstract base class which provides shared methods for Components across the Sencha product line.
23736  *
23737  * Please refer to sub class's documentation
23738  * @private
23739  */
23740 Ext.define('Ext.AbstractComponent', {
23741
23742     /* Begin Definitions */
23743     requires: [
23744         'Ext.ComponentQuery',
23745         'Ext.ComponentManager'
23746     ],
23747
23748     mixins: {
23749         observable: 'Ext.util.Observable',
23750         animate: 'Ext.util.Animate',
23751         state: 'Ext.state.Stateful'
23752     },
23753
23754     // The "uses" property specifies class which are used in an instantiated AbstractComponent.
23755     // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
23756     uses: [
23757         'Ext.PluginManager',
23758         'Ext.ComponentManager',
23759         'Ext.Element',
23760         'Ext.DomHelper',
23761         'Ext.XTemplate',
23762         'Ext.ComponentQuery',
23763         'Ext.ComponentLoader',
23764         'Ext.EventManager',
23765         'Ext.layout.Layout',
23766         'Ext.layout.component.Auto',
23767         'Ext.LoadMask',
23768         'Ext.ZIndexManager'
23769     ],
23770
23771     statics: {
23772         AUTO_ID: 1000
23773     },
23774
23775     /* End Definitions */
23776
23777     isComponent: true,
23778
23779     getAutoId: function() {
23780         return ++Ext.AbstractComponent.AUTO_ID;
23781     },
23782
23783
23784     /**
23785      * @cfg {String} id
23786      * The **unique id of this component instance.**
23787      *
23788      * It should not be necessary to use this configuration except for singleton objects in your application. Components
23789      * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
23790      *
23791      * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
23792      * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
23793      * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
23794      * its descendant Components by selector.
23795      *
23796      * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
23797      * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
23798      * component uniquely, and also to select sub-elements using this component's id as the parent.
23799      *
23800      * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
23801      *
23802      * **Note**: to access the container of a Component see `{@link #ownerCt}`.
23803      *
23804      * Defaults to an {@link #getId auto-assigned id}.
23805      */
23806
23807     /**
23808      * @cfg {String} itemId
23809      * An itemId can be used as an alternative way to get a reference to a component when no object reference is
23810      * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
23811      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
23812      * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
23813      * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
23814      * which requires a **unique** `{@link #id}`.
23815      *
23816      *     var c = new Ext.panel.Panel({ //
23817      *         {@link Ext.Component#height height}: 300,
23818      *         {@link #renderTo}: document.body,
23819      *         {@link Ext.container.Container#layout layout}: 'auto',
23820      *         {@link Ext.container.Container#items items}: [
23821      *             {
23822      *                 itemId: 'p1',
23823      *                 {@link Ext.panel.Panel#title title}: 'Panel 1',
23824      *                 {@link Ext.Component#height height}: 150
23825      *             },
23826      *             {
23827      *                 itemId: 'p2',
23828      *                 {@link Ext.panel.Panel#title title}: 'Panel 2',
23829      *                 {@link Ext.Component#height height}: 150
23830      *             }
23831      *         ]
23832      *     })
23833      *     p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
23834      *     p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
23835      *
23836      * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
23837      * `{@link Ext.container.Container#child}`.
23838      *
23839      * **Note**: to access the container of an item see {@link #ownerCt}.
23840      */
23841
23842     /**
23843      * @property {Ext.Container} ownerCt
23844      * This Component's owner {@link Ext.container.Container Container} (is set automatically
23845      * when this Component is added to a Container). Read-only.
23846      *
23847      * **Note**: to access items within the Container see {@link #itemId}.
23848      */
23849
23850     /**
23851      * @property {Boolean} layoutManagedWidth
23852      * @private
23853      * Flag set by the container layout to which this Component is added.
23854      * If the layout manages this Component's width, it sets the value to 1.
23855      * If it does NOT manage the width, it sets it to 2.
23856      * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
23857      */
23858
23859     /**
23860      * @property {Boolean} layoutManagedHeight
23861      * @private
23862      * Flag set by the container layout to which this Component is added.
23863      * If the layout manages this Component's height, it sets the value to 1.
23864      * If it does NOT manage the height, it sets it to 2.
23865      * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
23866      */
23867
23868     /**
23869      * @cfg {String/Object} autoEl
23870      * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
23871      * encapsulate this Component.
23872      *
23873      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
23874      * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
23875      * complex DOM structure specified by their own {@link #renderTpl}s.
23876      *
23877      * This is intended to allow the developer to create application-specific utility Components encapsulated by
23878      * different DOM elements. Example usage:
23879      *
23880      *     {
23881      *         xtype: 'component',
23882      *         autoEl: {
23883      *             tag: 'img',
23884      *             src: 'http://www.example.com/example.jpg'
23885      *         }
23886      *     }, {
23887      *         xtype: 'component',
23888      *         autoEl: {
23889      *             tag: 'blockquote',
23890      *             html: 'autoEl is cool!'
23891      *         }
23892      *     }, {
23893      *         xtype: 'container',
23894      *         autoEl: 'ul',
23895      *         cls: 'ux-unordered-list',
23896      *         items: {
23897      *             xtype: 'component',
23898      *             autoEl: 'li',
23899      *             html: 'First list item'
23900      *         }
23901      *     }
23902      */
23903
23904     /**
23905      * @cfg {Ext.XTemplate/String/String[]} renderTpl
23906      * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
23907      * {@link #getEl Element}.
23908      *
23909      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
23910      * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
23911      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
23912      * classes which use a more complex DOM structure, provide their own template definitions.
23913      *
23914      * This is intended to allow the developer to create application-specific utility Components with customized
23915      * internal structure.
23916      *
23917      * Upon rendering, any created child elements may be automatically imported into object properties using the
23918      * {@link #renderSelectors} and {@link #childEls} options.
23919      */
23920     renderTpl: null,
23921
23922     /**
23923      * @cfg {Object} renderData
23924      *
23925      * The data used by {@link #renderTpl} in addition to the following property values of the component:
23926      *
23927      * - id
23928      * - ui
23929      * - uiCls
23930      * - baseCls
23931      * - componentCls
23932      * - frame
23933      *
23934      * See {@link #renderSelectors} and {@link #childEls} for usage examples.
23935      */
23936
23937     /**
23938      * @cfg {Object} renderSelectors
23939      * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
23940      * created by the render process.
23941      *
23942      * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
23943      * and the found Elements are added as properties to the Component using the `renderSelector` property name.
23944      *
23945      * For example, a Component which renderes a title and description into its element:
23946      *
23947      *     Ext.create('Ext.Component', {
23948      *         renderTo: Ext.getBody(),
23949      *         renderTpl: [
23950      *             '<h1 class="title">{title}</h1>',
23951      *             '<p>{desc}</p>'
23952      *         ],
23953      *         renderData: {
23954      *             title: "Error",
23955      *             desc: "Something went wrong"
23956      *         },
23957      *         renderSelectors: {
23958      *             titleEl: 'h1.title',
23959      *             descEl: 'p'
23960      *         },
23961      *         listeners: {
23962      *             afterrender: function(cmp){
23963      *                 // After rendering the component will have a titleEl and descEl properties
23964      *                 cmp.titleEl.setStyle({color: "red"});
23965      *             }
23966      *         }
23967      *     });
23968      *
23969      * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
23970      * Component after render), see {@link #childEls} and {@link #addChildEls}.
23971      */
23972
23973     /**
23974      * @cfg {Object[]} childEls
23975      * An array describing the child elements of the Component. Each member of the array
23976      * is an object with these properties:
23977      *
23978      * - `name` - The property name on the Component for the child element.
23979      * - `itemId` - The id to combine with the Component's id that is the id of the child element.
23980      * - `id` - The id of the child element.
23981      *
23982      * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
23983      *
23984      * For example, a Component which renders a title and body text:
23985      *
23986      *     Ext.create('Ext.Component', {
23987      *         renderTo: Ext.getBody(),
23988      *         renderTpl: [
23989      *             '<h1 id="{id}-title">{title}</h1>',
23990      *             '<p>{msg}</p>',
23991      *         ],
23992      *         renderData: {
23993      *             title: "Error",
23994      *             msg: "Something went wrong"
23995      *         },
23996      *         childEls: ["title"],
23997      *         listeners: {
23998      *             afterrender: function(cmp){
23999      *                 // After rendering the component will have a title property
24000      *                 cmp.title.setStyle({color: "red"});
24001      *             }
24002      *         }
24003      *     });
24004      *
24005      * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
24006      */
24007
24008     /**
24009      * @cfg {String/HTMLElement/Ext.Element} renderTo
24010      * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
24011      *
24012      * **Notes:**
24013      *
24014      * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
24015      * It is the responsibility of the {@link Ext.container.Container Container}'s
24016      * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
24017      *
24018      * When using this config, a call to render() is not required.
24019      *
24020      * See `{@link #render}` also.
24021      */
24022
24023     /**
24024      * @cfg {Boolean} frame
24025      * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
24026      * graphical rounded frame around the Component content.
24027      *
24028      * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
24029      * Explorer prior to version 9 which do not support rounded corners natively.
24030      *
24031      * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
24032      */
24033
24034     /**
24035      * @property {Object} frameSize
24036      * Read-only property indicating the width of any framing elements which were added within the encapsulating element
24037      * to provide graphical, rounded borders. See the {@link #frame} config.
24038      *
24039      * This is an object containing the frame width in pixels for all four sides of the Component containing the
24040      * following properties:
24041      *
24042      * @property {Number} frameSize.top The width of the top framing element in pixels.
24043      * @property {Number} frameSize.right The width of the right framing element in pixels.
24044      * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
24045      * @property {Number} frameSize.left The width of the left framing element in pixels.
24046      */
24047
24048     /**
24049      * @cfg {String/Object} componentLayout
24050      * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
24051      * manager which sizes a Component's internal structure in response to the Component being sized.
24052      *
24053      * Generally, developers will not use this configuration as all provided Components which need their internal
24054      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
24055      *
24056      * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
24057      * class which simply sizes the Component's encapsulating element to the height and width specified in the
24058      * {@link #setSize} method.
24059      */
24060
24061     /**
24062      * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
24063      * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
24064      * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
24065      */
24066
24067     /**
24068      * @cfg {Object} data
24069      * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
24070      */
24071
24072     /**
24073      * @cfg {String} xtype
24074      * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
24075      * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
24076      *
24077      * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
24078      * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
24079      *
24080      *     Ext.define('PressMeButton', {
24081      *         extend: 'Ext.button.Button',
24082      *         alias: 'widget.pressmebutton',
24083      *         text: 'Press Me'
24084      *     })
24085      *
24086      * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
24087      * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
24088      * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
24089      * until they are actually needed. In complex, nested layouts containing many Components, this can make a
24090      * noticeable improvement in performance.
24091      *
24092      *     // Explicit creation of contained Components:
24093      *     var panel = new Ext.Panel({
24094      *        ...
24095      *        items: [
24096      *           Ext.create('Ext.button.Button', {
24097      *              text: 'OK'
24098      *           })
24099      *        ]
24100      *     };
24101      *
24102      *     // Implicit creation using xtype:
24103      *     var panel = new Ext.Panel({
24104      *        ...
24105      *        items: [{
24106      *           xtype: 'button',
24107      *           text: 'OK'
24108      *        }]
24109      *     };
24110      *
24111      * In the first example, the button will always be created immediately during the panel's initialization. With
24112      * many added Components, this approach could potentially slow the rendering of the page. In the second example,
24113      * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
24114      * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
24115      * will never consume any resources whatsoever.
24116      */
24117
24118     /**
24119      * @cfg {String} tplWriteMode
24120      * The Ext.(X)Template method to use when updating the content area of the Component.
24121      * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
24122      */
24123     tplWriteMode: 'overwrite',
24124
24125     /**
24126      * @cfg {String} [baseCls='x-component']
24127      * The base CSS class to apply to this components's element. This will also be prepended to elements within this
24128      * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
24129      * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
24130      * componentCls to add specific styling for this component.
24131      */
24132     baseCls: Ext.baseCSSPrefix + 'component',
24133
24134     /**
24135      * @cfg {String} componentCls
24136      * CSS Class to be added to a components root level element to give distinction to it via styling.
24137      */
24138
24139     /**
24140      * @cfg {String} [cls='']
24141      * An optional extra CSS class that will be added to this component's Element. This can be useful
24142      * for adding customized styles to the component or any of its children using standard CSS rules.
24143      */
24144
24145     /**
24146      * @cfg {String} [overCls='']
24147      * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
24148      * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
24149      * component or any of its children using standard CSS rules.
24150      */
24151
24152     /**
24153      * @cfg {String} [disabledCls='x-item-disabled']
24154      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
24155      */
24156     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
24157
24158     /**
24159      * @cfg {String/String[]} ui
24160      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
24161      */
24162     ui: 'default',
24163
24164     /**
24165      * @cfg {String[]} uiCls
24166      * An array of of classNames which are currently applied to this component
24167      * @private
24168      */
24169     uiCls: [],
24170
24171     /**
24172      * @cfg {String} style
24173      * A custom style specification to be applied to this component's Element. Should be a valid argument to
24174      * {@link Ext.Element#applyStyles}.
24175      *
24176      *     new Ext.panel.Panel({
24177      *         title: 'Some Title',
24178      *         renderTo: Ext.getBody(),
24179      *         width: 400, height: 300,
24180      *         layout: 'form',
24181      *         items: [{
24182      *             xtype: 'textarea',
24183      *             style: {
24184      *                 width: '95%',
24185      *                 marginBottom: '10px'
24186      *             }
24187      *         },
24188      *         new Ext.button.Button({
24189      *             text: 'Send',
24190      *             minWidth: '100',
24191      *             style: {
24192      *                 marginBottom: '10px'
24193      *             }
24194      *         })
24195      *         ]
24196      *     });
24197      */
24198
24199     /**
24200      * @cfg {Number} width
24201      * The width of this component in pixels.
24202      */
24203
24204     /**
24205      * @cfg {Number} height
24206      * The height of this component in pixels.
24207      */
24208
24209     /**
24210      * @cfg {Number/String} border
24211      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
24212      * be a CSS style specification for each style, for example: '10 5 3 10'.
24213      */
24214
24215     /**
24216      * @cfg {Number/String} padding
24217      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
24218      * can be a CSS style specification for each style, for example: '10 5 3 10'.
24219      */
24220
24221     /**
24222      * @cfg {Number/String} margin
24223      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
24224      * be a CSS style specification for each style, for example: '10 5 3 10'.
24225      */
24226
24227     /**
24228      * @cfg {Boolean} hidden
24229      * True to hide the component.
24230      */
24231     hidden: false,
24232
24233     /**
24234      * @cfg {Boolean} disabled
24235      * True to disable the component.
24236      */
24237     disabled: false,
24238
24239     /**
24240      * @cfg {Boolean} [draggable=false]
24241      * Allows the component to be dragged.
24242      */
24243
24244     /**
24245      * @property {Boolean} draggable
24246      * Read-only property indicating whether or not the component can be dragged
24247      */
24248     draggable: false,
24249
24250     /**
24251      * @cfg {Boolean} floating
24252      * Create the Component as a floating and use absolute positioning.
24253      *
24254      * The z-index of floating Components is handled by a ZIndexManager. If you simply render a floating Component into the DOM, it will be managed
24255      * by the global {@link Ext.WindowManager WindowManager}.
24256      *
24257      * If you include a floating Component as a child item of a Container, then upon render, ExtJS will seek an ancestor floating Component to house a new
24258      * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
24259      *
24260      * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
24261      */
24262     floating: false,
24263
24264     /**
24265      * @cfg {String} hideMode
24266      * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
24267      *
24268      *   - `'display'` : The Component will be hidden using the `display: none` style.
24269      *   - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
24270      *   - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
24271      *     This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
24272      *     Component having zero dimensions.
24273      */
24274     hideMode: 'display',
24275
24276     /**
24277      * @cfg {String} contentEl
24278      * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
24279      *
24280      * This config option is used to take an existing HTML element and place it in the layout element of a new component
24281      * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
24282      *
24283      * **Notes:**
24284      *
24285      * The specified HTML element is appended to the layout element of the component _after any configured
24286      * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
24287      * the {@link #render} event is fired.
24288      *
24289      * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
24290      * scheme that the Component may use. It is just HTML. Layouts operate on child
24291      * **`{@link Ext.container.Container#items items}`**.
24292      *
24293      * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
24294      * is rendered to the panel.
24295      */
24296
24297     /**
24298      * @cfg {String/Object} [html='']
24299      * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
24300      * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
24301      * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
24302      * is appended.
24303      */
24304
24305     /**
24306      * @cfg {Boolean} styleHtmlContent
24307      * True to automatically style the html inside the content target of this component (body for panels).
24308      */
24309     styleHtmlContent: false,
24310
24311     /**
24312      * @cfg {String} [styleHtmlCls='x-html']
24313      * The class that is added to the content target when you set styleHtmlContent to true.
24314      */
24315     styleHtmlCls: Ext.baseCSSPrefix + 'html',
24316
24317     /**
24318      * @cfg {Number} minHeight
24319      * The minimum value in pixels which this Component will set its height to.
24320      *
24321      * **Warning:** This will override any size management applied by layout managers.
24322      */
24323     /**
24324      * @cfg {Number} minWidth
24325      * The minimum value in pixels which this Component will set its width to.
24326      *
24327      * **Warning:** This will override any size management applied by layout managers.
24328      */
24329     /**
24330      * @cfg {Number} maxHeight
24331      * The maximum value in pixels which this Component will set its height to.
24332      *
24333      * **Warning:** This will override any size management applied by layout managers.
24334      */
24335     /**
24336      * @cfg {Number} maxWidth
24337      * The maximum value in pixels which this Component will set its width to.
24338      *
24339      * **Warning:** This will override any size management applied by layout managers.
24340      */
24341
24342     /**
24343      * @cfg {Ext.ComponentLoader/Object} loader
24344      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
24345      */
24346
24347     /**
24348      * @cfg {Boolean} autoShow
24349      * True to automatically show the component upon creation. This config option may only be used for
24350      * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
24351      */
24352     autoShow: false,
24353
24354     /**
24355      * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
24356      * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
24357      * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
24358      * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
24359      *
24360      * Specify as `true` to have this Component render to the document body upon first show.
24361      *
24362      * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
24363      * show.
24364      *
24365      * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
24366      */
24367     autoRender: false,
24368
24369     needsLayout: false,
24370
24371     // @private
24372     allowDomMove: true,
24373
24374     /**
24375      * @cfg {Object/Object[]} plugins
24376      * An object or array of objects that will provide custom functionality for this component. The only requirement for
24377      * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
24378      * is created, if any plugins are available, the component will call the init method on each plugin, passing a
24379      * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
24380      * its functionality.
24381      */
24382
24383     /**
24384      * @property {Boolean} rendered
24385      * Read-only property indicating whether or not the component has been rendered.
24386      */
24387     rendered: false,
24388
24389     /**
24390      * @property {Number} componentLayoutCounter
24391      * @private
24392      * The number of component layout calls made on this object.
24393      */
24394     componentLayoutCounter: 0,
24395
24396     weight: 0,
24397
24398     trimRe: /^\s+|\s+$/g,
24399     spacesRe: /\s+/,
24400
24401
24402     /**
24403      * @property {Boolean} maskOnDisable
24404      * This is an internal flag that you use when creating custom components. By default this is set to true which means
24405      * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
24406      * override this property to false since they want to implement custom disable logic.
24407      */
24408     maskOnDisable: true,
24409
24410     /**
24411      * Creates new Component.
24412      * @param {Object} config  (optional) Config object.
24413      */
24414     constructor : function(config) {
24415         var me = this,
24416             i, len;
24417
24418         config = config || {};
24419         me.initialConfig = config;
24420         Ext.apply(me, config);
24421
24422         me.addEvents(
24423             /**
24424              * @event beforeactivate
24425              * Fires before a Component has been visually activated. Returning false from an event listener can prevent
24426              * the activate from occurring.
24427              * @param {Ext.Component} this
24428              */
24429             'beforeactivate',
24430             /**
24431              * @event activate
24432              * Fires after a Component has been visually activated.
24433              * @param {Ext.Component} this
24434              */
24435             'activate',
24436             /**
24437              * @event beforedeactivate
24438              * Fires before a Component has been visually deactivated. Returning false from an event listener can
24439              * prevent the deactivate from occurring.
24440              * @param {Ext.Component} this
24441              */
24442             'beforedeactivate',
24443             /**
24444              * @event deactivate
24445              * Fires after a Component has been visually deactivated.
24446              * @param {Ext.Component} this
24447              */
24448             'deactivate',
24449             /**
24450              * @event added
24451              * Fires after a Component had been added to a Container.
24452              * @param {Ext.Component} this
24453              * @param {Ext.container.Container} container Parent Container
24454              * @param {Number} pos position of Component
24455              */
24456             'added',
24457             /**
24458              * @event disable
24459              * Fires after the component is disabled.
24460              * @param {Ext.Component} this
24461              */
24462             'disable',
24463             /**
24464              * @event enable
24465              * Fires after the component is enabled.
24466              * @param {Ext.Component} this
24467              */
24468             'enable',
24469             /**
24470              * @event beforeshow
24471              * Fires before the component is shown when calling the {@link #show} method. Return false from an event
24472              * handler to stop the show.
24473              * @param {Ext.Component} this
24474              */
24475             'beforeshow',
24476             /**
24477              * @event show
24478              * Fires after the component is shown when calling the {@link #show} method.
24479              * @param {Ext.Component} this
24480              */
24481             'show',
24482             /**
24483              * @event beforehide
24484              * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
24485              * handler to stop the hide.
24486              * @param {Ext.Component} this
24487              */
24488             'beforehide',
24489             /**
24490              * @event hide
24491              * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
24492              * method.
24493              * @param {Ext.Component} this
24494              */
24495             'hide',
24496             /**
24497              * @event removed
24498              * Fires when a component is removed from an Ext.container.Container
24499              * @param {Ext.Component} this
24500              * @param {Ext.container.Container} ownerCt Container which holds the component
24501              */
24502             'removed',
24503             /**
24504              * @event beforerender
24505              * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
24506              * {@link #render}.
24507              * @param {Ext.Component} this
24508              */
24509             'beforerender',
24510             /**
24511              * @event render
24512              * Fires after the component markup is {@link #rendered}.
24513              * @param {Ext.Component} this
24514              */
24515             'render',
24516             /**
24517              * @event afterrender
24518              * Fires after the component rendering is finished.
24519              *
24520              * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
24521              * afterRender method defined for the Component.
24522              * @param {Ext.Component} this
24523              */
24524             'afterrender',
24525             /**
24526              * @event beforedestroy
24527              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
24528              * {@link #destroy}.
24529              * @param {Ext.Component} this
24530              */
24531             'beforedestroy',
24532             /**
24533              * @event destroy
24534              * Fires after the component is {@link #destroy}ed.
24535              * @param {Ext.Component} this
24536              */
24537             'destroy',
24538             /**
24539              * @event resize
24540              * Fires after the component is resized.
24541              * @param {Ext.Component} this
24542              * @param {Number} adjWidth The box-adjusted width that was set
24543              * @param {Number} adjHeight The box-adjusted height that was set
24544              */
24545             'resize',
24546             /**
24547              * @event move
24548              * Fires after the component is moved.
24549              * @param {Ext.Component} this
24550              * @param {Number} x The new x position
24551              * @param {Number} y The new y position
24552              */
24553             'move'
24554         );
24555
24556         me.getId();
24557
24558         me.mons = [];
24559         me.additionalCls = [];
24560         me.renderData = me.renderData || {};
24561         me.renderSelectors = me.renderSelectors || {};
24562
24563         if (me.plugins) {
24564             me.plugins = [].concat(me.plugins);
24565             me.constructPlugins();
24566         }
24567
24568         me.initComponent();
24569
24570         // ititComponent gets a chance to change the id property before registering
24571         Ext.ComponentManager.register(me);
24572
24573         // Dont pass the config so that it is not applied to 'this' again
24574         me.mixins.observable.constructor.call(me);
24575         me.mixins.state.constructor.call(me, config);
24576
24577         // Save state on resize.
24578         this.addStateEvents('resize');
24579
24580         // Move this into Observable?
24581         if (me.plugins) {
24582             me.plugins = [].concat(me.plugins);
24583             for (i = 0, len = me.plugins.length; i < len; i++) {
24584                 me.plugins[i] = me.initPlugin(me.plugins[i]);
24585             }
24586         }
24587
24588         me.loader = me.getLoader();
24589
24590         if (me.renderTo) {
24591             me.render(me.renderTo);
24592             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
24593             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
24594             // implications to afterRender so we cannot do that.
24595         }
24596
24597         if (me.autoShow) {
24598             me.show();
24599         }
24600
24601     },
24602
24603     initComponent: function () {
24604         // This is called again here to allow derived classes to add plugin configs to the
24605         // plugins array before calling down to this, the base initComponent.
24606         this.constructPlugins();
24607     },
24608
24609     /**
24610      * The supplied default state gathering method for the AbstractComponent class.
24611      *
24612      * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
24613      * state.
24614      *
24615      * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
24616      * to the result if this basic state is to be saved.
24617      *
24618      * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
24619      * configured for the document.
24620      *
24621      * @return {Object}
24622      */
24623     getState: function() {
24624         var me = this,
24625             layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
24626             state = {
24627                 collapsed: me.collapsed
24628             },
24629             width = me.width,
24630             height = me.height,
24631             cm = me.collapseMemento,
24632             anchors;
24633
24634         // If a Panel-local collapse has taken place, use remembered values as the dimensions.
24635         // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
24636         if (me.collapsed && cm) {
24637             if (Ext.isDefined(cm.data.width)) {
24638                 width = cm.width;
24639             }
24640             if (Ext.isDefined(cm.data.height)) {
24641                 height = cm.height;
24642             }
24643         }
24644
24645         // If we have flex, only store the perpendicular dimension.
24646         if (layout && me.flex) {
24647             state.flex = me.flex;
24648             if (layout.perpendicularPrefix) {
24649                 state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
24650             } else {
24651             }
24652         }
24653         // If we have anchor, only store dimensions which are *not* being anchored
24654         else if (layout && me.anchor) {
24655             state.anchor = me.anchor;
24656             anchors = me.anchor.split(' ').concat(null);
24657             if (!anchors[0]) {
24658                 if (me.width) {
24659                     state.width = width;
24660                 }
24661             }
24662             if (!anchors[1]) {
24663                 if (me.height) {
24664                     state.height = height;
24665                 }
24666             }
24667         }
24668         // Store dimensions.
24669         else {
24670             if (me.width) {
24671                 state.width = width;
24672             }
24673             if (me.height) {
24674                 state.height = height;
24675             }
24676         }
24677
24678         // Don't save dimensions if they are unchanged from the original configuration.
24679         if (state.width == me.initialConfig.width) {
24680             delete state.width;
24681         }
24682         if (state.height == me.initialConfig.height) {
24683             delete state.height;
24684         }
24685
24686         // If a Box layout was managing the perpendicular dimension, don't save that dimension
24687         if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
24688             delete state[layout.perpendicularPrefix];
24689         }
24690         return state;
24691     },
24692
24693     show: Ext.emptyFn,
24694
24695     animate: function(animObj) {
24696         var me = this,
24697             to;
24698
24699         animObj = animObj || {};
24700         to = animObj.to || {};
24701
24702         if (Ext.fx.Manager.hasFxBlock(me.id)) {
24703             return me;
24704         }
24705         // Special processing for animating Component dimensions.
24706         if (!animObj.dynamic && (to.height || to.width)) {
24707             var curWidth = me.getWidth(),
24708                 w = curWidth,
24709                 curHeight = me.getHeight(),
24710                 h = curHeight,
24711                 needsResize = false;
24712
24713             if (to.height && to.height > curHeight) {
24714                 h = to.height;
24715                 needsResize = true;
24716             }
24717             if (to.width && to.width > curWidth) {
24718                 w = to.width;
24719                 needsResize = true;
24720             }
24721
24722             // If any dimensions are being increased, we must resize the internal structure
24723             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
24724             // The animation will then progressively reveal the larger content.
24725             if (needsResize) {
24726                 var clearWidth = !Ext.isNumber(me.width),
24727                     clearHeight = !Ext.isNumber(me.height);
24728
24729                 me.componentLayout.childrenChanged = true;
24730                 me.setSize(w, h, me.ownerCt);
24731                 me.el.setSize(curWidth, curHeight);
24732                 if (clearWidth) {
24733                     delete me.width;
24734                 }
24735                 if (clearHeight) {
24736                     delete me.height;
24737                 }
24738             }
24739         }
24740         return me.mixins.animate.animate.apply(me, arguments);
24741     },
24742
24743     /**
24744      * This method finds the topmost active layout who's processing will eventually determine the size and position of
24745      * this Component.
24746      *
24747      * This method is useful when dynamically adding Components into Containers, and some processing must take place
24748      * after the final sizing and positioning of the Component has been performed.
24749      *
24750      * @return {Ext.Component}
24751      */
24752     findLayoutController: function() {
24753         return this.findParentBy(function(c) {
24754             // Return true if we are at the root of the Container tree
24755             // or this Container's layout is busy but the next one up is not.
24756             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
24757         });
24758     },
24759
24760     onShow : function() {
24761         // Layout if needed
24762         var needsLayout = this.needsLayout;
24763         if (Ext.isObject(needsLayout)) {
24764             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
24765         }
24766     },
24767
24768     constructPlugin: function(plugin) {
24769         if (plugin.ptype && typeof plugin.init != 'function') {
24770             plugin.cmp = this;
24771             plugin = Ext.PluginManager.create(plugin);
24772         }
24773         else if (typeof plugin == 'string') {
24774             plugin = Ext.PluginManager.create({
24775                 ptype: plugin,
24776                 cmp: this
24777             });
24778         }
24779         return plugin;
24780     },
24781
24782     /**
24783      * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
24784      * appropriate instances.
24785      */
24786     constructPlugins: function() {
24787         var me = this,
24788             plugins = me.plugins,
24789             i, len;
24790
24791         if (plugins) {
24792             for (i = 0, len = plugins.length; i < len; i++) {
24793                 // this just returns already-constructed plugin instances...
24794                 plugins[i] = me.constructPlugin(plugins[i]);
24795             }
24796         }
24797     },
24798
24799     // @private
24800     initPlugin : function(plugin) {
24801         plugin.init(this);
24802
24803         return plugin;
24804     },
24805
24806     /**
24807      * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
24808      * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
24809      * document.body
24810      */
24811     doAutoRender: function() {
24812         var me = this;
24813         if (me.floating) {
24814             me.render(document.body);
24815         } else {
24816             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
24817         }
24818     },
24819
24820     // @private
24821     render : function(container, position) {
24822         var me = this;
24823
24824         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
24825
24826             // Flag set during the render process.
24827             // It can be used to inhibit event-driven layout calls during the render phase
24828             me.rendering = true;
24829
24830             // If this.el is defined, we want to make sure we are dealing with
24831             // an Ext Element.
24832             if (me.el) {
24833                 me.el = Ext.get(me.el);
24834             }
24835
24836             // Perform render-time processing for floating Components
24837             if (me.floating) {
24838                 me.onFloatRender();
24839             }
24840
24841             container = me.initContainer(container);
24842
24843             me.onRender(container, position);
24844
24845             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
24846             // This means DISPLAY, VISIBILITY or OFFSETS.
24847             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
24848
24849             if (me.overCls) {
24850                 me.el.hover(me.addOverCls, me.removeOverCls, me);
24851             }
24852
24853             me.fireEvent('render', me);
24854
24855             me.initContent();
24856
24857             me.afterRender(container);
24858             me.fireEvent('afterrender', me);
24859
24860             me.initEvents();
24861
24862             if (me.hidden) {
24863                 // Hiding during the render process should not perform any ancillary
24864                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
24865                 // So just make the element hidden according to the configured hideMode
24866                 me.el.hide();
24867             }
24868
24869             if (me.disabled) {
24870                 // pass silent so the event doesn't fire the first time.
24871                 me.disable(true);
24872             }
24873
24874             // Delete the flag once the rendering is done.
24875             delete me.rendering;
24876         }
24877         return me;
24878     },
24879
24880     // @private
24881     onRender : function(container, position) {
24882         var me = this,
24883             el = me.el,
24884             styles = me.initStyles(),
24885             renderTpl, renderData, i;
24886
24887         position = me.getInsertPosition(position);
24888
24889         if (!el) {
24890             if (position) {
24891                 el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
24892             }
24893             else {
24894                 el = Ext.DomHelper.append(container, me.getElConfig(), true);
24895             }
24896         }
24897         else if (me.allowDomMove !== false) {
24898             if (position) {
24899                 container.dom.insertBefore(el.dom, position);
24900             } else {
24901                 container.dom.appendChild(el.dom);
24902             }
24903         }
24904
24905         if (Ext.scopeResetCSS && !me.ownerCt) {
24906             // If this component's el is the body element, we add the reset class to the html tag
24907             if (el.dom == Ext.getBody().dom) {
24908                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
24909             }
24910             else {
24911                 // Else we wrap this element in an element that adds the reset class.
24912                 me.resetEl = el.wrap({
24913                     cls: Ext.baseCSSPrefix + 'reset'
24914                 });
24915             }
24916         }
24917
24918         me.setUI(me.ui);
24919
24920         el.addCls(me.initCls());
24921         el.setStyle(styles);
24922
24923         // Here we check if the component has a height set through style or css.
24924         // If it does then we set the this.height to that value and it won't be
24925         // considered an auto height component
24926         // if (this.height === undefined) {
24927         //     var height = el.getHeight();
24928         //     // This hopefully means that the panel has an explicit height set in style or css
24929         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
24930         //         this.height = height;
24931         //     }
24932         // }
24933
24934         me.el = el;
24935
24936         me.initFrame();
24937
24938         renderTpl = me.initRenderTpl();
24939         if (renderTpl) {
24940             renderData = me.initRenderData();
24941             renderTpl.append(me.getTargetEl(), renderData);
24942         }
24943
24944         me.applyRenderSelectors();
24945
24946         me.rendered = true;
24947     },
24948
24949     // @private
24950     afterRender : function() {
24951         var me = this,
24952             pos,
24953             xy;
24954
24955         me.getComponentLayout();
24956
24957         // Set the size if a size is configured, or if this is the outermost Container.
24958         // Also, if this is a collapsed Panel, it needs an initial component layout
24959         // to lay out its header so that it can have a height determined.
24960         if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
24961             me.setSize(me.width, me.height);
24962         } else {
24963             // It is expected that child items be rendered before this method returns and
24964             // the afterrender event fires. Since we aren't going to do the layout now, we
24965             // must render the child items. This is handled implicitly above in the layout
24966             // caused by setSize.
24967             me.renderChildren();
24968         }
24969
24970         // For floaters, calculate x and y if they aren't defined by aligning
24971         // the sized element to the center of either the container or the ownerCt
24972         if (me.floating && (me.x === undefined || me.y === undefined)) {
24973             if (me.floatParent) {
24974                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
24975                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
24976             } else {
24977                 xy = me.el.getAlignToXY(me.container, 'c-c');
24978                 pos = me.container.translatePoints(xy[0], xy[1]);
24979             }
24980             me.x = me.x === undefined ? pos.left: me.x;
24981             me.y = me.y === undefined ? pos.top: me.y;
24982         }
24983
24984         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
24985             me.setPosition(me.x, me.y);
24986         }
24987
24988         if (me.styleHtmlContent) {
24989             me.getTargetEl().addCls(me.styleHtmlCls);
24990         }
24991     },
24992
24993     /**
24994      * @private
24995      * Called by Component#doAutoRender
24996      *
24997      * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
24998      *
24999      * Components added in ths way will not participate in any layout, but will be rendered
25000      * upon first show in the way that {@link Ext.window.Window Window}s are.
25001      */
25002     registerFloatingItem: function(cmp) {
25003         var me = this;
25004         if (!me.floatingItems) {
25005             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
25006         }
25007         me.floatingItems.register(cmp);
25008     },
25009
25010     renderChildren: function () {
25011         var me = this,
25012             layout = me.getComponentLayout();
25013
25014         me.suspendLayout = true;
25015         layout.renderChildren();
25016         delete me.suspendLayout;
25017     },
25018
25019     frameCls: Ext.baseCSSPrefix + 'frame',
25020
25021     frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
25022
25023     frameElementCls: {
25024         tl: [],
25025         tc: [],
25026         tr: [],
25027         ml: [],
25028         mc: [],
25029         mr: [],
25030         bl: [],
25031         bc: [],
25032         br: []
25033     },
25034
25035     frameTpl: [
25036         '<tpl if="top">',
25037             '<tpl if="left"><div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25038                 '<tpl if="right"><div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25039                     '<div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>',
25040                 '<tpl if="right"></div></tpl>',
25041             '<tpl if="left"></div></tpl>',
25042         '</tpl>',
25043         '<tpl if="left"><div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25044             '<tpl if="right"><div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25045                 '<div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" role="presentation"></div>',
25046             '<tpl if="right"></div></tpl>',
25047         '<tpl if="left"></div></tpl>',
25048         '<tpl if="bottom">',
25049             '<tpl if="left"><div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25050                 '<tpl if="right"><div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25051                     '<div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>',
25052                 '<tpl if="right"></div></tpl>',
25053             '<tpl if="left"></div></tpl>',
25054         '</tpl>'
25055     ],
25056
25057     frameTableTpl: [
25058         '<table><tbody>',
25059             '<tpl if="top">',
25060                 '<tr>',
25061                     '<tpl if="left"><td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>',
25062                     '<td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>',
25063                     '<tpl if="right"><td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25064                 '</tr>',
25065             '</tpl>',
25066             '<tr>',
25067                 '<tpl if="left"><td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25068                 '<td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" style="background-position: 0 0;" role="presentation"></td>',
25069                 '<tpl if="right"><td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25070             '</tr>',
25071             '<tpl if="bottom">',
25072                 '<tr>',
25073                     '<tpl if="left"><td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25074                     '<td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>',
25075                     '<tpl if="right"><td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25076                 '</tr>',
25077             '</tpl>',
25078         '</tbody></table>'
25079     ],
25080
25081     /**
25082      * @private
25083      */
25084     initFrame : function() {
25085         if (Ext.supports.CSS3BorderRadius) {
25086             return false;
25087         }
25088
25089         var me = this,
25090             frameInfo = me.getFrameInfo(),
25091             frameWidth = frameInfo.width,
25092             frameTpl = me.getFrameTpl(frameInfo.table),
25093             frameGenId;
25094
25095         if (me.frame) {
25096             // since we render id's into the markup and id's NEED to be unique, we have a
25097             // simple strategy for numbering their generations.
25098             me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
25099             frameGenId = me.id + '-frame' + frameGenId;
25100
25101             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
25102             frameTpl.insertFirst(me.el, Ext.apply({}, {
25103                 fgid:       frameGenId,
25104                 ui:         me.ui,
25105                 uiCls:      me.uiCls,
25106                 frameCls:   me.frameCls,
25107                 baseCls:    me.baseCls,
25108                 frameWidth: frameWidth,
25109                 top:        !!frameInfo.top,
25110                 left:       !!frameInfo.left,
25111                 right:      !!frameInfo.right,
25112                 bottom:     !!frameInfo.bottom
25113             }, me.getFramePositions(frameInfo)));
25114
25115             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
25116             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
25117
25118             // Clean out the childEls for the old frame elements (the majority of the els)
25119             me.removeChildEls(function (c) {
25120                 return c.id && me.frameIdRegex.test(c.id);
25121             });
25122
25123             // Add the childEls for each of the new frame elements
25124             Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
25125                 me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
25126             });
25127         }
25128     },
25129
25130     updateFrame: function() {
25131         if (Ext.supports.CSS3BorderRadius) {
25132             return false;
25133         }
25134
25135         var me = this,
25136             wasTable = this.frameSize && this.frameSize.table,
25137             oldFrameTL = this.frameTL,
25138             oldFrameBL = this.frameBL,
25139             oldFrameML = this.frameML,
25140             oldFrameMC = this.frameMC,
25141             newMCClassName;
25142
25143         this.initFrame();
25144
25145         if (oldFrameMC) {
25146             if (me.frame) {
25147                 // Reapply render selectors
25148                 delete me.frameTL;
25149                 delete me.frameTC;
25150                 delete me.frameTR;
25151                 delete me.frameML;
25152                 delete me.frameMC;
25153                 delete me.frameMR;
25154                 delete me.frameBL;
25155                 delete me.frameBC;
25156                 delete me.frameBR;
25157                 this.applyRenderSelectors();
25158
25159                 // Store the class names set on the new mc
25160                 newMCClassName = this.frameMC.dom.className;
25161
25162                 // Replace the new mc with the old mc
25163                 oldFrameMC.insertAfter(this.frameMC);
25164                 this.frameMC.remove();
25165
25166                 // Restore the reference to the old frame mc as the framebody
25167                 this.frameBody = this.frameMC = oldFrameMC;
25168
25169                 // Apply the new mc classes to the old mc element
25170                 oldFrameMC.dom.className = newMCClassName;
25171
25172                 // Remove the old framing
25173                 if (wasTable) {
25174                     me.el.query('> table')[1].remove();
25175                 }
25176                 else {
25177                     if (oldFrameTL) {
25178                         oldFrameTL.remove();
25179                     }
25180                     if (oldFrameBL) {
25181                         oldFrameBL.remove();
25182                     }
25183                     oldFrameML.remove();
25184                 }
25185             }
25186             else {
25187                 // We were framed but not anymore. Move all content from the old frame to the body
25188
25189             }
25190         }
25191         else if (me.frame) {
25192             this.applyRenderSelectors();
25193         }
25194     },
25195
25196     getFrameInfo: function() {
25197         if (Ext.supports.CSS3BorderRadius) {
25198             return false;
25199         }
25200
25201         var me = this,
25202             left = me.el.getStyle('background-position-x'),
25203             top = me.el.getStyle('background-position-y'),
25204             info, frameInfo = false, max;
25205
25206         // Some browsers dont support background-position-x and y, so for those
25207         // browsers let's split background-position into two parts.
25208         if (!left && !top) {
25209             info = me.el.getStyle('background-position').split(' ');
25210             left = info[0];
25211             top = info[1];
25212         }
25213
25214         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
25215         // the background position of this.el from the css to indicate to IE that this component needs
25216         // framing. We parse it here and change the markup accordingly.
25217         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
25218             max = Math.max;
25219
25220             frameInfo = {
25221                 // Table markup starts with 110, div markup with 100.
25222                 table: left.substr(0, 3) == '110',
25223
25224                 // Determine if we are dealing with a horizontal or vertical component
25225                 vertical: top.substr(0, 3) == '110',
25226
25227                 // Get and parse the different border radius sizes
25228                 top:    max(left.substr(3, 2), left.substr(5, 2)),
25229                 right:  max(left.substr(5, 2), top.substr(3, 2)),
25230                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
25231                 left:   max(top.substr(5, 2), left.substr(3, 2))
25232             };
25233
25234             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
25235
25236             // Just to be sure we set the background image of the el to none.
25237             me.el.setStyle('background-image', 'none');
25238         }
25239
25240         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
25241         // This way IE can't figure out what sizes to use and thus framing can't work.
25242         if (me.frame === true && !frameInfo) {
25243         }
25244
25245         me.frame = me.frame || !!frameInfo;
25246         me.frameSize = frameInfo || false;
25247
25248         return frameInfo;
25249     },
25250
25251     getFramePositions: function(frameInfo) {
25252         var me = this,
25253             frameWidth = frameInfo.width,
25254             dock = me.dock,
25255             positions, tc, bc, ml, mr;
25256
25257         if (frameInfo.vertical) {
25258             tc = '0 -' + (frameWidth * 0) + 'px';
25259             bc = '0 -' + (frameWidth * 1) + 'px';
25260
25261             if (dock && dock == "right") {
25262                 tc = 'right -' + (frameWidth * 0) + 'px';
25263                 bc = 'right -' + (frameWidth * 1) + 'px';
25264             }
25265
25266             positions = {
25267                 tl: '0 -' + (frameWidth * 0) + 'px',
25268                 tr: '0 -' + (frameWidth * 1) + 'px',
25269                 bl: '0 -' + (frameWidth * 2) + 'px',
25270                 br: '0 -' + (frameWidth * 3) + 'px',
25271
25272                 ml: '-' + (frameWidth * 1) + 'px 0',
25273                 mr: 'right 0',
25274
25275                 tc: tc,
25276                 bc: bc
25277             };
25278         } else {
25279             ml = '-' + (frameWidth * 0) + 'px 0';
25280             mr = 'right 0';
25281
25282             if (dock && dock == "bottom") {
25283                 ml = 'left bottom';
25284                 mr = 'right bottom';
25285             }
25286
25287             positions = {
25288                 tl: '0 -' + (frameWidth * 2) + 'px',
25289                 tr: 'right -' + (frameWidth * 3) + 'px',
25290                 bl: '0 -' + (frameWidth * 4) + 'px',
25291                 br: 'right -' + (frameWidth * 5) + 'px',
25292
25293                 ml: ml,
25294                 mr: mr,
25295
25296                 tc: '0 -' + (frameWidth * 0) + 'px',
25297                 bc: '0 -' + (frameWidth * 1) + 'px'
25298             };
25299         }
25300
25301         return positions;
25302     },
25303
25304     /**
25305      * @private
25306      */
25307     getFrameTpl : function(table) {
25308         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
25309     },
25310
25311     /**
25312      * Creates an array of class names from the configurations to add to this Component's `el` on render.
25313      *
25314      * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
25315      *
25316      * @return {String[]} An array of class names with which the Component's element will be rendered.
25317      * @private
25318      */
25319     initCls: function() {
25320         var me = this,
25321             cls = [];
25322
25323         cls.push(me.baseCls);
25324
25325         if (Ext.isDefined(me.cmpCls)) {
25326             if (Ext.isDefined(Ext.global.console)) {
25327                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
25328             }
25329             me.componentCls = me.cmpCls;
25330             delete me.cmpCls;
25331         }
25332
25333         if (me.componentCls) {
25334             cls.push(me.componentCls);
25335         } else {
25336             me.componentCls = me.baseCls;
25337         }
25338         if (me.cls) {
25339             cls.push(me.cls);
25340             delete me.cls;
25341         }
25342
25343         return cls.concat(me.additionalCls);
25344     },
25345
25346     /**
25347      * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
25348      * uiCls set on the component and rename them so they include the new UI
25349      * @param {String} ui The new UI for the component
25350      */
25351     setUI: function(ui) {
25352         var me = this,
25353             oldUICls = Ext.Array.clone(me.uiCls),
25354             newUICls = [],
25355             classes = [],
25356             cls,
25357             i;
25358
25359         //loop through all exisiting uiCls and update the ui in them
25360         for (i = 0; i < oldUICls.length; i++) {
25361             cls = oldUICls[i];
25362
25363             classes = classes.concat(me.removeClsWithUI(cls, true));
25364             newUICls.push(cls);
25365         }
25366
25367         if (classes.length) {
25368             me.removeCls(classes);
25369         }
25370
25371         //remove the UI from the element
25372         me.removeUIFromElement();
25373
25374         //set the UI
25375         me.ui = ui;
25376
25377         //add the new UI to the elemend
25378         me.addUIToElement();
25379
25380         //loop through all exisiting uiCls and update the ui in them
25381         classes = [];
25382         for (i = 0; i < newUICls.length; i++) {
25383             cls = newUICls[i];
25384             classes = classes.concat(me.addClsWithUI(cls, true));
25385         }
25386
25387         if (classes.length) {
25388             me.addCls(classes);
25389         }
25390     },
25391
25392     /**
25393      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
25394      * component.
25395      * @param {String/String[]} cls A string or an array of strings to add to the uiCls
25396      * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
25397      */
25398     addClsWithUI: function(cls, skip) {
25399         var me = this,
25400             classes = [],
25401             i;
25402
25403         if (!Ext.isArray(cls)) {
25404             cls = [cls];
25405         }
25406
25407         for (i = 0; i < cls.length; i++) {
25408             if (cls[i] && !me.hasUICls(cls[i])) {
25409                 me.uiCls = Ext.Array.clone(me.uiCls);
25410                 me.uiCls.push(cls[i]);
25411
25412                 classes = classes.concat(me.addUIClsToElement(cls[i]));
25413             }
25414         }
25415
25416         if (skip !== true) {
25417             me.addCls(classes);
25418         }
25419
25420         return classes;
25421     },
25422
25423     /**
25424      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
25425      * elements of this component.
25426      * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
25427      */
25428     removeClsWithUI: function(cls, skip) {
25429         var me = this,
25430             classes = [],
25431             i;
25432
25433         if (!Ext.isArray(cls)) {
25434             cls = [cls];
25435         }
25436
25437         for (i = 0; i < cls.length; i++) {
25438             if (cls[i] && me.hasUICls(cls[i])) {
25439                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
25440
25441                 classes = classes.concat(me.removeUIClsFromElement(cls[i]));
25442             }
25443         }
25444
25445         if (skip !== true) {
25446             me.removeCls(classes);
25447         }
25448
25449         return classes;
25450     },
25451
25452     /**
25453      * Checks if there is currently a specified uiCls
25454      * @param {String} cls The cls to check
25455      */
25456     hasUICls: function(cls) {
25457         var me = this,
25458             uiCls = me.uiCls || [];
25459
25460         return Ext.Array.contains(uiCls, cls);
25461     },
25462
25463     /**
25464      * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
25465      * than just the components element.
25466      * @param {String} ui The UI to remove from the element
25467      */
25468     addUIClsToElement: function(cls, force) {
25469         var me = this,
25470             result = [],
25471             frameElementCls = me.frameElementCls;
25472
25473         result.push(Ext.baseCSSPrefix + cls);
25474         result.push(me.baseCls + '-' + cls);
25475         result.push(me.baseCls + '-' + me.ui + '-' + cls);
25476
25477         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
25478             // define each element of the frame
25479             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25480                 classes, i, j, el;
25481
25482             // loop through each of them, and if they are defined add the ui
25483             for (i = 0; i < els.length; i++) {
25484                 el = me['frame' + els[i].toUpperCase()];
25485                 classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
25486                 if (el && el.dom) {
25487                     el.addCls(classes);
25488                 } else {
25489                     for (j = 0; j < classes.length; j++) {
25490                         if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
25491                             frameElementCls[els[i]].push(classes[j]);
25492                         }
25493                     }
25494                 }
25495             }
25496         }
25497
25498         me.frameElementCls = frameElementCls;
25499
25500         return result;
25501     },
25502
25503     /**
25504      * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
25505      * will be: `this.baseCls + '-' + ui`
25506      * @param {String} ui The UI to add to the element
25507      */
25508     removeUIClsFromElement: function(cls, force) {
25509         var me = this,
25510             result = [],
25511             frameElementCls = me.frameElementCls;
25512
25513         result.push(Ext.baseCSSPrefix + cls);
25514         result.push(me.baseCls + '-' + cls);
25515         result.push(me.baseCls + '-' + me.ui + '-' + cls);
25516
25517         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
25518             // define each element of the frame
25519             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25520                 i, el;
25521             cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
25522             // loop through each of them, and if they are defined add the ui
25523             for (i = 0; i < els.length; i++) {
25524                 el = me['frame' + els[i].toUpperCase()];
25525                 if (el && el.dom) {
25526                     el.removeCls(cls);
25527                 } else {
25528                     Ext.Array.remove(frameElementCls[els[i]], cls);
25529                 }
25530             }
25531         }
25532
25533         me.frameElementCls = frameElementCls;
25534
25535         return result;
25536     },
25537
25538     /**
25539      * Method which adds a specified UI to the components element.
25540      * @private
25541      */
25542     addUIToElement: function(force) {
25543         var me = this,
25544             frameElementCls = me.frameElementCls;
25545
25546         me.addCls(me.baseCls + '-' + me.ui);
25547
25548         if (me.frame && !Ext.supports.CSS3BorderRadius) {
25549             // define each element of the frame
25550             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25551                 i, el, cls;
25552
25553             // loop through each of them, and if they are defined add the ui
25554             for (i = 0; i < els.length; i++) {
25555                 el = me['frame' + els[i].toUpperCase()];
25556                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
25557                 if (el) {
25558                     el.addCls(cls);
25559                 } else {
25560                     if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
25561                         frameElementCls[els[i]].push(cls);
25562                     }
25563                 }
25564             }
25565         }
25566     },
25567
25568     /**
25569      * Method which removes a specified UI from the components element.
25570      * @private
25571      */
25572     removeUIFromElement: function() {
25573         var me = this,
25574             frameElementCls = me.frameElementCls;
25575
25576         me.removeCls(me.baseCls + '-' + me.ui);
25577
25578         if (me.frame && !Ext.supports.CSS3BorderRadius) {
25579             // define each element of the frame
25580             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25581                 i, j, el, cls;
25582
25583             // loop through each of them, and if they are defined add the ui
25584             for (i = 0; i < els.length; i++) {
25585                 el = me['frame' + els[i].toUpperCase()];
25586                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
25587
25588                 if (el) {
25589                     el.removeCls(cls);
25590                 } else {
25591                     Ext.Array.remove(frameElementCls[els[i]], cls);
25592                 }
25593             }
25594         }
25595     },
25596
25597     getElConfig : function() {
25598         if (Ext.isString(this.autoEl)) {
25599             this.autoEl = {
25600                 tag: this.autoEl
25601             };
25602         }
25603
25604         var result = this.autoEl || {tag: 'div'};
25605         result.id = this.id;
25606         return result;
25607     },
25608
25609     /**
25610      * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
25611      * insertBefore.
25612      * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
25613      * component before.
25614      * @return {HTMLElement} DOM element that you can use in the insertBefore
25615      */
25616     getInsertPosition: function(position) {
25617         // Convert the position to an element to insert before
25618         if (position !== undefined) {
25619             if (Ext.isNumber(position)) {
25620                 position = this.container.dom.childNodes[position];
25621             }
25622             else {
25623                 position = Ext.getDom(position);
25624             }
25625         }
25626
25627         return position;
25628     },
25629
25630     /**
25631      * Adds ctCls to container.
25632      * @return {Ext.Element} The initialized container
25633      * @private
25634      */
25635     initContainer: function(container) {
25636         var me = this;
25637
25638         // If you render a component specifying the el, we get the container
25639         // of the el, and make sure we dont move the el around in the dom
25640         // during the render
25641         if (!container && me.el) {
25642             container = me.el.dom.parentNode;
25643             me.allowDomMove = false;
25644         }
25645
25646         me.container = Ext.get(container);
25647
25648         if (me.ctCls) {
25649             me.container.addCls(me.ctCls);
25650         }
25651
25652         return me.container;
25653     },
25654
25655     /**
25656      * Initialized the renderData to be used when rendering the renderTpl.
25657      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
25658      * @private
25659      */
25660     initRenderData: function() {
25661         var me = this;
25662
25663         return Ext.applyIf(me.renderData, {
25664             id: me.id,
25665             ui: me.ui,
25666             uiCls: me.uiCls,
25667             baseCls: me.baseCls,
25668             componentCls: me.componentCls,
25669             frame: me.frame
25670         });
25671     },
25672
25673     /**
25674      * @private
25675      */
25676     getTpl: function(name) {
25677         var me = this,
25678             prototype = me.self.prototype,
25679             ownerPrototype,
25680             tpl;
25681
25682         if (me.hasOwnProperty(name)) {
25683             tpl = me[name];
25684             if (tpl && !(tpl instanceof Ext.XTemplate)) {
25685                 me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
25686             }
25687
25688             return me[name];
25689         }
25690
25691         if (!(prototype[name] instanceof Ext.XTemplate)) {
25692             ownerPrototype = prototype;
25693
25694             do {
25695                 if (ownerPrototype.hasOwnProperty(name)) {
25696                     tpl = ownerPrototype[name];
25697                     if (tpl && !(tpl instanceof Ext.XTemplate)) {
25698                         ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
25699                         break;
25700                     }
25701                 }
25702
25703                 ownerPrototype = ownerPrototype.superclass;
25704             } while (ownerPrototype);
25705         }
25706
25707         return prototype[name];
25708     },
25709
25710     /**
25711      * Initializes the renderTpl.
25712      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
25713      * @private
25714      */
25715     initRenderTpl: function() {
25716         return this.getTpl('renderTpl');
25717     },
25718
25719     /**
25720      * Converts style definitions to String.
25721      * @return {String} A CSS style string with style, padding, margin and border.
25722      * @private
25723      */
25724     initStyles: function() {
25725         var style = {},
25726             me = this,
25727             Element = Ext.Element;
25728
25729         if (Ext.isString(me.style)) {
25730             style = Element.parseStyles(me.style);
25731         } else {
25732             style = Ext.apply({}, me.style);
25733         }
25734
25735         // Convert the padding, margin and border properties from a space seperated string
25736         // into a proper style string
25737         if (me.padding !== undefined) {
25738             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
25739         }
25740
25741         if (me.margin !== undefined) {
25742             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
25743         }
25744
25745         delete me.style;
25746         return style;
25747     },
25748
25749     /**
25750      * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
25751      * @private
25752      */
25753     initContent: function() {
25754         var me = this,
25755             target = me.getTargetEl(),
25756             contentEl,
25757             pre;
25758
25759         if (me.html) {
25760             target.update(Ext.DomHelper.markup(me.html));
25761             delete me.html;
25762         }
25763
25764         if (me.contentEl) {
25765             contentEl = Ext.get(me.contentEl);
25766             pre = Ext.baseCSSPrefix;
25767             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
25768             target.appendChild(contentEl.dom);
25769         }
25770
25771         if (me.tpl) {
25772             // Make sure this.tpl is an instantiated XTemplate
25773             if (!me.tpl.isTemplate) {
25774                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
25775             }
25776
25777             if (me.data) {
25778                 me.tpl[me.tplWriteMode](target, me.data);
25779                 delete me.data;
25780             }
25781         }
25782     },
25783
25784     // @private
25785     initEvents : function() {
25786         var me = this,
25787             afterRenderEvents = me.afterRenderEvents,
25788             el,
25789             property,
25790             fn = function(listeners){
25791                 me.mon(el, listeners);
25792             };
25793         if (afterRenderEvents) {
25794             for (property in afterRenderEvents) {
25795                 if (afterRenderEvents.hasOwnProperty(property)) {
25796                     el = me[property];
25797                     if (el && el.on) {
25798                         Ext.each(afterRenderEvents[property], fn);
25799                     }
25800                 }
25801             }
25802         }
25803     },
25804
25805     /**
25806      * Adds each argument passed to this method to the {@link #childEls} array.
25807      */
25808     addChildEls: function () {
25809         var me = this,
25810             childEls = me.childEls || (me.childEls = []);
25811
25812         childEls.push.apply(childEls, arguments);
25813     },
25814
25815     /**
25816      * Removes items in the childEls array based on the return value of a supplied test function. The function is called
25817      * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
25818      * kept.
25819      * @param {Function} testFn The test function.
25820      */
25821     removeChildEls: function (testFn) {
25822         var me = this,
25823             old = me.childEls,
25824             keepers = (me.childEls = []),
25825             n, i, cel;
25826
25827         for (i = 0, n = old.length; i < n; ++i) {
25828             cel = old[i];
25829             if (!testFn(cel)) {
25830                 keepers.push(cel);
25831             }
25832         }
25833     },
25834
25835     /**
25836      * Sets references to elements inside the component. This applies {@link #renderSelectors}
25837      * as well as {@link #childEls}.
25838      * @private
25839      */
25840     applyRenderSelectors: function() {
25841         var me = this,
25842             childEls = me.childEls,
25843             selectors = me.renderSelectors,
25844             el = me.el,
25845             dom = el.dom,
25846             baseId, childName, childId, i, selector;
25847
25848         if (childEls) {
25849             baseId = me.id + '-';
25850             for (i = childEls.length; i--; ) {
25851                 childName = childId = childEls[i];
25852                 if (typeof(childName) != 'string') {
25853                     childId = childName.id || (baseId + childName.itemId);
25854                     childName = childName.name;
25855                 } else {
25856                     childId = baseId + childId;
25857                 }
25858
25859                 // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
25860                 // we know the el's are children of our el we use getById instead:
25861                 me[childName] = el.getById(childId);
25862             }
25863         }
25864
25865         // We still support renderSelectors. There are a few places in the framework that
25866         // need them and they are a documented part of the API. In fact, we support mixing
25867         // childEls and renderSelectors (no reason not to).
25868         if (selectors) {
25869             for (selector in selectors) {
25870                 if (selectors.hasOwnProperty(selector) && selectors[selector]) {
25871                     me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
25872                 }
25873             }
25874         }
25875     },
25876
25877     /**
25878      * Tests whether this Component matches the selector string.
25879      * @param {String} selector The selector string to test against.
25880      * @return {Boolean} True if this Component matches the selector.
25881      */
25882     is: function(selector) {
25883         return Ext.ComponentQuery.is(this, selector);
25884     },
25885
25886     /**
25887      * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
25888      *
25889      * Example:
25890      *
25891      *     var owningTabPanel = grid.up('tabpanel');
25892      *
25893      * @param {String} [selector] The simple selector to test.
25894      * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
25895      */
25896     up: function(selector) {
25897         var result = this.ownerCt;
25898         if (selector) {
25899             for (; result; result = result.ownerCt) {
25900                 if (Ext.ComponentQuery.is(result, selector)) {
25901                     return result;
25902                 }
25903             }
25904         }
25905         return result;
25906     },
25907
25908     /**
25909      * Returns the next sibling of this Component.
25910      *
25911      * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
25912      *
25913      * May also be refered to as **`next()`**
25914      *
25915      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
25916      * {@link #nextNode}
25917      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
25918      * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
25919      * Returns null if there is no matching sibling.
25920      */
25921     nextSibling: function(selector) {
25922         var o = this.ownerCt, it, last, idx, c;
25923         if (o) {
25924             it = o.items;
25925             idx = it.indexOf(this) + 1;
25926             if (idx) {
25927                 if (selector) {
25928                     for (last = it.getCount(); idx < last; idx++) {
25929                         if ((c = it.getAt(idx)).is(selector)) {
25930                             return c;
25931                         }
25932                     }
25933                 } else {
25934                     if (idx < it.getCount()) {
25935                         return it.getAt(idx);
25936                     }
25937                 }
25938             }
25939         }
25940         return null;
25941     },
25942
25943     /**
25944      * Returns the previous sibling of this Component.
25945      *
25946      * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
25947      * selector.
25948      *
25949      * May also be refered to as **`prev()`**
25950      *
25951      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
25952      * {@link #previousNode}
25953      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
25954      * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
25955      * Returns null if there is no matching sibling.
25956      */
25957     previousSibling: function(selector) {
25958         var o = this.ownerCt, it, idx, c;
25959         if (o) {
25960             it = o.items;
25961             idx = it.indexOf(this);
25962             if (idx != -1) {
25963                 if (selector) {
25964                     for (--idx; idx >= 0; idx--) {
25965                         if ((c = it.getAt(idx)).is(selector)) {
25966                             return c;
25967                         }
25968                     }
25969                 } else {
25970                     if (idx) {
25971                         return it.getAt(--idx);
25972                     }
25973                 }
25974             }
25975         }
25976         return null;
25977     },
25978
25979     /**
25980      * Returns the previous node in the Component tree in tree traversal order.
25981      *
25982      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
25983      * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
25984      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
25985      * @return {Ext.Component} The previous node (or the previous node which matches the selector).
25986      * Returns null if there is no matching node.
25987      */
25988     previousNode: function(selector, includeSelf) {
25989         var node = this,
25990             result,
25991             it, len, i;
25992
25993         // If asked to include self, test me
25994         if (includeSelf && node.is(selector)) {
25995             return node;
25996         }
25997
25998         result = this.prev(selector);
25999         if (result) {
26000             return result;
26001         }
26002
26003         if (node.ownerCt) {
26004             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
26005                 if (it[i].query) {
26006                     result = it[i].query(selector);
26007                     result = result[result.length - 1];
26008                     if (result) {
26009                         return result;
26010                     }
26011                 }
26012             }
26013             return node.ownerCt.previousNode(selector, true);
26014         }
26015     },
26016
26017     /**
26018      * Returns the next node in the Component tree in tree traversal order.
26019      *
26020      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26021      * tree to attempt to find a match. Contrast with {@link #nextSibling}.
26022      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
26023      * @return {Ext.Component} The next node (or the next node which matches the selector).
26024      * Returns null if there is no matching node.
26025      */
26026     nextNode: function(selector, includeSelf) {
26027         var node = this,
26028             result,
26029             it, len, i;
26030
26031         // If asked to include self, test me
26032         if (includeSelf && node.is(selector)) {
26033             return node;
26034         }
26035
26036         result = this.next(selector);
26037         if (result) {
26038             return result;
26039         }
26040
26041         if (node.ownerCt) {
26042             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
26043                 if (it[i].down) {
26044                     result = it[i].down(selector);
26045                     if (result) {
26046                         return result;
26047                     }
26048                 }
26049             }
26050             return node.ownerCt.nextNode(selector);
26051         }
26052     },
26053
26054     /**
26055      * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
26056      * @return {String}
26057      */
26058     getId : function() {
26059         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
26060     },
26061
26062     getItemId : function() {
26063         return this.itemId || this.id;
26064     },
26065
26066     /**
26067      * Retrieves the top level element representing this component.
26068      * @return {Ext.core.Element}
26069      */
26070     getEl : function() {
26071         return this.el;
26072     },
26073
26074     /**
26075      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
26076      * @private
26077      */
26078     getTargetEl: function() {
26079         return this.frameBody || this.el;
26080     },
26081
26082     /**
26083      * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
26084      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
26085      *
26086      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26087      * determination of inherited xtypes.**
26088      *
26089      * For a list of all available xtypes, see the {@link Ext.Component} header.
26090      *
26091      * Example usage:
26092      *
26093      *     var t = new Ext.form.field.Text();
26094      *     var isText = t.isXType('textfield');        // true
26095      *     var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
26096      *     var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
26097      *
26098      * @param {String} xtype The xtype to check for this Component
26099      * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
26100      * check whether this Component is descended from the xtype.
26101      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
26102      */
26103     isXType: function(xtype, shallow) {
26104         //assume a string by default
26105         if (Ext.isFunction(xtype)) {
26106             xtype = xtype.xtype;
26107             //handle being passed the class, e.g. Ext.Component
26108         } else if (Ext.isObject(xtype)) {
26109             xtype = xtype.statics().xtype;
26110             //handle being passed an instance
26111         }
26112
26113         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
26114     },
26115
26116     /**
26117      * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
26118      * {@link Ext.Component} header.
26119      *
26120      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26121      * determination of inherited xtypes.**
26122      *
26123      * Example usage:
26124      *
26125      *     var t = new Ext.form.field.Text();
26126      *     alert(t.getXTypes());  // alerts 'component/field/textfield'
26127      *
26128      * @return {String} The xtype hierarchy string
26129      */
26130     getXTypes: function() {
26131         var self = this.self,
26132             xtypes, parentPrototype, parentXtypes;
26133
26134         if (!self.xtypes) {
26135             xtypes = [];
26136             parentPrototype = this;
26137
26138             while (parentPrototype) {
26139                 parentXtypes = parentPrototype.xtypes;
26140
26141                 if (parentXtypes !== undefined) {
26142                     xtypes.unshift.apply(xtypes, parentXtypes);
26143                 }
26144
26145                 parentPrototype = parentPrototype.superclass;
26146             }
26147
26148             self.xtypeChain = xtypes;
26149             self.xtypes = xtypes.join('/');
26150         }
26151
26152         return self.xtypes;
26153     },
26154
26155     /**
26156      * Update the content area of a component.
26157      * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
26158      * it will use this argument as data to populate the template. If this component was not configured with a template,
26159      * the components content area will be updated via Ext.Element update
26160      * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
26161      * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
26162      * scripts have finished loading
26163      */
26164     update : function(htmlOrData, loadScripts, cb) {
26165         var me = this;
26166
26167         if (me.tpl && !Ext.isString(htmlOrData)) {
26168             me.data = htmlOrData;
26169             if (me.rendered) {
26170                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
26171             }
26172         } else {
26173             me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
26174             if (me.rendered) {
26175                 me.getTargetEl().update(me.html, loadScripts, cb);
26176             }
26177         }
26178
26179         if (me.rendered) {
26180             me.doComponentLayout();
26181         }
26182     },
26183
26184     /**
26185      * Convenience function to hide or show this component by boolean.
26186      * @param {Boolean} visible True to show, false to hide
26187      * @return {Ext.Component} this
26188      */
26189     setVisible : function(visible) {
26190         return this[visible ? 'show': 'hide']();
26191     },
26192
26193     /**
26194      * Returns true if this component is visible.
26195      *
26196      * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
26197      * determine whether this Component is truly visible to the user.
26198      *
26199      * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
26200      * dynamically laid out UIs in a hidden Container before showing them.
26201      *
26202      * @return {Boolean} True if this component is visible, false otherwise.
26203      */
26204     isVisible: function(deep) {
26205         var me = this,
26206             child = me,
26207             visible = !me.hidden,
26208             ancestor = me.ownerCt;
26209
26210         // Clear hiddenOwnerCt property
26211         me.hiddenAncestor = false;
26212         if (me.destroyed) {
26213             return false;
26214         }
26215
26216         if (deep && visible && me.rendered && ancestor) {
26217             while (ancestor) {
26218                 // If any ancestor is hidden, then this is hidden.
26219                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
26220                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
26221                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
26222                 if (ancestor.hidden || (ancestor.collapsed &&
26223                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
26224                     // Store hiddenOwnerCt property if needed
26225                     me.hiddenAncestor = ancestor;
26226                     visible = false;
26227                     break;
26228                 }
26229                 child = ancestor;
26230                 ancestor = ancestor.ownerCt;
26231             }
26232         }
26233         return visible;
26234     },
26235
26236     /**
26237      * Enable the component
26238      * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
26239      */
26240     enable: function(silent) {
26241         var me = this;
26242
26243         if (me.rendered) {
26244             me.el.removeCls(me.disabledCls);
26245             me.el.dom.disabled = false;
26246             me.onEnable();
26247         }
26248
26249         me.disabled = false;
26250
26251         if (silent !== true) {
26252             me.fireEvent('enable', me);
26253         }
26254
26255         return me;
26256     },
26257
26258     /**
26259      * Disable the component.
26260      * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
26261      */
26262     disable: function(silent) {
26263         var me = this;
26264
26265         if (me.rendered) {
26266             me.el.addCls(me.disabledCls);
26267             me.el.dom.disabled = true;
26268             me.onDisable();
26269         }
26270
26271         me.disabled = true;
26272
26273         if (silent !== true) {
26274             me.fireEvent('disable', me);
26275         }
26276
26277         return me;
26278     },
26279
26280     // @private
26281     onEnable: function() {
26282         if (this.maskOnDisable) {
26283             this.el.unmask();
26284         }
26285     },
26286
26287     // @private
26288     onDisable : function() {
26289         if (this.maskOnDisable) {
26290             this.el.mask();
26291         }
26292     },
26293
26294     /**
26295      * Method to determine whether this Component is currently disabled.
26296      * @return {Boolean} the disabled state of this Component.
26297      */
26298     isDisabled : function() {
26299         return this.disabled;
26300     },
26301
26302     /**
26303      * Enable or disable the component.
26304      * @param {Boolean} disabled True to disable.
26305      */
26306     setDisabled : function(disabled) {
26307         return this[disabled ? 'disable': 'enable']();
26308     },
26309
26310     /**
26311      * Method to determine whether this Component is currently set to hidden.
26312      * @return {Boolean} the hidden state of this Component.
26313      */
26314     isHidden : function() {
26315         return this.hidden;
26316     },
26317
26318     /**
26319      * Adds a CSS class to the top level element representing this component.
26320      * @param {String} cls The CSS class name to add
26321      * @return {Ext.Component} Returns the Component to allow method chaining.
26322      */
26323     addCls : function(className) {
26324         var me = this;
26325         if (!className) {
26326             return me;
26327         }
26328         if (!Ext.isArray(className)){
26329             className = className.replace(me.trimRe, '').split(me.spacesRe);
26330         }
26331         if (me.rendered) {
26332             me.el.addCls(className);
26333         }
26334         else {
26335             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
26336         }
26337         return me;
26338     },
26339
26340     /**
26341      * Adds a CSS class to the top level element representing this component.
26342      * @param {String} cls The CSS class name to add
26343      * @return {Ext.Component} Returns the Component to allow method chaining.
26344      */
26345     addClass : function() {
26346         return this.addCls.apply(this, arguments);
26347     },
26348
26349     /**
26350      * Removes a CSS class from the top level element representing this component.
26351      * @param {Object} className
26352      * @return {Ext.Component} Returns the Component to allow method chaining.
26353      */
26354     removeCls : function(className) {
26355         var me = this;
26356
26357         if (!className) {
26358             return me;
26359         }
26360         if (!Ext.isArray(className)){
26361             className = className.replace(me.trimRe, '').split(me.spacesRe);
26362         }
26363         if (me.rendered) {
26364             me.el.removeCls(className);
26365         }
26366         else if (me.additionalCls.length) {
26367             Ext.each(className, function(cls) {
26368                 Ext.Array.remove(me.additionalCls, cls);
26369             });
26370         }
26371         return me;
26372     },
26373
26374
26375     addOverCls: function() {
26376         var me = this;
26377         if (!me.disabled) {
26378             me.el.addCls(me.overCls);
26379         }
26380     },
26381
26382     removeOverCls: function() {
26383         this.el.removeCls(this.overCls);
26384     },
26385
26386     addListener : function(element, listeners, scope, options) {
26387         var me = this,
26388             fn,
26389             option;
26390
26391         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
26392             if (options.element) {
26393                 fn = listeners;
26394
26395                 listeners = {};
26396                 listeners[element] = fn;
26397                 element = options.element;
26398                 if (scope) {
26399                     listeners.scope = scope;
26400                 }
26401
26402                 for (option in options) {
26403                     if (options.hasOwnProperty(option)) {
26404                         if (me.eventOptionsRe.test(option)) {
26405                             listeners[option] = options[option];
26406                         }
26407                     }
26408                 }
26409             }
26410
26411             // At this point we have a variable called element,
26412             // and a listeners object that can be passed to on
26413             if (me[element] && me[element].on) {
26414                 me.mon(me[element], listeners);
26415             } else {
26416                 me.afterRenderEvents = me.afterRenderEvents || {};
26417                 if (!me.afterRenderEvents[element]) {
26418                     me.afterRenderEvents[element] = [];
26419                 }
26420                 me.afterRenderEvents[element].push(listeners);
26421             }
26422         }
26423
26424         return me.mixins.observable.addListener.apply(me, arguments);
26425     },
26426
26427     // inherit docs
26428     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
26429         var me = this,
26430             element = managedListener.options ? managedListener.options.element : null;
26431
26432         if (element) {
26433             element = me[element];
26434             if (element && element.un) {
26435                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
26436                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
26437                     if (!isClear) {
26438                         Ext.Array.remove(me.managedListeners, managedListener);
26439                     }
26440                 }
26441             }
26442         } else {
26443             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
26444         }
26445     },
26446
26447     /**
26448      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
26449      * @return {Ext.container.Container} the Container which owns this Component.
26450      */
26451     getBubbleTarget : function() {
26452         return this.ownerCt;
26453     },
26454
26455     /**
26456      * Method to determine whether this Component is floating.
26457      * @return {Boolean} the floating state of this component.
26458      */
26459     isFloating : function() {
26460         return this.floating;
26461     },
26462
26463     /**
26464      * Method to determine whether this Component is draggable.
26465      * @return {Boolean} the draggable state of this component.
26466      */
26467     isDraggable : function() {
26468         return !!this.draggable;
26469     },
26470
26471     /**
26472      * Method to determine whether this Component is droppable.
26473      * @return {Boolean} the droppable state of this component.
26474      */
26475     isDroppable : function() {
26476         return !!this.droppable;
26477     },
26478
26479     /**
26480      * @private
26481      * Method to manage awareness of when components are added to their
26482      * respective Container, firing an added event.
26483      * References are established at add time rather than at render time.
26484      * @param {Ext.container.Container} container Container which holds the component
26485      * @param {Number} pos Position at which the component was added
26486      */
26487     onAdded : function(container, pos) {
26488         this.ownerCt = container;
26489         this.fireEvent('added', this, container, pos);
26490     },
26491
26492     /**
26493      * @private
26494      * Method to manage awareness of when components are removed from their
26495      * respective Container, firing an removed event. References are properly
26496      * cleaned up after removing a component from its owning container.
26497      */
26498     onRemoved : function() {
26499         var me = this;
26500
26501         me.fireEvent('removed', me, me.ownerCt);
26502         delete me.ownerCt;
26503     },
26504
26505     // @private
26506     beforeDestroy : Ext.emptyFn,
26507     // @private
26508     // @private
26509     onResize : Ext.emptyFn,
26510
26511     /**
26512      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
26513      * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
26514      *
26515      * @param {Number/String/Object} width The new width to set. This may be one of:
26516      *
26517      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26518      *   - A String used to set the CSS width style.
26519      *   - A size object in the format `{width: widthValue, height: heightValue}`.
26520      *   - `undefined` to leave the width unchanged.
26521      *
26522      * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
26523      * This may be one of:
26524      *
26525      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26526      *   - A String used to set the CSS height style. Animation may **not** be used.
26527      *   - `undefined` to leave the height unchanged.
26528      *
26529      * @return {Ext.Component} this
26530      */
26531     setSize : function(width, height) {
26532         var me = this,
26533             layoutCollection;
26534
26535         // support for standard size objects
26536         if (Ext.isObject(width)) {
26537             height = width.height;
26538             width  = width.width;
26539         }
26540
26541         // Constrain within configured maxima
26542         if (Ext.isNumber(width)) {
26543             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
26544         }
26545         if (Ext.isNumber(height)) {
26546             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
26547         }
26548
26549         if (!me.rendered || !me.isVisible()) {
26550             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
26551             if (me.hiddenAncestor) {
26552                 layoutCollection = me.hiddenAncestor.layoutOnShow;
26553                 layoutCollection.remove(me);
26554                 layoutCollection.add(me);
26555             }
26556             me.needsLayout = {
26557                 width: width,
26558                 height: height,
26559                 isSetSize: true
26560             };
26561             if (!me.rendered) {
26562                 me.width  = (width !== undefined) ? width : me.width;
26563                 me.height = (height !== undefined) ? height : me.height;
26564             }
26565             return me;
26566         }
26567         me.doComponentLayout(width, height, true);
26568
26569         return me;
26570     },
26571
26572     isFixedWidth: function() {
26573         var me = this,
26574             layoutManagedWidth = me.layoutManagedWidth;
26575
26576         if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
26577             return true;
26578         }
26579         if (layoutManagedWidth == 2) {
26580             return false;
26581         }
26582         return (me.ownerCt && me.ownerCt.isFixedWidth());
26583     },
26584
26585     isFixedHeight: function() {
26586         var me = this,
26587             layoutManagedHeight = me.layoutManagedHeight;
26588
26589         if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
26590             return true;
26591         }
26592         if (layoutManagedHeight == 2) {
26593             return false;
26594         }
26595         return (me.ownerCt && me.ownerCt.isFixedHeight());
26596     },
26597
26598     setCalculatedSize : function(width, height, callingContainer) {
26599         var me = this,
26600             layoutCollection;
26601
26602         // support for standard size objects
26603         if (Ext.isObject(width)) {
26604             callingContainer = width.ownerCt;
26605             height = width.height;
26606             width  = width.width;
26607         }
26608
26609         // Constrain within configured maxima
26610         if (Ext.isNumber(width)) {
26611             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
26612         }
26613         if (Ext.isNumber(height)) {
26614             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
26615         }
26616
26617         if (!me.rendered || !me.isVisible()) {
26618             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
26619             if (me.hiddenAncestor) {
26620                 layoutCollection = me.hiddenAncestor.layoutOnShow;
26621                 layoutCollection.remove(me);
26622                 layoutCollection.add(me);
26623             }
26624             me.needsLayout = {
26625                 width: width,
26626                 height: height,
26627                 isSetSize: false,
26628                 ownerCt: callingContainer
26629             };
26630             return me;
26631         }
26632         me.doComponentLayout(width, height, false, callingContainer);
26633
26634         return me;
26635     },
26636
26637     /**
26638      * This method needs to be called whenever you change something on this component that requires the Component's
26639      * layout to be recalculated.
26640      * @param {Object} width
26641      * @param {Object} height
26642      * @param {Object} isSetSize
26643      * @param {Object} callingContainer
26644      * @return {Ext.container.Container} this
26645      */
26646     doComponentLayout : function(width, height, isSetSize, callingContainer) {
26647         var me = this,
26648             componentLayout = me.getComponentLayout(),
26649             lastComponentSize = componentLayout.lastComponentSize || {
26650                 width: undefined,
26651                 height: undefined
26652             };
26653
26654         // collapsed state is not relevant here, so no testing done.
26655         // Only Panels have a collapse method, and that just sets the width/height such that only
26656         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
26657         if (me.rendered && componentLayout) {
26658             // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
26659             if (!Ext.isDefined(width)) {
26660                 if (me.isFixedWidth()) {
26661                     width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
26662                 }
26663             }
26664             // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
26665             if (!Ext.isDefined(height)) {
26666                 if (me.isFixedHeight()) {
26667                     height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
26668                 }
26669             }
26670
26671             if (isSetSize) {
26672                 me.width = width;
26673                 me.height = height;
26674             }
26675
26676             componentLayout.layout(width, height, isSetSize, callingContainer);
26677         }
26678
26679         return me;
26680     },
26681
26682     /**
26683      * Forces this component to redo its componentLayout.
26684      */
26685     forceComponentLayout: function () {
26686         this.doComponentLayout();
26687     },
26688
26689     // @private
26690     setComponentLayout : function(layout) {
26691         var currentLayout = this.componentLayout;
26692         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
26693             currentLayout.setOwner(null);
26694         }
26695         this.componentLayout = layout;
26696         layout.setOwner(this);
26697     },
26698
26699     getComponentLayout : function() {
26700         var me = this;
26701
26702         if (!me.componentLayout || !me.componentLayout.isLayout) {
26703             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
26704         }
26705         return me.componentLayout;
26706     },
26707
26708     /**
26709      * Occurs after componentLayout is run.
26710      * @param {Number} adjWidth The box-adjusted width that was set
26711      * @param {Number} adjHeight The box-adjusted height that was set
26712      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
26713      * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
26714      */
26715     afterComponentLayout: function(width, height, isSetSize, callingContainer) {
26716         var me = this,
26717             layout = me.componentLayout,
26718             oldSize = me.preLayoutSize;
26719
26720         ++me.componentLayoutCounter;
26721         if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
26722             me.fireEvent('resize', me, width, height);
26723         }
26724     },
26725
26726     /**
26727      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
26728      * being executed.
26729      * @param {Number} adjWidth The box-adjusted width that was set
26730      * @param {Number} adjHeight The box-adjusted height that was set
26731      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
26732      * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
26733      */
26734     beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
26735         this.preLayoutSize = this.componentLayout.lastComponentSize;
26736         return true;
26737     },
26738
26739     /**
26740      * Sets the left and top of the component. To set the page XY position instead, use
26741      * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
26742      * @param {Number} left The new left
26743      * @param {Number} top The new top
26744      * @return {Ext.Component} this
26745      */
26746     setPosition : function(x, y) {
26747         var me = this;
26748
26749         if (Ext.isObject(x)) {
26750             y = x.y;
26751             x = x.x;
26752         }
26753
26754         if (!me.rendered) {
26755             return me;
26756         }
26757
26758         if (x !== undefined || y !== undefined) {
26759             me.el.setBox(x, y);
26760             me.onPosition(x, y);
26761             me.fireEvent('move', me, x, y);
26762         }
26763         return me;
26764     },
26765
26766     /**
26767      * @private
26768      * Called after the component is moved, this method is empty by default but can be implemented by any
26769      * subclass that needs to perform custom logic after a move occurs.
26770      * @param {Number} x The new x position
26771      * @param {Number} y The new y position
26772      */
26773     onPosition: Ext.emptyFn,
26774
26775     /**
26776      * Sets the width of the component. This method fires the {@link #resize} event.
26777      *
26778      * @param {Number} width The new width to setThis may be one of:
26779      *
26780      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26781      *   - A String used to set the CSS width style.
26782      *
26783      * @return {Ext.Component} this
26784      */
26785     setWidth : function(width) {
26786         return this.setSize(width);
26787     },
26788
26789     /**
26790      * Sets the height of the component. This method fires the {@link #resize} event.
26791      *
26792      * @param {Number} height The new height to set. This may be one of:
26793      *
26794      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26795      *   - A String used to set the CSS height style.
26796      *   - _undefined_ to leave the height unchanged.
26797      *
26798      * @return {Ext.Component} this
26799      */
26800     setHeight : function(height) {
26801         return this.setSize(undefined, height);
26802     },
26803
26804     /**
26805      * Gets the current size of the component's underlying element.
26806      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
26807      */
26808     getSize : function() {
26809         return this.el.getSize();
26810     },
26811
26812     /**
26813      * Gets the current width of the component's underlying element.
26814      * @return {Number}
26815      */
26816     getWidth : function() {
26817         return this.el.getWidth();
26818     },
26819
26820     /**
26821      * Gets the current height of the component's underlying element.
26822      * @return {Number}
26823      */
26824     getHeight : function() {
26825         return this.el.getHeight();
26826     },
26827
26828     /**
26829      * Gets the {@link Ext.ComponentLoader} for this Component.
26830      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
26831      */
26832     getLoader: function(){
26833         var me = this,
26834             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
26835             loader = me.loader || autoLoad;
26836
26837         if (loader) {
26838             if (!loader.isLoader) {
26839                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
26840                     target: me,
26841                     autoLoad: autoLoad
26842                 }, loader));
26843             } else {
26844                 loader.setTarget(me);
26845             }
26846             return me.loader;
26847
26848         }
26849         return null;
26850     },
26851
26852     /**
26853      * This method allows you to show or hide a LoadMask on top of this component.
26854      *
26855      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
26856      * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
26857      * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
26858      * setting this to true on a Panel will cause only the body to be masked.
26859      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
26860      */
26861     setLoading : function(load, targetEl) {
26862         var me = this,
26863             config;
26864
26865         if (me.rendered) {
26866             if (load !== false && !me.collapsed) {
26867                 if (Ext.isObject(load)) {
26868                     config = load;
26869                 }
26870                 else if (Ext.isString(load)) {
26871                     config = {msg: load};
26872                 }
26873                 else {
26874                     config = {};
26875                 }
26876                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
26877                 me.loadMask.show();
26878             } else if (me.loadMask) {
26879                 Ext.destroy(me.loadMask);
26880                 me.loadMask = null;
26881             }
26882         }
26883
26884         return me.loadMask;
26885     },
26886
26887     /**
26888      * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
26889      * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
26890      * @param {Object} dock The dock position.
26891      * @param {Boolean} [layoutParent=false] True to re-layout parent.
26892      * @return {Ext.Component} this
26893      */
26894     setDocked : function(dock, layoutParent) {
26895         var me = this;
26896
26897         me.dock = dock;
26898         if (layoutParent && me.ownerCt && me.rendered) {
26899             me.ownerCt.doComponentLayout();
26900         }
26901         return me;
26902     },
26903
26904     onDestroy : function() {
26905         var me = this;
26906
26907         if (me.monitorResize && Ext.EventManager.resizeEvent) {
26908             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
26909         }
26910         // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
26911         Ext.destroy(
26912             me.componentLayout,
26913             me.loadMask,
26914             me.floatingItems
26915         );
26916     },
26917
26918     /**
26919      * Remove any references to elements added via renderSelectors/childEls
26920      * @private
26921      */
26922     cleanElementRefs: function(){
26923         var me = this,
26924             i = 0,
26925             childEls = me.childEls,
26926             selectors = me.renderSelectors,
26927             selector,
26928             name,
26929             len;
26930
26931         if (me.rendered) {
26932             if (childEls) {
26933                 for (len = childEls.length; i < len; ++i) {
26934                     name = childEls[i];
26935                     if (typeof(name) != 'string') {
26936                         name = name.name;
26937                     }
26938                     delete me[name];
26939                 }
26940             }
26941
26942             if (selectors) {
26943                 for (selector in selectors) {
26944                     if (selectors.hasOwnProperty(selector)) {
26945                         delete me[selector];
26946                     }
26947                 }
26948             }
26949         }
26950         delete me.rendered;
26951         delete me.el;
26952         delete me.frameBody;
26953     },
26954
26955     /**
26956      * Destroys the Component.
26957      */
26958     destroy : function() {
26959         var me = this;
26960
26961         if (!me.isDestroyed) {
26962             if (me.fireEvent('beforedestroy', me) !== false) {
26963                 me.destroying = true;
26964                 me.beforeDestroy();
26965
26966                 if (me.floating) {
26967                     delete me.floatParent;
26968                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
26969                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
26970                     if (me.zIndexManager) {
26971                         me.zIndexManager.unregister(me);
26972                     }
26973                 } else if (me.ownerCt && me.ownerCt.remove) {
26974                     me.ownerCt.remove(me, false);
26975                 }
26976
26977                 me.onDestroy();
26978
26979                 // Attempt to destroy all plugins
26980                 Ext.destroy(me.plugins);
26981
26982                 if (me.rendered) {
26983                     me.el.remove();
26984                 }
26985
26986                 me.fireEvent('destroy', me);
26987                 Ext.ComponentManager.unregister(me);
26988
26989                 me.mixins.state.destroy.call(me);
26990
26991                 me.clearListeners();
26992                 // make sure we clean up the element references after removing all events
26993                 me.cleanElementRefs();
26994                 me.destroying = false;
26995                 me.isDestroyed = true;
26996             }
26997         }
26998     },
26999
27000     /**
27001      * Retrieves a plugin by its pluginId which has been bound to this component.
27002      * @param {Object} pluginId
27003      * @return {Ext.AbstractPlugin} plugin instance.
27004      */
27005     getPlugin: function(pluginId) {
27006         var i = 0,
27007             plugins = this.plugins,
27008             ln = plugins.length;
27009         for (; i < ln; i++) {
27010             if (plugins[i].pluginId === pluginId) {
27011                 return plugins[i];
27012             }
27013         }
27014     },
27015
27016     /**
27017      * Determines whether this component is the descendant of a particular container.
27018      * @param {Ext.Container} container
27019      * @return {Boolean} True if it is.
27020      */
27021     isDescendantOf: function(container) {
27022         return !!this.findParentBy(function(p){
27023             return p === container;
27024         });
27025     }
27026 }, function() {
27027     this.createAlias({
27028         on: 'addListener',
27029         prev: 'previousSibling',
27030         next: 'nextSibling'
27031     });
27032 });
27033
27034 /**
27035  * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
27036  *
27037  * This class defines the essential API of plugins as used by Components by defining the following methods:
27038  *
27039  *   - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
27040  *
27041  *     The Component passes itself as the sole parameter.
27042  *
27043  *     Subclasses should set up bidirectional links between the plugin and its client Component here.
27044  *
27045  *   - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
27046  *
27047  *     Use this method to break links between the plugin and the Component and to free any allocated resources.
27048  *
27049  *   - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
27050  *
27051  *   - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
27052  */
27053 Ext.define('Ext.AbstractPlugin', {
27054     disabled: false,
27055
27056     constructor: function(config) {
27057         Ext.apply(this, config);
27058     },
27059
27060     getCmp: function() {
27061         return this.cmp;
27062     },
27063
27064     /**
27065      * @method
27066      * The init method is invoked after initComponent method has been run for the client Component.
27067      *
27068      * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
27069      * links between the plugin and its client Component in their own implementation of this method.
27070      * @param {Ext.Component} client The client Component which owns this plugin.
27071      */
27072     init: Ext.emptyFn,
27073
27074     /**
27075      * @method
27076      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
27077      *
27078      * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
27079      * this method.
27080      */
27081     destroy: Ext.emptyFn,
27082
27083     /**
27084      * The base implementation just sets the plugin's `disabled` flag to `false`
27085      *
27086      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27087      */
27088     enable: function() {
27089         this.disabled = false;
27090     },
27091
27092     /**
27093      * The base implementation just sets the plugin's `disabled` flag to `true`
27094      *
27095      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27096      */
27097     disable: function() {
27098         this.disabled = true;
27099     }
27100 });
27101 /**
27102  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
27103  * to a configured URL, or to a URL specified at request time.
27104  *
27105  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
27106  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
27107  * in the request options object, or an {@link #requestcomplete event listener}.
27108  *
27109  * # File Uploads
27110  *
27111  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27112  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
27113  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
27114  * after the return data has been gathered.
27115  *
27116  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
27117  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
27118  * insert the text unchanged into the document body.
27119  *
27120  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
27121  * `&amp;` etc.
27122  *
27123  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
27124  * responseText property in order to conform to the requirements of event handlers and callbacks.
27125  *
27126  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
27127  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
27128  * packet content.
27129  *
27130  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
27131  */
27132 Ext.define('Ext.data.Connection', {
27133     mixins: {
27134         observable: 'Ext.util.Observable'
27135     },
27136
27137     statics: {
27138         requestId: 0
27139     },
27140
27141     url: null,
27142     async: true,
27143     method: null,
27144     username: '',
27145     password: '',
27146
27147     /**
27148      * @cfg {Boolean} disableCaching
27149      * True to add a unique cache-buster param to GET requests.
27150      */
27151     disableCaching: true,
27152
27153     /**
27154      * @cfg {Boolean} withCredentials
27155      * True to set `withCredentials = true` on the XHR object
27156      */
27157     withCredentials: false,
27158
27159     /**
27160      * @cfg {Boolean} cors
27161      * True to enable CORS support on the XHR object. Currently the only effect of this option
27162      * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
27163      */
27164     cors: false,
27165
27166     /**
27167      * @cfg {String} disableCachingParam
27168      * Change the parameter which is sent went disabling caching through a cache buster.
27169      */
27170     disableCachingParam: '_dc',
27171
27172     /**
27173      * @cfg {Number} timeout
27174      * The timeout in milliseconds to be used for requests.
27175      */
27176     timeout : 30000,
27177
27178     /**
27179      * @cfg {Object} extraParams
27180      * Any parameters to be appended to the request.
27181      */
27182
27183     useDefaultHeader : true,
27184     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
27185     useDefaultXhrHeader : true,
27186     defaultXhrHeader : 'XMLHttpRequest',
27187
27188     constructor : function(config) {
27189         config = config || {};
27190         Ext.apply(this, config);
27191
27192         this.addEvents(
27193             /**
27194              * @event beforerequest
27195              * Fires before a network request is made to retrieve a data object.
27196              * @param {Ext.data.Connection} conn This Connection object.
27197              * @param {Object} options The options config object passed to the {@link #request} method.
27198              */
27199             'beforerequest',
27200             /**
27201              * @event requestcomplete
27202              * Fires if the request was successfully completed.
27203              * @param {Ext.data.Connection} conn This Connection object.
27204              * @param {Object} response The XHR object containing the response data.
27205              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27206              * @param {Object} options The options config object passed to the {@link #request} method.
27207              */
27208             'requestcomplete',
27209             /**
27210              * @event requestexception
27211              * Fires if an error HTTP status was returned from the server.
27212              * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
27213              * for details of HTTP status codes.
27214              * @param {Ext.data.Connection} conn This Connection object.
27215              * @param {Object} response The XHR object containing the response data.
27216              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27217              * @param {Object} options The options config object passed to the {@link #request} method.
27218              */
27219             'requestexception'
27220         );
27221         this.requests = {};
27222         this.mixins.observable.constructor.call(this);
27223     },
27224
27225     /**
27226      * Sends an HTTP request to a remote server.
27227      *
27228      * **Important:** Ajax server requests are asynchronous, and this call will
27229      * return before the response has been received. Process any returned data
27230      * in a callback function.
27231      *
27232      *     Ext.Ajax.request({
27233      *         url: 'ajax_demo/sample.json',
27234      *         success: function(response, opts) {
27235      *             var obj = Ext.decode(response.responseText);
27236      *             console.dir(obj);
27237      *         },
27238      *         failure: function(response, opts) {
27239      *             console.log('server-side failure with status code ' + response.status);
27240      *         }
27241      *     });
27242      *
27243      * To execute a callback function in the correct scope, use the `scope` option.
27244      *
27245      * @param {Object} options An object which may contain the following properties:
27246      *
27247      * (The options object may also contain any other property which might be needed to perform
27248      * postprocessing in a callback because it is passed to callback functions.)
27249      *
27250      * @param {String/Function} options.url The URL to which to send the request, or a function
27251      * to call which returns a URL string. The scope of the function is specified by the `scope` option.
27252      * Defaults to the configured `url`.
27253      *
27254      * @param {Object/String/Function} options.params An object containing properties which are
27255      * used as parameters to the request, a url encoded string or a function to call to get either. The scope
27256      * of the function is specified by the `scope` option.
27257      *
27258      * @param {String} options.method The HTTP method to use
27259      * for the request. Defaults to the configured method, or if no method was configured,
27260      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
27261      * the method name is case-sensitive and should be all caps.
27262      *
27263      * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
27264      * The callback is called regardless of success or failure and is passed the following parameters:
27265      * @param {Object} options.callback.options The parameter to the request call.
27266      * @param {Boolean} options.callback.success True if the request succeeded.
27267      * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
27268      * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
27269      * accessing elements of the response.
27270      *
27271      * @param {Function} options.success The function to be called upon success of the request.
27272      * The callback is passed the following parameters:
27273      * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
27274      * @param {Object} options.success.options The parameter to the request call.
27275      *
27276      * @param {Function} options.failure The function to be called upon success of the request.
27277      * The callback is passed the following parameters:
27278      * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
27279      * @param {Object} options.failure.options The parameter to the request call.
27280      *
27281      * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
27282      * the callback function. If the `url`, or `params` options were specified as functions from which to
27283      * draw values, then this also serves as the scope for those function calls. Defaults to the browser
27284      * window.
27285      *
27286      * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
27287      * Defaults to 30 seconds.
27288      *
27289      * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
27290      * to pull parameters from.
27291      *
27292      * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
27293      *
27294      * True if the form object is a file upload (will be set automatically if the form was configured
27295      * with **`enctype`** `"multipart/form-data"`).
27296      *
27297      * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
27298      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
27299      * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
27300      * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
27301      * has been gathered.
27302      *
27303      * The server response is parsed by the browser to create the document for the IFRAME. If the
27304      * server is using JSON to send the return object, then the [Content-Type][] header must be set to
27305      * "text/html" in order to tell the browser to insert the text unchanged into the document body.
27306      *
27307      * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
27308      * containing a `responseText` property in order to conform to the requirements of event handlers
27309      * and callbacks.
27310      *
27311      * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
27312      * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
27313      * and parameter values from the packet content.
27314      *
27315      * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
27316      * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
27317      * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
27318      *
27319      * @param {Object} options.headers Request headers to set for the request.
27320      *
27321      * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
27322      * of params for the post data. Any params will be appended to the URL.
27323      *
27324      * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
27325      * instead of params for the post data. Any params will be appended to the URL.
27326      *
27327      * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
27328      *
27329      * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
27330      *
27331      * @return {Object} The request object. This may be used to cancel the request.
27332      */
27333     request : function(options) {
27334         options = options || {};
27335         var me = this,
27336             scope = options.scope || window,
27337             username = options.username || me.username,
27338             password = options.password || me.password || '',
27339             async,
27340             requestOptions,
27341             request,
27342             headers,
27343             xhr;
27344
27345         if (me.fireEvent('beforerequest', me, options) !== false) {
27346
27347             requestOptions = me.setOptions(options, scope);
27348
27349             if (this.isFormUpload(options) === true) {
27350                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
27351                 return null;
27352             }
27353
27354             // if autoabort is set, cancel the current transactions
27355             if (options.autoAbort === true || me.autoAbort) {
27356                 me.abort();
27357             }
27358
27359             // create a connection object
27360
27361             if ((options.cors === true || me.cors === true) && Ext.isIe && Ext.ieVersion >= 8) {
27362                 xhr = new XDomainRequest();
27363             } else {
27364                 xhr = this.getXhrInstance();
27365             }
27366
27367             async = options.async !== false ? (options.async || me.async) : false;
27368
27369             // open the request
27370             if (username) {
27371                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
27372             } else {
27373                 xhr.open(requestOptions.method, requestOptions.url, async);
27374             }
27375
27376             if (options.withCredentials === true || me.withCredentials === true) {
27377                 xhr.withCredentials = true;
27378             }
27379
27380             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
27381
27382             // create the transaction object
27383             request = {
27384                 id: ++Ext.data.Connection.requestId,
27385                 xhr: xhr,
27386                 headers: headers,
27387                 options: options,
27388                 async: async,
27389                 timeout: setTimeout(function() {
27390                     request.timedout = true;
27391                     me.abort(request);
27392                 }, options.timeout || me.timeout)
27393             };
27394             me.requests[request.id] = request;
27395             me.latestId = request.id;
27396             // bind our statechange listener
27397             if (async) {
27398                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
27399             }
27400
27401             // start the request!
27402             xhr.send(requestOptions.data);
27403             if (!async) {
27404                 return this.onComplete(request);
27405             }
27406             return request;
27407         } else {
27408             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
27409             return null;
27410         }
27411     },
27412
27413     /**
27414      * Uploads a form using a hidden iframe.
27415      * @param {String/HTMLElement/Ext.Element} form The form to upload
27416      * @param {String} url The url to post to
27417      * @param {String} params Any extra parameters to pass
27418      * @param {Object} options The initial options
27419      */
27420     upload: function(form, url, params, options) {
27421         form = Ext.getDom(form);
27422         options = options || {};
27423
27424         var id = Ext.id(),
27425                 frame = document.createElement('iframe'),
27426                 hiddens = [],
27427                 encoding = 'multipart/form-data',
27428                 buf = {
27429                     target: form.target,
27430                     method: form.method,
27431                     encoding: form.encoding,
27432                     enctype: form.enctype,
27433                     action: form.action
27434                 }, hiddenItem;
27435
27436         /*
27437          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
27438          * the frame had been added to the document. It seems this has since been corrected in
27439          * Opera so the behaviour has been reverted, the URL will be set before being added.
27440          */
27441         Ext.fly(frame).set({
27442             id: id,
27443             name: id,
27444             cls: Ext.baseCSSPrefix + 'hide-display',
27445             src: Ext.SSL_SECURE_URL
27446         });
27447
27448         document.body.appendChild(frame);
27449
27450         // This is required so that IE doesn't pop the response up in a new window.
27451         if (document.frames) {
27452            document.frames[id].name = id;
27453         }
27454
27455         Ext.fly(form).set({
27456             target: id,
27457             method: 'POST',
27458             enctype: encoding,
27459             encoding: encoding,
27460             action: url || buf.action
27461         });
27462
27463         // add dynamic params
27464         if (params) {
27465             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
27466                 hiddenItem = document.createElement('input');
27467                 Ext.fly(hiddenItem).set({
27468                     type: 'hidden',
27469                     value: value,
27470                     name: name
27471                 });
27472                 form.appendChild(hiddenItem);
27473                 hiddens.push(hiddenItem);
27474             });
27475         }
27476
27477         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
27478         form.submit();
27479
27480         Ext.fly(form).set(buf);
27481         Ext.each(hiddens, function(h) {
27482             Ext.removeNode(h);
27483         });
27484     },
27485
27486     /**
27487      * @private
27488      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
27489      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
27490      * (or a textarea inside the body). We then clean up by removing the iframe
27491      */
27492     onUploadComplete: function(frame, options) {
27493         var me = this,
27494             // bogus response object
27495             response = {
27496                 responseText: '',
27497                 responseXML: null
27498             }, doc, firstChild;
27499
27500         try {
27501             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
27502             if (doc) {
27503                 if (doc.body) {
27504                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
27505                         response.responseText = firstChild.value;
27506                     } else {
27507                         response.responseText = doc.body.innerHTML;
27508                     }
27509                 }
27510                 //in IE the document may still have a body even if returns XML.
27511                 response.responseXML = doc.XMLDocument || doc;
27512             }
27513         } catch (e) {
27514         }
27515
27516         me.fireEvent('requestcomplete', me, response, options);
27517
27518         Ext.callback(options.success, options.scope, [response, options]);
27519         Ext.callback(options.callback, options.scope, [options, true, response]);
27520
27521         setTimeout(function(){
27522             Ext.removeNode(frame);
27523         }, 100);
27524     },
27525
27526     /**
27527      * Detects whether the form is intended to be used for an upload.
27528      * @private
27529      */
27530     isFormUpload: function(options){
27531         var form = this.getForm(options);
27532         if (form) {
27533             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
27534         }
27535         return false;
27536     },
27537
27538     /**
27539      * Gets the form object from options.
27540      * @private
27541      * @param {Object} options The request options
27542      * @return {HTMLElement} The form, null if not passed
27543      */
27544     getForm: function(options){
27545         return Ext.getDom(options.form) || null;
27546     },
27547
27548     /**
27549      * Sets various options such as the url, params for the request
27550      * @param {Object} options The initial options
27551      * @param {Object} scope The scope to execute in
27552      * @return {Object} The params for the request
27553      */
27554     setOptions: function(options, scope){
27555         var me =  this,
27556             params = options.params || {},
27557             extraParams = me.extraParams,
27558             urlParams = options.urlParams,
27559             url = options.url || me.url,
27560             jsonData = options.jsonData,
27561             method,
27562             disableCache,
27563             data;
27564
27565
27566         // allow params to be a method that returns the params object
27567         if (Ext.isFunction(params)) {
27568             params = params.call(scope, options);
27569         }
27570
27571         // allow url to be a method that returns the actual url
27572         if (Ext.isFunction(url)) {
27573             url = url.call(scope, options);
27574         }
27575
27576         url = this.setupUrl(options, url);
27577
27578
27579         // check for xml or json data, and make sure json data is encoded
27580         data = options.rawData || options.xmlData || jsonData || null;
27581         if (jsonData && !Ext.isPrimitive(jsonData)) {
27582             data = Ext.encode(data);
27583         }
27584
27585         // make sure params are a url encoded string and include any extraParams if specified
27586         if (Ext.isObject(params)) {
27587             params = Ext.Object.toQueryString(params);
27588         }
27589
27590         if (Ext.isObject(extraParams)) {
27591             extraParams = Ext.Object.toQueryString(extraParams);
27592         }
27593
27594         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
27595
27596         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
27597
27598         params = this.setupParams(options, params);
27599
27600         // decide the proper method for this request
27601         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
27602         this.setupMethod(options, method);
27603
27604
27605         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
27606         // if the method is get append date to prevent caching
27607         if (method === 'GET' && disableCache) {
27608             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
27609         }
27610
27611         // if the method is get or there is json/xml data append the params to the url
27612         if ((method == 'GET' || data) && params) {
27613             url = Ext.urlAppend(url, params);
27614             params = null;
27615         }
27616
27617         // allow params to be forced into the url
27618         if (urlParams) {
27619             url = Ext.urlAppend(url, urlParams);
27620         }
27621
27622         return {
27623             url: url,
27624             method: method,
27625             data: data || params || null
27626         };
27627     },
27628
27629     /**
27630      * Template method for overriding url
27631      * @template
27632      * @private
27633      * @param {Object} options
27634      * @param {String} url
27635      * @return {String} The modified url
27636      */
27637     setupUrl: function(options, url){
27638         var form = this.getForm(options);
27639         if (form) {
27640             url = url || form.action;
27641         }
27642         return url;
27643     },
27644
27645
27646     /**
27647      * Template method for overriding params
27648      * @template
27649      * @private
27650      * @param {Object} options
27651      * @param {String} params
27652      * @return {String} The modified params
27653      */
27654     setupParams: function(options, params) {
27655         var form = this.getForm(options),
27656             serializedForm;
27657         if (form && !this.isFormUpload(options)) {
27658             serializedForm = Ext.Element.serializeForm(form);
27659             params = params ? (params + '&' + serializedForm) : serializedForm;
27660         }
27661         return params;
27662     },
27663
27664     /**
27665      * Template method for overriding method
27666      * @template
27667      * @private
27668      * @param {Object} options
27669      * @param {String} method
27670      * @return {String} The modified method
27671      */
27672     setupMethod: function(options, method){
27673         if (this.isFormUpload(options)) {
27674             return 'POST';
27675         }
27676         return method;
27677     },
27678
27679     /**
27680      * Setup all the headers for the request
27681      * @private
27682      * @param {Object} xhr The xhr object
27683      * @param {Object} options The options for the request
27684      * @param {Object} data The data for the request
27685      * @param {Object} params The params for the request
27686      */
27687     setupHeaders: function(xhr, options, data, params){
27688         var me = this,
27689             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
27690             contentType = me.defaultPostHeader,
27691             jsonData = options.jsonData,
27692             xmlData = options.xmlData,
27693             key,
27694             header;
27695
27696         if (!headers['Content-Type'] && (data || params)) {
27697             if (data) {
27698                 if (options.rawData) {
27699                     contentType = 'text/plain';
27700                 } else {
27701                     if (xmlData && Ext.isDefined(xmlData)) {
27702                         contentType = 'text/xml';
27703                     } else if (jsonData && Ext.isDefined(jsonData)) {
27704                         contentType = 'application/json';
27705                     }
27706                 }
27707             }
27708             headers['Content-Type'] = contentType;
27709         }
27710
27711         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
27712             headers['X-Requested-With'] = me.defaultXhrHeader;
27713         }
27714         // set up all the request headers on the xhr object
27715         try{
27716             for (key in headers) {
27717                 if (headers.hasOwnProperty(key)) {
27718                     header = headers[key];
27719                     xhr.setRequestHeader(key, header);
27720                 }
27721
27722             }
27723         } catch(e) {
27724             me.fireEvent('exception', key, header);
27725         }
27726         return headers;
27727     },
27728
27729     /**
27730      * Creates the appropriate XHR transport for the browser.
27731      * @private
27732      */
27733     getXhrInstance: (function(){
27734         var options = [function(){
27735             return new XMLHttpRequest();
27736         }, function(){
27737             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
27738         }, function(){
27739             return new ActiveXObject('MSXML2.XMLHTTP');
27740         }, function(){
27741             return new ActiveXObject('Microsoft.XMLHTTP');
27742         }], i = 0,
27743             len = options.length,
27744             xhr;
27745
27746         for(; i < len; ++i) {
27747             try{
27748                 xhr = options[i];
27749                 xhr();
27750                 break;
27751             }catch(e){}
27752         }
27753         return xhr;
27754     })(),
27755
27756     /**
27757      * Determines whether this object has a request outstanding.
27758      * @param {Object} [request] Defaults to the last transaction
27759      * @return {Boolean} True if there is an outstanding request.
27760      */
27761     isLoading : function(request) {
27762         if (!request) {
27763             request = this.getLatest();
27764         }
27765         if (!(request && request.xhr)) {
27766             return false;
27767         }
27768         // if there is a connection and readyState is not 0 or 4
27769         var state = request.xhr.readyState;
27770         return !(state === 0 || state == 4);
27771     },
27772
27773     /**
27774      * Aborts an active request.
27775      * @param {Object} [request] Defaults to the last request
27776      */
27777     abort : function(request) {
27778         var me = this;
27779         
27780         if (!request) {
27781             request = me.getLatest();
27782         }
27783
27784         if (request && me.isLoading(request)) {
27785             /*
27786              * Clear out the onreadystatechange here, this allows us
27787              * greater control, the browser may/may not fire the function
27788              * depending on a series of conditions.
27789              */
27790             request.xhr.onreadystatechange = null;
27791             request.xhr.abort();
27792             me.clearTimeout(request);
27793             if (!request.timedout) {
27794                 request.aborted = true;
27795             }
27796             me.onComplete(request);
27797             me.cleanup(request);
27798         }
27799     },
27800     
27801     /**
27802      * Aborts all active requests
27803      */
27804     abortAll: function(){
27805         var requests = this.requests,
27806             id;
27807         
27808         for (id in requests) {
27809             if (requests.hasOwnProperty(id)) {
27810                 this.abort(requests[id]);
27811             }
27812         }
27813     },
27814     
27815     /**
27816      * Gets the most recent request
27817      * @private
27818      * @return {Object} The request. Null if there is no recent request
27819      */
27820     getLatest: function(){
27821         var id = this.latestId,
27822             request;
27823             
27824         if (id) {
27825             request = this.requests[id];
27826         }
27827         return request || null;
27828     },
27829
27830     /**
27831      * Fires when the state of the xhr changes
27832      * @private
27833      * @param {Object} request The request
27834      */
27835     onStateChange : function(request) {
27836         if (request.xhr.readyState == 4) {
27837             this.clearTimeout(request);
27838             this.onComplete(request);
27839             this.cleanup(request);
27840         }
27841     },
27842
27843     /**
27844      * Clears the timeout on the request
27845      * @private
27846      * @param {Object} The request
27847      */
27848     clearTimeout: function(request){
27849         clearTimeout(request.timeout);
27850         delete request.timeout;
27851     },
27852
27853     /**
27854      * Cleans up any left over information from the request
27855      * @private
27856      * @param {Object} The request
27857      */
27858     cleanup: function(request){
27859         request.xhr = null;
27860         delete request.xhr;
27861     },
27862
27863     /**
27864      * To be called when the request has come back from the server
27865      * @private
27866      * @param {Object} request
27867      * @return {Object} The response
27868      */
27869     onComplete : function(request) {
27870         var me = this,
27871             options = request.options,
27872             result,
27873             success,
27874             response;
27875
27876         try {
27877             result = me.parseStatus(request.xhr.status);
27878         } catch (e) {
27879             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
27880             result = {
27881                 success : false,
27882                 isException : false
27883             };
27884         }
27885         success = result.success;
27886
27887         if (success) {
27888             response = me.createResponse(request);
27889             me.fireEvent('requestcomplete', me, response, options);
27890             Ext.callback(options.success, options.scope, [response, options]);
27891         } else {
27892             if (result.isException || request.aborted || request.timedout) {
27893                 response = me.createException(request);
27894             } else {
27895                 response = me.createResponse(request);
27896             }
27897             me.fireEvent('requestexception', me, response, options);
27898             Ext.callback(options.failure, options.scope, [response, options]);
27899         }
27900         Ext.callback(options.callback, options.scope, [options, success, response]);
27901         delete me.requests[request.id];
27902         return response;
27903     },
27904
27905     /**
27906      * Checks if the response status was successful
27907      * @param {Number} status The status code
27908      * @return {Object} An object containing success/status state
27909      */
27910     parseStatus: function(status) {
27911         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
27912         status = status == 1223 ? 204 : status;
27913
27914         var success = (status >= 200 && status < 300) || status == 304,
27915             isException = false;
27916
27917         if (!success) {
27918             switch (status) {
27919                 case 12002:
27920                 case 12029:
27921                 case 12030:
27922                 case 12031:
27923                 case 12152:
27924                 case 13030:
27925                     isException = true;
27926                     break;
27927             }
27928         }
27929         return {
27930             success: success,
27931             isException: isException
27932         };
27933     },
27934
27935     /**
27936      * Creates the response object
27937      * @private
27938      * @param {Object} request
27939      */
27940     createResponse : function(request) {
27941         var xhr = request.xhr,
27942             headers = {},
27943             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
27944             count = lines.length,
27945             line, index, key, value, response;
27946
27947         while (count--) {
27948             line = lines[count];
27949             index = line.indexOf(':');
27950             if(index >= 0) {
27951                 key = line.substr(0, index).toLowerCase();
27952                 if (line.charAt(index + 1) == ' ') {
27953                     ++index;
27954                 }
27955                 headers[key] = line.substr(index + 1);
27956             }
27957         }
27958
27959         request.xhr = null;
27960         delete request.xhr;
27961
27962         response = {
27963             request: request,
27964             requestId : request.id,
27965             status : xhr.status,
27966             statusText : xhr.statusText,
27967             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
27968             getAllResponseHeaders : function(){ return headers; },
27969             responseText : xhr.responseText,
27970             responseXML : xhr.responseXML
27971         };
27972
27973         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
27974         // functions created with getResponseHeader/getAllResponseHeaders
27975         xhr = null;
27976         return response;
27977     },
27978
27979     /**
27980      * Creates the exception object
27981      * @private
27982      * @param {Object} request
27983      */
27984     createException : function(request) {
27985         return {
27986             request : request,
27987             requestId : request.id,
27988             status : request.aborted ? -1 : 0,
27989             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
27990             aborted: request.aborted,
27991             timedout: request.timedout
27992         };
27993     }
27994 });
27995
27996 /**
27997  * @class Ext.Ajax
27998  * @singleton
27999  * @markdown
28000  * @extends Ext.data.Connection
28001
28002 A singleton instance of an {@link Ext.data.Connection}. This class
28003 is used to communicate with your server side code. It can be used as follows:
28004
28005     Ext.Ajax.request({
28006         url: 'page.php',
28007         params: {
28008             id: 1
28009         },
28010         success: function(response){
28011             var text = response.responseText;
28012             // process server response here
28013         }
28014     });
28015
28016 Default options for all requests can be set by changing a property on the Ext.Ajax class:
28017
28018     Ext.Ajax.timeout = 60000; // 60 seconds
28019
28020 Any options specified in the request method for the Ajax request will override any
28021 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
28022 request will be 60 seconds.
28023
28024     Ext.Ajax.timeout = 120000; // 120 seconds
28025     Ext.Ajax.request({
28026         url: 'page.aspx',
28027         timeout: 60000
28028     });
28029
28030 In general, this class will be used for all Ajax requests in your application.
28031 The main reason for creating a separate {@link Ext.data.Connection} is for a
28032 series of requests that share common settings that are different to all other
28033 requests in the application.
28034
28035  */
28036 Ext.define('Ext.Ajax', {
28037     extend: 'Ext.data.Connection',
28038     singleton: true,
28039
28040     /**
28041      * @cfg {String} url @hide
28042      */
28043     /**
28044      * @cfg {Object} extraParams @hide
28045      */
28046     /**
28047      * @cfg {Object} defaultHeaders @hide
28048      */
28049     /**
28050      * @cfg {String} method (Optional) @hide
28051      */
28052     /**
28053      * @cfg {Number} timeout (Optional) @hide
28054      */
28055     /**
28056      * @cfg {Boolean} autoAbort (Optional) @hide
28057      */
28058
28059     /**
28060      * @cfg {Boolean} disableCaching (Optional) @hide
28061      */
28062
28063     /**
28064      * @property {Boolean} disableCaching
28065      * True to add a unique cache-buster param to GET requests. Defaults to true.
28066      */
28067     /**
28068      * @property {String} url
28069      * The default URL to be used for requests to the server.
28070      * If the server receives all requests through one URL, setting this once is easier than
28071      * entering it on every request.
28072      */
28073     /**
28074      * @property {Object} extraParams
28075      * An object containing properties which are used as extra parameters to each request made
28076      * by this object. Session information and other data that you need
28077      * to pass with each request are commonly put here.
28078      */
28079     /**
28080      * @property {Object} defaultHeaders
28081      * An object containing request headers which are added to each request made by this object.
28082      */
28083     /**
28084      * @property {String} method
28085      * The default HTTP method to be used for requests. Note that this is case-sensitive and
28086      * should be all caps (if not set but params are present will use
28087      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
28088      */
28089     /**
28090      * @property {Number} timeout
28091      * The timeout in milliseconds to be used for requests. Defaults to 30000.
28092      */
28093
28094     /**
28095      * @property {Boolean} autoAbort
28096      * Whether a new request should abort any pending requests.
28097      */
28098     autoAbort : false
28099 });
28100 /**
28101  * A class used to load remote content to an Element. Sample usage:
28102  *
28103  *     Ext.get('el').load({
28104  *         url: 'myPage.php',
28105  *         scripts: true,
28106  *         params: {
28107  *             id: 1
28108  *         }
28109  *     });
28110  *
28111  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
28112  * will be used.
28113  */
28114 Ext.define('Ext.ElementLoader', {
28115
28116     /* Begin Definitions */
28117
28118     mixins: {
28119         observable: 'Ext.util.Observable'
28120     },
28121
28122     uses: [
28123         'Ext.data.Connection',
28124         'Ext.Ajax'
28125     ],
28126
28127     statics: {
28128         Renderer: {
28129             Html: function(loader, response, active){
28130                 loader.getTarget().update(response.responseText, active.scripts === true);
28131                 return true;
28132             }
28133         }
28134     },
28135
28136     /* End Definitions */
28137
28138     /**
28139      * @cfg {String} url
28140      * The url to retrieve the content from.
28141      */
28142     url: null,
28143
28144     /**
28145      * @cfg {Object} params
28146      * Any params to be attached to the Ajax request. These parameters will
28147      * be overridden by any params in the load options.
28148      */
28149     params: null,
28150
28151     /**
28152      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
28153      * will not be overridden by any params in the load options.
28154      */
28155     baseParams: null,
28156
28157     /**
28158      * @cfg {Boolean/Object} autoLoad
28159      * True to have the loader make a request as soon as it is created.
28160      * This argument can also be a set of options that will be passed to {@link #load} is called.
28161      */
28162     autoLoad: false,
28163
28164     /**
28165      * @cfg {HTMLElement/Ext.Element/String} target
28166      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
28167      */
28168     target: null,
28169
28170     /**
28171      * @cfg {Boolean/String} loadMask
28172      * True or a string to show when the element is loading.
28173      */
28174     loadMask: false,
28175
28176     /**
28177      * @cfg {Object} ajaxOptions
28178      * Any additional options to be passed to the request, for example timeout or headers.
28179      */
28180     ajaxOptions: null,
28181
28182     /**
28183      * @cfg {Boolean} scripts
28184      * True to parse any inline script tags in the response.
28185      */
28186     scripts: false,
28187
28188     /**
28189      * @cfg {Function} success
28190      * A function to be called when a load request is successful.
28191      * Will be called with the following config parameters:
28192      *
28193      * - this - The ElementLoader instance.
28194      * - response - The response object.
28195      * - options - Ajax options.
28196      */
28197
28198     /**
28199      * @cfg {Function} failure A function to be called when a load request fails.
28200      * Will be called with the following config parameters:
28201      *
28202      * - this - The ElementLoader instance.
28203      * - response - The response object.
28204      * - options - Ajax options.
28205      */
28206
28207     /**
28208      * @cfg {Function} callback A function to be called when a load request finishes.
28209      * Will be called with the following config parameters:
28210      *
28211      * - this - The ElementLoader instance.
28212      * - success - True if successful request.
28213      * - response - The response object.
28214      * - options - Ajax options.
28215      */
28216
28217     /**
28218      * @cfg {Object} scope
28219      * The scope to execute the {@link #success} and {@link #failure} functions in.
28220      */
28221
28222     /**
28223      * @cfg {Function} renderer
28224      * A custom function to render the content to the element. The passed parameters are:
28225      *
28226      * - The loader
28227      * - The response
28228      * - The active request
28229      */
28230
28231     isLoader: true,
28232
28233     constructor: function(config) {
28234         var me = this,
28235             autoLoad;
28236
28237         config = config || {};
28238         Ext.apply(me, config);
28239         me.setTarget(me.target);
28240         me.addEvents(
28241             /**
28242              * @event beforeload
28243              * Fires before a load request is made to the server.
28244              * Returning false from an event listener can prevent the load
28245              * from occurring.
28246              * @param {Ext.ElementLoader} this
28247              * @param {Object} options The options passed to the request
28248              */
28249             'beforeload',
28250
28251             /**
28252              * @event exception
28253              * Fires after an unsuccessful load.
28254              * @param {Ext.ElementLoader} this
28255              * @param {Object} response The response from the server
28256              * @param {Object} options The options passed to the request
28257              */
28258             'exception',
28259
28260             /**
28261              * @event load
28262              * Fires after a successful load.
28263              * @param {Ext.ElementLoader} this
28264              * @param {Object} response The response from the server
28265              * @param {Object} options The options passed to the request
28266              */
28267             'load'
28268         );
28269
28270         // don't pass config because we have already applied it.
28271         me.mixins.observable.constructor.call(me);
28272
28273         if (me.autoLoad) {
28274             autoLoad = me.autoLoad;
28275             if (autoLoad === true) {
28276                 autoLoad = {};
28277             }
28278             me.load(autoLoad);
28279         }
28280     },
28281
28282     /**
28283      * Sets an {@link Ext.Element} as the target of this loader.
28284      * Note that if the target is changed, any active requests will be aborted.
28285      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
28286      */
28287     setTarget: function(target){
28288         var me = this;
28289         target = Ext.get(target);
28290         if (me.target && me.target != target) {
28291             me.abort();
28292         }
28293         me.target = target;
28294     },
28295
28296     /**
28297      * Returns the target of this loader.
28298      * @return {Ext.Component} The target or null if none exists.
28299      */
28300     getTarget: function(){
28301         return this.target || null;
28302     },
28303
28304     /**
28305      * Aborts the active load request
28306      */
28307     abort: function(){
28308         var active = this.active;
28309         if (active !== undefined) {
28310             Ext.Ajax.abort(active.request);
28311             if (active.mask) {
28312                 this.removeMask();
28313             }
28314             delete this.active;
28315         }
28316     },
28317
28318     /**
28319      * Removes the mask on the target
28320      * @private
28321      */
28322     removeMask: function(){
28323         this.target.unmask();
28324     },
28325
28326     /**
28327      * Adds the mask on the target
28328      * @private
28329      * @param {Boolean/Object} mask The mask configuration
28330      */
28331     addMask: function(mask){
28332         this.target.mask(mask === true ? null : mask);
28333     },
28334
28335     /**
28336      * Loads new data from the server.
28337      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
28338      * the class, with the exception of the target option. Note that any options passed to the method will override any
28339      * class defaults.
28340      */
28341     load: function(options) {
28342
28343         options = Ext.apply({}, options);
28344
28345         var me = this,
28346             target = me.target,
28347             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
28348             params = Ext.apply({}, options.params),
28349             ajaxOptions = Ext.apply({}, options.ajaxOptions),
28350             callback = options.callback || me.callback,
28351             scope = options.scope || me.scope || me,
28352             request;
28353
28354         Ext.applyIf(ajaxOptions, me.ajaxOptions);
28355         Ext.applyIf(options, ajaxOptions);
28356
28357         Ext.applyIf(params, me.params);
28358         Ext.apply(params, me.baseParams);
28359
28360         Ext.applyIf(options, {
28361             url: me.url
28362         });
28363
28364
28365         Ext.apply(options, {
28366             scope: me,
28367             params: params,
28368             callback: me.onComplete
28369         });
28370
28371         if (me.fireEvent('beforeload', me, options) === false) {
28372             return;
28373         }
28374
28375         if (mask) {
28376             me.addMask(mask);
28377         }
28378
28379         request = Ext.Ajax.request(options);
28380         me.active = {
28381             request: request,
28382             options: options,
28383             mask: mask,
28384             scope: scope,
28385             callback: callback,
28386             success: options.success || me.success,
28387             failure: options.failure || me.failure,
28388             renderer: options.renderer || me.renderer,
28389             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
28390         };
28391         me.setOptions(me.active, options);
28392     },
28393
28394     /**
28395      * Sets any additional options on the active request
28396      * @private
28397      * @param {Object} active The active request
28398      * @param {Object} options The initial options
28399      */
28400     setOptions: Ext.emptyFn,
28401
28402     /**
28403      * Parses the response after the request completes
28404      * @private
28405      * @param {Object} options Ajax options
28406      * @param {Boolean} success Success status of the request
28407      * @param {Object} response The response object
28408      */
28409     onComplete: function(options, success, response) {
28410         var me = this,
28411             active = me.active,
28412             scope = active.scope,
28413             renderer = me.getRenderer(active.renderer);
28414
28415
28416         if (success) {
28417             success = renderer.call(me, me, response, active);
28418         }
28419
28420         if (success) {
28421             Ext.callback(active.success, scope, [me, response, options]);
28422             me.fireEvent('load', me, response, options);
28423         } else {
28424             Ext.callback(active.failure, scope, [me, response, options]);
28425             me.fireEvent('exception', me, response, options);
28426         }
28427         Ext.callback(active.callback, scope, [me, success, response, options]);
28428
28429         if (active.mask) {
28430             me.removeMask();
28431         }
28432
28433         delete me.active;
28434     },
28435
28436     /**
28437      * Gets the renderer to use
28438      * @private
28439      * @param {String/Function} renderer The renderer to use
28440      * @return {Function} A rendering function to use.
28441      */
28442     getRenderer: function(renderer){
28443         if (Ext.isFunction(renderer)) {
28444             return renderer;
28445         }
28446         return this.statics().Renderer.Html;
28447     },
28448
28449     /**
28450      * Automatically refreshes the content over a specified period.
28451      * @param {Number} interval The interval to refresh in ms.
28452      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
28453      */
28454     startAutoRefresh: function(interval, options){
28455         var me = this;
28456         me.stopAutoRefresh();
28457         me.autoRefresh = setInterval(function(){
28458             me.load(options);
28459         }, interval);
28460     },
28461
28462     /**
28463      * Clears any auto refresh. See {@link #startAutoRefresh}.
28464      */
28465     stopAutoRefresh: function(){
28466         clearInterval(this.autoRefresh);
28467         delete this.autoRefresh;
28468     },
28469
28470     /**
28471      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
28472      * @return {Boolean} True if the loader is automatically refreshing
28473      */
28474     isAutoRefreshing: function(){
28475         return Ext.isDefined(this.autoRefresh);
28476     },
28477
28478     /**
28479      * Destroys the loader. Any active requests will be aborted.
28480      */
28481     destroy: function(){
28482         var me = this;
28483         me.stopAutoRefresh();
28484         delete me.target;
28485         me.abort();
28486         me.clearListeners();
28487     }
28488 });
28489
28490 /**
28491  * @class Ext.ComponentLoader
28492  * @extends Ext.ElementLoader
28493  *
28494  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
28495  * this class will not be instanced directly, rather a loader configuration will be passed to the
28496  * constructor of the {@link Ext.Component}.
28497  *
28498  * ## HTML Renderer
28499  * By default, the content loaded will be processed as raw html. The response text
28500  * from the request is taken and added to the component. This can be used in
28501  * conjunction with the {@link #scripts} option to execute any inline scripts in
28502  * the resulting content. Using this renderer has the same effect as passing the
28503  * {@link Ext.Component#html} configuration option.
28504  *
28505  * ## Data Renderer
28506  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
28507  * The content received from the response is passed to the {@link Ext.Component#update} method.
28508  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
28509  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
28510  * configuration in conjunction with a {@link Ext.Component#tpl}.
28511  *
28512  * ## Component Renderer
28513  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
28514  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
28515  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
28516  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
28517  * the {@link Ext.container.Container#items} configuration on a Container.
28518  *
28519  * ## Custom Renderer
28520  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
28521  *
28522  * ## Example Usage
28523  *     new Ext.Component({
28524  *         tpl: '{firstName} - {lastName}',
28525  *         loader: {
28526  *             url: 'myPage.php',
28527  *             renderer: 'data',
28528  *             params: {
28529  *                 userId: 1
28530  *             }
28531  *         }
28532  *     });
28533  */
28534 Ext.define('Ext.ComponentLoader', {
28535
28536     /* Begin Definitions */
28537
28538     extend: 'Ext.ElementLoader',
28539
28540     statics: {
28541         Renderer: {
28542             Data: function(loader, response, active){
28543                 var success = true;
28544                 try {
28545                     loader.getTarget().update(Ext.decode(response.responseText));
28546                 } catch (e) {
28547                     success = false;
28548                 }
28549                 return success;
28550             },
28551
28552             Component: function(loader, response, active){
28553                 var success = true,
28554                     target = loader.getTarget(),
28555                     items = [];
28556
28557
28558                 try {
28559                     items = Ext.decode(response.responseText);
28560                 } catch (e) {
28561                     success = false;
28562                 }
28563
28564                 if (success) {
28565                     if (active.removeAll) {
28566                         target.removeAll();
28567                     }
28568                     target.add(items);
28569                 }
28570                 return success;
28571             }
28572         }
28573     },
28574
28575     /* End Definitions */
28576
28577     /**
28578      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
28579      * If a string is passed it will be looked up via the id.
28580      */
28581     target: null,
28582
28583     /**
28584      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
28585      */
28586     loadMask: false,
28587
28588     /**
28589      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
28590      * {@link #renderer}.
28591      */
28592
28593     /**
28594      * @cfg {String/Function} renderer
28595
28596 The type of content that is to be loaded into, which can be one of 3 types:
28597
28598 + **html** : Loads raw html content, see {@link Ext.Component#html}
28599 + **data** : Loads raw html content, see {@link Ext.Component#data}
28600 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
28601
28602 Alternatively, you can pass a function which is called with the following parameters.
28603
28604 + loader - Loader instance
28605 + response - The server response
28606 + active - The active request
28607
28608 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
28609
28610     new Ext.Component({
28611         loader: {
28612             url: 'myPage.php',
28613             renderer: function(loader, response, active) {
28614                 var text = response.responseText;
28615                 loader.getTarget().update('The response is ' + text);
28616                 return true;
28617             }
28618         }
28619     });
28620      */
28621     renderer: 'html',
28622
28623     /**
28624      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
28625      * any active requests will be aborted.
28626      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
28627      * it will be looked up via its id.
28628      */
28629     setTarget: function(target){
28630         var me = this;
28631
28632         if (Ext.isString(target)) {
28633             target = Ext.getCmp(target);
28634         }
28635
28636         if (me.target && me.target != target) {
28637             me.abort();
28638         }
28639         me.target = target;
28640     },
28641
28642     // inherit docs
28643     removeMask: function(){
28644         this.target.setLoading(false);
28645     },
28646
28647     /**
28648      * Add the mask on the target
28649      * @private
28650      * @param {Boolean/Object} mask The mask configuration
28651      */
28652     addMask: function(mask){
28653         this.target.setLoading(mask);
28654     },
28655
28656     /**
28657      * Get the target of this loader.
28658      * @return {Ext.Component} target The target, null if none exists.
28659      */
28660
28661     setOptions: function(active, options){
28662         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
28663     },
28664
28665     /**
28666      * Gets the renderer to use
28667      * @private
28668      * @param {String/Function} renderer The renderer to use
28669      * @return {Function} A rendering function to use.
28670      */
28671     getRenderer: function(renderer){
28672         if (Ext.isFunction(renderer)) {
28673             return renderer;
28674         }
28675
28676         var renderers = this.statics().Renderer;
28677         switch (renderer) {
28678             case 'component':
28679                 return renderers.Component;
28680             case 'data':
28681                 return renderers.Data;
28682             default:
28683                 return Ext.ElementLoader.Renderer.Html;
28684         }
28685     }
28686 });
28687
28688 /**
28689  * @author Ed Spencer
28690  *
28691  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
28692  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
28693  * express like this:
28694  *
28695  *     Ext.define('User', {
28696  *         extend: 'Ext.data.Model',
28697  *         fields: ['id', 'name', 'email'],
28698  *
28699  *         hasMany: {model: 'Order', name: 'orders'}
28700  *     });
28701  *
28702  *     Ext.define('Order', {
28703  *         extend: 'Ext.data.Model',
28704  *         fields: ['id', 'user_id', 'status', 'price'],
28705  *
28706  *         belongsTo: 'User'
28707  *     });
28708  *
28709  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
28710  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
28711  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
28712  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
28713  *
28714  * **Further Reading**
28715  *
28716  *   - {@link Ext.data.HasManyAssociation hasMany associations}
28717  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
28718  *   - {@link Ext.data.Model using Models}
28719  *
28720  * # Self association models
28721  *
28722  * We can also have models that create parent/child associations between the same type. Below is an example, where
28723  * groups can be nested inside other groups:
28724  *
28725  *     // Server Data
28726  *     {
28727  *         "groups": {
28728  *             "id": 10,
28729  *             "parent_id": 100,
28730  *             "name": "Main Group",
28731  *             "parent_group": {
28732  *                 "id": 100,
28733  *                 "parent_id": null,
28734  *                 "name": "Parent Group"
28735  *             },
28736  *             "child_groups": [{
28737  *                 "id": 2,
28738  *                 "parent_id": 10,
28739  *                 "name": "Child Group 1"
28740  *             },{
28741  *                 "id": 3,
28742  *                 "parent_id": 10,
28743  *                 "name": "Child Group 2"
28744  *             },{
28745  *                 "id": 4,
28746  *                 "parent_id": 10,
28747  *                 "name": "Child Group 3"
28748  *             }]
28749  *         }
28750  *     }
28751  *
28752  *     // Client code
28753  *     Ext.define('Group', {
28754  *         extend: 'Ext.data.Model',
28755  *         fields: ['id', 'parent_id', 'name'],
28756  *         proxy: {
28757  *             type: 'ajax',
28758  *             url: 'data.json',
28759  *             reader: {
28760  *                 type: 'json',
28761  *                 root: 'groups'
28762  *             }
28763  *         },
28764  *         associations: [{
28765  *             type: 'hasMany',
28766  *             model: 'Group',
28767  *             primaryKey: 'id',
28768  *             foreignKey: 'parent_id',
28769  *             autoLoad: true,
28770  *             associationKey: 'child_groups' // read child data from child_groups
28771  *         }, {
28772  *             type: 'belongsTo',
28773  *             model: 'Group',
28774  *             primaryKey: 'id',
28775  *             foreignKey: 'parent_id',
28776  *             associationKey: 'parent_group' // read parent data from parent_group
28777  *         }]
28778  *     });
28779  *
28780  *     Ext.onReady(function(){
28781  *
28782  *         Group.load(10, {
28783  *             success: function(group){
28784  *                 console.log(group.getGroup().get('name'));
28785  *
28786  *                 group.groups().each(function(rec){
28787  *                     console.log(rec.get('name'));
28788  *                 });
28789  *             }
28790  *         });
28791  *
28792  *     });
28793  *
28794  */
28795 Ext.define('Ext.data.Association', {
28796     /**
28797      * @cfg {String} ownerModel (required)
28798      * The string name of the model that owns the association.
28799      */
28800
28801     /**
28802      * @cfg {String} associatedModel (required)
28803      * The string name of the model that is being associated with.
28804      */
28805
28806     /**
28807      * @cfg {String} primaryKey
28808      * The name of the primary key on the associated model. In general this will be the
28809      * {@link Ext.data.Model#idProperty} of the Model.
28810      */
28811     primaryKey: 'id',
28812
28813     /**
28814      * @cfg {Ext.data.reader.Reader} reader
28815      * A special reader to read associated data
28816      */
28817     
28818     /**
28819      * @cfg {String} associationKey
28820      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
28821      */
28822
28823     defaultReaderType: 'json',
28824
28825     statics: {
28826         create: function(association){
28827             if (!association.isAssociation) {
28828                 if (Ext.isString(association)) {
28829                     association = {
28830                         type: association
28831                     };
28832                 }
28833
28834                 switch (association.type) {
28835                     case 'belongsTo':
28836                         return Ext.create('Ext.data.BelongsToAssociation', association);
28837                     case 'hasMany':
28838                         return Ext.create('Ext.data.HasManyAssociation', association);
28839                     //TODO Add this back when it's fixed
28840 //                    case 'polymorphic':
28841 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
28842                     default:
28843                 }
28844             }
28845             return association;
28846         }
28847     },
28848
28849     /**
28850      * Creates the Association object.
28851      * @param {Object} [config] Config object.
28852      */
28853     constructor: function(config) {
28854         Ext.apply(this, config);
28855
28856         var types           = Ext.ModelManager.types,
28857             ownerName       = config.ownerModel,
28858             associatedName  = config.associatedModel,
28859             ownerModel      = types[ownerName],
28860             associatedModel = types[associatedName],
28861             ownerProto;
28862
28863
28864         this.ownerModel = ownerModel;
28865         this.associatedModel = associatedModel;
28866
28867         /**
28868          * @property {String} ownerName
28869          * The name of the model that 'owns' the association
28870          */
28871
28872         /**
28873          * @property {String} associatedName
28874          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
28875          * 'Order')
28876          */
28877
28878         Ext.applyIf(this, {
28879             ownerName : ownerName,
28880             associatedName: associatedName
28881         });
28882     },
28883
28884     /**
28885      * Get a specialized reader for reading associated data
28886      * @return {Ext.data.reader.Reader} The reader, null if not supplied
28887      */
28888     getReader: function(){
28889         var me = this,
28890             reader = me.reader,
28891             model = me.associatedModel;
28892
28893         if (reader) {
28894             if (Ext.isString(reader)) {
28895                 reader = {
28896                     type: reader
28897                 };
28898             }
28899             if (reader.isReader) {
28900                 reader.setModel(model);
28901             } else {
28902                 Ext.applyIf(reader, {
28903                     model: model,
28904                     type : me.defaultReaderType
28905                 });
28906             }
28907             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
28908         }
28909         return me.reader || null;
28910     }
28911 });
28912
28913 /**
28914  * @author Ed Spencer
28915  * @class Ext.ModelManager
28916  * @extends Ext.AbstractManager
28917
28918 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
28919
28920 __Creating Model Instances__
28921
28922 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
28923 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
28924 this by using the Model type directly. The following 3 snippets are equivalent:
28925
28926     Ext.define('User', {
28927         extend: 'Ext.data.Model',
28928         fields: ['first', 'last']
28929     });
28930
28931     // method 1, create using Ext.create (recommended)
28932     Ext.create('User', {
28933         first: 'Ed',
28934         last: 'Spencer'
28935     });
28936
28937     // method 2, create through the manager (deprecated)
28938     Ext.ModelManager.create({
28939         first: 'Ed',
28940         last: 'Spencer'
28941     }, 'User');
28942
28943     // method 3, create on the type directly
28944     new User({
28945         first: 'Ed',
28946         last: 'Spencer'
28947     });
28948
28949 __Accessing Model Types__
28950
28951 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
28952 are normal classes, you can access the type directly. The following snippets are equivalent:
28953
28954     Ext.define('User', {
28955         extend: 'Ext.data.Model',
28956         fields: ['first', 'last']
28957     });
28958
28959     // method 1, access model type through the manager
28960     var UserType = Ext.ModelManager.getModel('User');
28961
28962     // method 2, reference the type directly
28963     var UserType = User;
28964
28965  * @markdown
28966  * @singleton
28967  */
28968 Ext.define('Ext.ModelManager', {
28969     extend: 'Ext.AbstractManager',
28970     alternateClassName: 'Ext.ModelMgr',
28971     requires: ['Ext.data.Association'],
28972
28973     singleton: true,
28974
28975     typeName: 'mtype',
28976
28977     /**
28978      * Private stack of associations that must be created once their associated model has been defined
28979      * @property {Ext.data.Association[]} associationStack
28980      */
28981     associationStack: [],
28982
28983     /**
28984      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
28985      * immediately, as are any addition plugins defined in the model config.
28986      * @private
28987      */
28988     registerType: function(name, config) {
28989         var proto = config.prototype,
28990             model;
28991         if (proto && proto.isModel) {
28992             // registering an already defined model
28993             model = config;
28994         } else {
28995             // passing in a configuration
28996             if (!config.extend) {
28997                 config.extend = 'Ext.data.Model';
28998             }
28999             model = Ext.define(name, config);
29000         }
29001         this.types[name] = model;
29002         return model;
29003     },
29004
29005     /**
29006      * @private
29007      * Private callback called whenever a model has just been defined. This sets up any associations
29008      * that were waiting for the given model to be defined
29009      * @param {Function} model The model that was just created
29010      */
29011     onModelDefined: function(model) {
29012         var stack  = this.associationStack,
29013             length = stack.length,
29014             create = [],
29015             association, i, created;
29016
29017         for (i = 0; i < length; i++) {
29018             association = stack[i];
29019
29020             if (association.associatedModel == model.modelName) {
29021                 create.push(association);
29022             }
29023         }
29024
29025         for (i = 0, length = create.length; i < length; i++) {
29026             created = create[i];
29027             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
29028             Ext.Array.remove(stack, created);
29029         }
29030     },
29031
29032     /**
29033      * Registers an association where one of the models defined doesn't exist yet.
29034      * The ModelManager will check when new models are registered if it can link them
29035      * together
29036      * @private
29037      * @param {Ext.data.Association} association The association
29038      */
29039     registerDeferredAssociation: function(association){
29040         this.associationStack.push(association);
29041     },
29042
29043     /**
29044      * Returns the {@link Ext.data.Model} for a given model name
29045      * @param {String/Object} id The id of the model or the model instance.
29046      * @return {Ext.data.Model} a model class.
29047      */
29048     getModel: function(id) {
29049         var model = id;
29050         if (typeof model == 'string') {
29051             model = this.types[model];
29052         }
29053         return model;
29054     },
29055
29056     /**
29057      * Creates a new instance of a Model using the given data.
29058      *
29059      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
29060      *
29061      *     Ext.create('User', {
29062      *         first: 'Ed',
29063      *         last: 'Spencer'
29064      *     });
29065      *
29066      * @param {Object} data Data to initialize the Model's fields with
29067      * @param {String} name The name of the model to create
29068      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
29069      */
29070     create: function(config, name, id) {
29071         var con = typeof name == 'function' ? name : this.types[name || config.name];
29072
29073         return new con(config, id);
29074     }
29075 }, function() {
29076
29077     /**
29078      * Old way for creating Model classes.  Instead use:
29079      *
29080      *     Ext.define("MyModel", {
29081      *         extend: "Ext.data.Model",
29082      *         fields: []
29083      *     });
29084      *
29085      * @param {String} name Name of the Model class.
29086      * @param {Object} config A configuration object for the Model you wish to create.
29087      * @return {Ext.data.Model} The newly registered Model
29088      * @member Ext
29089      * @deprecated 4.0.0 Use {@link Ext#define} instead.
29090      */
29091     Ext.regModel = function() {
29092         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
29093     };
29094 });
29095
29096 /**
29097  * @singleton
29098  *
29099  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
29100  *
29101  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
29102  *
29103  *     {
29104  *         ptype: 'gridviewdragdrop',
29105  *         dragText: 'Drag and drop to reorganize'
29106  *     }
29107  *
29108  * Or just use the ptype on its own:
29109  *
29110  *     'gridviewdragdrop'
29111  *
29112  * Alternatively you can instantiate the plugin with Ext.create:
29113  *
29114  *     Ext.create('Ext.view.plugin.AutoComplete', {
29115  *         ptype: 'gridviewdragdrop',
29116  *         dragText: 'Drag and drop to reorganize'
29117  *     })
29118  */
29119 Ext.define('Ext.PluginManager', {
29120     extend: 'Ext.AbstractManager',
29121     alternateClassName: 'Ext.PluginMgr',
29122     singleton: true,
29123     typeName: 'ptype',
29124
29125     /**
29126      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
29127      * instantiate.
29128      * @param {Object} config A configuration object for the Plugin you wish to create.
29129      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
29130      * contain a `ptype`. (Optional if the config contains a `ptype`).
29131      * @return {Ext.Component} The newly instantiated Plugin.
29132      */
29133     //create: function(plugin, defaultType) {
29134     //    if (plugin instanceof this) {
29135     //        return plugin;
29136     //    } else {
29137     //        var type, config = {};
29138     //
29139     //        if (Ext.isString(plugin)) {
29140     //            type = plugin;
29141     //        }
29142     //        else {
29143     //            type = plugin[this.typeName] || defaultType;
29144     //            config = plugin;
29145     //        }
29146     //
29147     //        return Ext.createByAlias('plugin.' + type, config);
29148     //    }
29149     //},
29150
29151     create : function(config, defaultType){
29152         if (config.init) {
29153             return config;
29154         } else {
29155             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
29156         }
29157
29158         // Prior system supported Singleton plugins.
29159         //var PluginCls = this.types[config.ptype || defaultType];
29160         //if (PluginCls.init) {
29161         //    return PluginCls;
29162         //} else {
29163         //    return new PluginCls(config);
29164         //}
29165     },
29166
29167     /**
29168      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
29169      * @param {String} type The type to search for
29170      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
29171      * truthy
29172      * @return {Ext.AbstractPlugin[]} All matching plugins
29173      */
29174     findByType: function(type, defaultsOnly) {
29175         var matches = [],
29176             types   = this.types;
29177
29178         for (var name in types) {
29179             if (!types.hasOwnProperty(name)) {
29180                 continue;
29181             }
29182             var item = types[name];
29183
29184             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
29185                 matches.push(item);
29186             }
29187         }
29188
29189         return matches;
29190     }
29191 }, function() {
29192     /**
29193      * Shorthand for {@link Ext.PluginManager#registerType}
29194      * @param {String} ptype The ptype mnemonic string by which the Plugin class
29195      * may be looked up.
29196      * @param {Function} cls The new Plugin class.
29197      * @member Ext
29198      * @method preg
29199      */
29200     Ext.preg = function() {
29201         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
29202     };
29203 });
29204
29205 /**
29206  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
29207  *
29208  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
29209  * arguments:
29210  *
29211  * # Single argument: String/Array
29212  *
29213  * The single argument may be either a String or an Array:
29214  *
29215  * - String:
29216  *
29217  *       var t = new Ext.Template("<div>Hello {0}.</div>");
29218  *       t.{@link #append}('some-element', ['foo']);
29219  *
29220  * - Array:
29221  *
29222  *   An Array will be combined with `join('')`.
29223  *
29224  *       var t = new Ext.Template([
29225  *           '<div name="{id}">',
29226  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
29227  *           '</div>',
29228  *       ]);
29229  *       t.{@link #compile}();
29230  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
29231  *
29232  * # Multiple arguments: String, Object, Array, ...
29233  *
29234  * Multiple arguments will be combined with `join('')`.
29235  *
29236  *     var t = new Ext.Template(
29237  *         '<div name="{id}">',
29238  *             '<span class="{cls}">{name} {value}</span>',
29239  *         '</div>',
29240  *         // a configuration object:
29241  *         {
29242  *             compiled: true,      // {@link #compile} immediately
29243  *         }
29244  *     );
29245  *
29246  * # Notes
29247  *
29248  * - For a list of available format functions, see {@link Ext.util.Format}.
29249  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
29250  */
29251 Ext.define('Ext.Template', {
29252
29253     /* Begin Definitions */
29254
29255     requires: ['Ext.DomHelper', 'Ext.util.Format'],
29256
29257     inheritableStatics: {
29258         /**
29259          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
29260          * @param {String/HTMLElement} el A DOM element or its id
29261          * @param {Object} config (optional) Config object
29262          * @return {Ext.Template} The created template
29263          * @static
29264          * @inheritable
29265          */
29266         from: function(el, config) {
29267             el = Ext.getDom(el);
29268             return new this(el.value || el.innerHTML, config || '');
29269         }
29270     },
29271
29272     /* End Definitions */
29273
29274     /**
29275      * Creates new template.
29276      * 
29277      * @param {String...} html List of strings to be concatenated into template.
29278      * Alternatively an array of strings can be given, but then no config object may be passed.
29279      * @param {Object} config (optional) Config object
29280      */
29281     constructor: function(html) {
29282         var me = this,
29283             args = arguments,
29284             buffer = [],
29285             i = 0,
29286             length = args.length,
29287             value;
29288
29289         me.initialConfig = {};
29290
29291         if (length > 1) {
29292             for (; i < length; i++) {
29293                 value = args[i];
29294                 if (typeof value == 'object') {
29295                     Ext.apply(me.initialConfig, value);
29296                     Ext.apply(me, value);
29297                 } else {
29298                     buffer.push(value);
29299                 }
29300             }
29301             html = buffer.join('');
29302         } else {
29303             if (Ext.isArray(html)) {
29304                 buffer.push(html.join(''));
29305             } else {
29306                 buffer.push(html);
29307             }
29308         }
29309
29310         // @private
29311         me.html = buffer.join('');
29312
29313         if (me.compiled) {
29314             me.compile();
29315         }
29316     },
29317
29318     isTemplate: true,
29319
29320     /**
29321      * @cfg {Boolean} compiled
29322      * True to immediately compile the template. Defaults to false.
29323      */
29324
29325     /**
29326      * @cfg {Boolean} disableFormats
29327      * True to disable format functions in the template. If the template doesn't contain
29328      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
29329      */
29330     disableFormats: false,
29331
29332     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
29333
29334     /**
29335      * Returns an HTML fragment of this template with the specified values applied.
29336      *
29337      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
29338      *
29339      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
29340      *     tpl.applyTemplate(['John', 25]);
29341      *
29342      * or an object:
29343      *
29344      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
29345      *     tpl.applyTemplate({name: 'John', age: 25});
29346      *
29347      * @return {String} The HTML fragment
29348      */
29349     applyTemplate: function(values) {
29350         var me = this,
29351             useFormat = me.disableFormats !== true,
29352             fm = Ext.util.Format,
29353             tpl = me;
29354
29355         if (me.compiled) {
29356             return me.compiled(values);
29357         }
29358         function fn(m, name, format, args) {
29359             if (format && useFormat) {
29360                 if (args) {
29361                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
29362                 } else {
29363                     args = [values[name]];
29364                 }
29365                 if (format.substr(0, 5) == "this.") {
29366                     return tpl[format.substr(5)].apply(tpl, args);
29367                 }
29368                 else {
29369                     return fm[format].apply(fm, args);
29370                 }
29371             }
29372             else {
29373                 return values[name] !== undefined ? values[name] : "";
29374             }
29375         }
29376         return me.html.replace(me.re, fn);
29377     },
29378
29379     /**
29380      * Sets the HTML used as the template and optionally compiles it.
29381      * @param {String} html
29382      * @param {Boolean} compile (optional) True to compile the template.
29383      * @return {Ext.Template} this
29384      */
29385     set: function(html, compile) {
29386         var me = this;
29387         me.html = html;
29388         me.compiled = null;
29389         return compile ? me.compile() : me;
29390     },
29391
29392     compileARe: /\\/g,
29393     compileBRe: /(\r\n|\n)/g,
29394     compileCRe: /'/g,
29395
29396     /**
29397      * Compiles the template into an internal function, eliminating the RegEx overhead.
29398      * @return {Ext.Template} this
29399      */
29400     compile: function() {
29401         var me = this,
29402             fm = Ext.util.Format,
29403             useFormat = me.disableFormats !== true,
29404             body, bodyReturn;
29405
29406         function fn(m, name, format, args) {
29407             if (format && useFormat) {
29408                 args = args ? ',' + args: "";
29409                 if (format.substr(0, 5) != "this.") {
29410                     format = "fm." + format + '(';
29411                 }
29412                 else {
29413                     format = 'this.' + format.substr(5) + '(';
29414                 }
29415             }
29416             else {
29417                 args = '';
29418                 format = "(values['" + name + "'] == undefined ? '' : ";
29419             }
29420             return "'," + format + "values['" + name + "']" + args + ") ,'";
29421         }
29422
29423         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
29424         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
29425         eval(body);
29426         return me;
29427     },
29428
29429     /**
29430      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
29431      *
29432      * @param {String/HTMLElement/Ext.Element} el The context element
29433      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29434      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29435      * @return {HTMLElement/Ext.Element} The new node or Element
29436      */
29437     insertFirst: function(el, values, returnElement) {
29438         return this.doInsert('afterBegin', el, values, returnElement);
29439     },
29440
29441     /**
29442      * Applies the supplied values to the template and inserts the new node(s) before el.
29443      *
29444      * @param {String/HTMLElement/Ext.Element} el The context element
29445      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29446      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29447      * @return {HTMLElement/Ext.Element} The new node or Element
29448      */
29449     insertBefore: function(el, values, returnElement) {
29450         return this.doInsert('beforeBegin', el, values, returnElement);
29451     },
29452
29453     /**
29454      * Applies the supplied values to the template and inserts the new node(s) after el.
29455      *
29456      * @param {String/HTMLElement/Ext.Element} el The context element
29457      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29458      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29459      * @return {HTMLElement/Ext.Element} The new node or Element
29460      */
29461     insertAfter: function(el, values, returnElement) {
29462         return this.doInsert('afterEnd', el, values, returnElement);
29463     },
29464
29465     /**
29466      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
29467      *
29468      * For example usage see {@link Ext.Template Ext.Template class docs}.
29469      *
29470      * @param {String/HTMLElement/Ext.Element} el The context element
29471      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29472      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
29473      * @return {HTMLElement/Ext.Element} The new node or Element
29474      */
29475     append: function(el, values, returnElement) {
29476         return this.doInsert('beforeEnd', el, values, returnElement);
29477     },
29478
29479     doInsert: function(where, el, values, returnEl) {
29480         el = Ext.getDom(el);
29481         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
29482         return returnEl ? Ext.get(newNode, true) : newNode;
29483     },
29484
29485     /**
29486      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
29487      *
29488      * @param {String/HTMLElement/Ext.Element} el The context element
29489      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29490      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29491      * @return {HTMLElement/Ext.Element} The new node or Element
29492      */
29493     overwrite: function(el, values, returnElement) {
29494         el = Ext.getDom(el);
29495         el.innerHTML = this.applyTemplate(values);
29496         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
29497     }
29498 }, function() {
29499
29500     /**
29501      * @method apply
29502      * @member Ext.Template
29503      * Alias for {@link #applyTemplate}.
29504      * @alias Ext.Template#applyTemplate
29505      */
29506     this.createAlias('apply', 'applyTemplate');
29507 });
29508
29509 /**
29510  * A template class that supports advanced functionality like:
29511  *
29512  * - Autofilling arrays using templates and sub-templates
29513  * - Conditional processing with basic comparison operators
29514  * - Basic math function support
29515  * - Execute arbitrary inline code with special built-in template variables
29516  * - Custom member functions
29517  * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
29518  *
29519  * XTemplate provides the templating mechanism built into:
29520  *
29521  * - {@link Ext.view.View}
29522  *
29523  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
29524  * demonstrate all of the supported features.
29525  *
29526  * # Sample Data
29527  *
29528  * This is the data object used for reference in each code example:
29529  *
29530  *     var data = {
29531  *         name: 'Tommy Maintz',
29532  *         title: 'Lead Developer',
29533  *         company: 'Sencha Inc.',
29534  *         email: 'tommy@sencha.com',
29535  *         address: '5 Cups Drive',
29536  *         city: 'Palo Alto',
29537  *         state: 'CA',
29538  *         zip: '44102',
29539  *         drinks: ['Coffee', 'Soda', 'Water'],
29540  *         kids: [
29541  *             {
29542  *                 name: 'Joshua',
29543  *                 age:3
29544  *             },
29545  *             {
29546  *                 name: 'Matthew',
29547  *                 age:2
29548  *             },
29549  *             {
29550  *                 name: 'Solomon',
29551  *                 age:0
29552  *             }
29553  *         ]
29554  *     };
29555  *
29556  * # Auto filling of arrays
29557  *
29558  * The **tpl** tag and the **for** operator are used to process the provided data object:
29559  *
29560  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
29561  *   tag for each item in the array.
29562  * - If for="." is specified, the data object provided is examined.
29563  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
29564  *
29565  * Examples:
29566  *
29567  *     <tpl for=".">...</tpl>       // loop through array at root node
29568  *     <tpl for="foo">...</tpl>     // loop through array at foo node
29569  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
29570  *
29571  * Using the sample data above:
29572  *
29573  *     var tpl = new Ext.XTemplate(
29574  *         '<p>Kids: ',
29575  *         '<tpl for=".">',       // process the data.kids node
29576  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
29577  *         '</tpl></p>'
29578  *     );
29579  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
29580  *
29581  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
29582  * object to populate the template:
29583  *
29584  *     var tpl = new Ext.XTemplate(
29585  *         '<p>Name: {name}</p>',
29586  *         '<p>Title: {title}</p>',
29587  *         '<p>Company: {company}</p>',
29588  *         '<p>Kids: ',
29589  *         '<tpl for="kids">',     // interrogate the kids property within the data
29590  *             '<p>{name}</p>',
29591  *         '</tpl></p>'
29592  *     );
29593  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
29594  *
29595  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
29596  * loop. This variable will represent the value of the array at the current index:
29597  *
29598  *     var tpl = new Ext.XTemplate(
29599  *         '<p>{name}\'s favorite beverages:</p>',
29600  *         '<tpl for="drinks">',
29601  *             '<div> - {.}</div>',
29602  *         '</tpl>'
29603  *     );
29604  *     tpl.overwrite(panel.body, data);
29605  *
29606  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
29607  * members via the **parent** object:
29608  *
29609  *     var tpl = new Ext.XTemplate(
29610  *         '<p>Name: {name}</p>',
29611  *         '<p>Kids: ',
29612  *         '<tpl for="kids">',
29613  *             '<tpl if="age &gt; 1">',
29614  *                 '<p>{name}</p>',
29615  *                 '<p>Dad: {parent.name}</p>',
29616  *             '</tpl>',
29617  *         '</tpl></p>'
29618  *     );
29619  *     tpl.overwrite(panel.body, data);
29620  *
29621  * # Conditional processing with basic comparison operators
29622  *
29623  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
29624  * specific parts of the template. Notes:
29625  *
29626  * - Double quotes must be encoded if used within the conditional
29627  * - There is no else operator -- if needed, two opposite if statements should be used.
29628  *
29629  * Examples:
29630  *
29631  *     <tpl if="age > 1 && age < 10">Child</tpl>
29632  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
29633  *     <tpl if="this.isGirl(name)">...</tpl>
29634  *     <tpl if="id==\'download\'">...</tpl>
29635  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
29636  *     // no good:
29637  *     <tpl if="name == "Tommy"">Hello</tpl>
29638  *     // encode " if it is part of the condition, e.g.
29639  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
29640  *
29641  * Using the sample data above:
29642  *
29643  *     var tpl = new Ext.XTemplate(
29644  *         '<p>Name: {name}</p>',
29645  *         '<p>Kids: ',
29646  *         '<tpl for="kids">',
29647  *             '<tpl if="age &gt; 1">',
29648  *                 '<p>{name}</p>',
29649  *             '</tpl>',
29650  *         '</tpl></p>'
29651  *     );
29652  *     tpl.overwrite(panel.body, data);
29653  *
29654  * # Basic math support
29655  *
29656  * The following basic math operators may be applied directly on numeric data values:
29657  *
29658  *     + - * /
29659  *
29660  * For example:
29661  *
29662  *     var tpl = new Ext.XTemplate(
29663  *         '<p>Name: {name}</p>',
29664  *         '<p>Kids: ',
29665  *         '<tpl for="kids">',
29666  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
29667  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
29668  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
29669  *                 '<p>Dad: {parent.name}</p>',
29670  *             '</tpl>',
29671  *         '</tpl></p>'
29672  *     );
29673  *     tpl.overwrite(panel.body, data);
29674  *
29675  * # Execute arbitrary inline code with special built-in template variables
29676  *
29677  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
29678  * variables available in that code:
29679  *
29680  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
29681  *   you can change what values is.
29682  * - **parent**: The scope (values) of the ancestor template.
29683  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
29684  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
29685  *
29686  * This example demonstrates basic row striping using an inline code block and the xindex variable:
29687  *
29688  *     var tpl = new Ext.XTemplate(
29689  *         '<p>Name: {name}</p>',
29690  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
29691  *         '<p>Kids: ',
29692  *         '<tpl for="kids">',
29693  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
29694  *             '{name}',
29695  *             '</div>',
29696  *         '</tpl></p>'
29697  *      );
29698  *     tpl.overwrite(panel.body, data);
29699  *
29700  * # Template member functions
29701  *
29702  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
29703  * more complex processing:
29704  *
29705  *     var tpl = new Ext.XTemplate(
29706  *         '<p>Name: {name}</p>',
29707  *         '<p>Kids: ',
29708  *         '<tpl for="kids">',
29709  *             '<tpl if="this.isGirl(name)">',
29710  *                 '<p>Girl: {name} - {age}</p>',
29711  *             '</tpl>',
29712  *              // use opposite if statement to simulate 'else' processing:
29713  *             '<tpl if="this.isGirl(name) == false">',
29714  *                 '<p>Boy: {name} - {age}</p>',
29715  *             '</tpl>',
29716  *             '<tpl if="this.isBaby(age)">',
29717  *                 '<p>{name} is a baby!</p>',
29718  *             '</tpl>',
29719  *         '</tpl></p>',
29720  *         {
29721  *             // XTemplate configuration:
29722  *             disableFormats: true,
29723  *             // member functions:
29724  *             isGirl: function(name){
29725  *                return name == 'Sara Grace';
29726  *             },
29727  *             isBaby: function(age){
29728  *                return age < 1;
29729  *             }
29730  *         }
29731  *     );
29732  *     tpl.overwrite(panel.body, data);
29733  */
29734 Ext.define('Ext.XTemplate', {
29735
29736     /* Begin Definitions */
29737
29738     extend: 'Ext.Template',
29739
29740     /* End Definitions */
29741
29742     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
29743     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
29744     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
29745     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
29746     constructor: function() {
29747         this.callParent(arguments);
29748
29749         var me = this,
29750             html = me.html,
29751             argsRe = me.argsRe,
29752             nameRe = me.nameRe,
29753             ifRe = me.ifRe,
29754             execRe = me.execRe,
29755             id = 0,
29756             tpls = [],
29757             VALUES = 'values',
29758             PARENT = 'parent',
29759             XINDEX = 'xindex',
29760             XCOUNT = 'xcount',
29761             RETURN = 'return ',
29762             WITHVALUES = 'with(values){ ',
29763             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
29764
29765         html = ['<tpl>', html, '</tpl>'].join('');
29766
29767         while ((m = html.match(argsRe))) {
29768             exp = null;
29769             fn = null;
29770             exec = null;
29771             matchName = m[0].match(nameRe);
29772             matchIf = m[0].match(ifRe);
29773             matchExec = m[0].match(execRe);
29774
29775             exp = matchIf ? matchIf[1] : null;
29776             if (exp) {
29777                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
29778             }
29779
29780             exp = matchExec ? matchExec[1] : null;
29781             if (exp) {
29782                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
29783             }
29784
29785             name = matchName ? matchName[1] : null;
29786             if (name) {
29787                 if (name === '.') {
29788                     name = VALUES;
29789                 } else if (name === '..') {
29790                     name = PARENT;
29791                 }
29792                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
29793             }
29794
29795             tpls.push({
29796                 id: id,
29797                 target: name,
29798                 exec: exec,
29799                 test: fn,
29800                 body: m[1] || ''
29801             });
29802
29803             html = html.replace(m[0], '{xtpl' + id + '}');
29804             id = id + 1;
29805         }
29806
29807         for (i = tpls.length - 1; i >= 0; --i) {
29808             me.compileTpl(tpls[i]);
29809         }
29810         me.master = tpls[tpls.length - 1];
29811         me.tpls = tpls;
29812     },
29813
29814     // @private
29815     applySubTemplate: function(id, values, parent, xindex, xcount) {
29816         var me = this, t = me.tpls[id];
29817         return t.compiled.call(me, values, parent, xindex, xcount);
29818     },
29819
29820     /**
29821      * @cfg {RegExp} codeRe
29822      * The regular expression used to match code variables. Default: matches {[expression]}.
29823      */
29824     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
29825
29826     /**
29827      * @cfg {Boolean} compiled
29828      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
29829      */
29830
29831     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
29832
29833     // @private
29834     compileTpl: function(tpl) {
29835         var fm = Ext.util.Format,
29836             me = this,
29837             useFormat = me.disableFormats !== true,
29838             body, bodyReturn, evaluatedFn;
29839
29840         function fn(m, name, format, args, math) {
29841             var v;
29842             // name is what is inside the {}
29843             // Name begins with xtpl, use a Sub Template
29844             if (name.substr(0, 4) == 'xtpl') {
29845                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
29846             }
29847             // name = "." - Just use the values object.
29848             if (name == '.') {
29849                 // filter to not include arrays/objects/nulls
29850                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
29851             }
29852
29853             // name = "#" - Use the xindex
29854             else if (name == '#') {
29855                 v = 'xindex';
29856             }
29857             else if (name.substr(0, 7) == "parent.") {
29858                 v = name;
29859             }
29860             // name has a . in it - Use object literal notation, starting from values
29861             else if (name.indexOf('.') != -1) {
29862                 v = "values." + name;
29863             }
29864
29865             // name is a property of values
29866             else {
29867                 v = "values['" + name + "']";
29868             }
29869             if (math) {
29870                 v = '(' + v + math + ')';
29871             }
29872             if (format && useFormat) {
29873                 args = args ? ',' + args : "";
29874                 if (format.substr(0, 5) != "this.") {
29875                     format = "fm." + format + '(';
29876                 }
29877                 else {
29878                     format = 'this.' + format.substr(5) + '(';
29879                 }
29880             }
29881             else {
29882                 args = '';
29883                 format = "(" + v + " === undefined ? '' : ";
29884             }
29885             return "'," + format + v + args + "),'";
29886         }
29887
29888         function codeFn(m, code) {
29889             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
29890             return "',(" + code.replace(me.compileARe, "'") + "),'";
29891         }
29892
29893         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
29894         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
29895         eval(body);
29896
29897         tpl.compiled = function(values, parent, xindex, xcount) {
29898             var vs,
29899                 length,
29900                 buffer,
29901                 i;
29902
29903             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
29904                 return '';
29905             }
29906
29907             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
29908             if (!vs) {
29909                return '';
29910             }
29911
29912             parent = tpl.target ? values : parent;
29913             if (tpl.target && Ext.isArray(vs)) {
29914                 buffer = [];
29915                 length = vs.length;
29916                 if (tpl.exec) {
29917                     for (i = 0; i < length; i++) {
29918                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29919                         tpl.exec.call(me, vs[i], parent, i + 1, length);
29920                     }
29921                 } else {
29922                     for (i = 0; i < length; i++) {
29923                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29924                     }
29925                 }
29926                 return buffer.join('');
29927             }
29928
29929             if (tpl.exec) {
29930                 tpl.exec.call(me, vs, parent, xindex, xcount);
29931             }
29932             return evaluatedFn.call(me, vs, parent, xindex, xcount);
29933         };
29934
29935         return this;
29936     },
29937
29938     // inherit docs from Ext.Template
29939     applyTemplate: function(values) {
29940         return this.master.compiled.call(this, values, {}, 1, 1);
29941     },
29942
29943     /**
29944      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
29945      * @return {Ext.XTemplate} this
29946      */
29947     compile: function() {
29948         return this;
29949     }
29950 }, function() {
29951     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
29952     this.createAlias('apply', 'applyTemplate');
29953 });
29954
29955 /**
29956  * @class Ext.app.Controller
29957  *
29958  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
29959  * views) and take some action. Here's how we might create a Controller to manage Users:
29960  *
29961  *     Ext.define('MyApp.controller.Users', {
29962  *         extend: 'Ext.app.Controller',
29963  *
29964  *         init: function() {
29965  *             console.log('Initialized Users! This happens before the Application launch function is called');
29966  *         }
29967  *     });
29968  *
29969  * The init function is a special method that is called when your application boots. It is called before the
29970  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
29971  * your Viewport is created.
29972  *
29973  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
29974  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
29975  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
29976  * our Users controller to tell us when the panel is rendered:
29977  *
29978  *     Ext.define('MyApp.controller.Users', {
29979  *         extend: 'Ext.app.Controller',
29980  *
29981  *         init: function() {
29982  *             this.control({
29983  *                 'viewport > panel': {
29984  *                     render: this.onPanelRendered
29985  *                 }
29986  *             });
29987  *         },
29988  *
29989  *         onPanelRendered: function() {
29990  *             console.log('The panel was rendered');
29991  *         }
29992  *     });
29993  *
29994  * We've updated the init function to use this.control to set up listeners on views in our application. The control
29995  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
29996  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
29997  * it allows us to pass a CSS-like selector that will find every matching component on the page.
29998  *
29999  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
30000  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
30001  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
30002  * onPanelRendered function is called.
30003  *
30004  * <u>Using refs</u>
30005  *
30006  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
30007  * make it really easy to get references to Views on your page. Let's look at an example of this now:
30008  *
30009  *     Ext.define('MyApp.controller.Users', {
30010  *         extend: 'Ext.app.Controller',
30011  *
30012  *         refs: [
30013  *             {
30014  *                 ref: 'list',
30015  *                 selector: 'grid'
30016  *             }
30017  *         ],
30018  *
30019  *         init: function() {
30020  *             this.control({
30021  *                 'button': {
30022  *                     click: this.refreshGrid
30023  *                 }
30024  *             });
30025  *         },
30026  *
30027  *         refreshGrid: function() {
30028  *             this.getList().store.load();
30029  *         }
30030  *     });
30031  *
30032  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
30033  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
30034  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30035  * assigns it to the reference 'list'.
30036  *
30037  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30038  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
30039  * was capitalized and prepended with get to go from 'list' to 'getList'.
30040  *
30041  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30042  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
30043  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30044  * match a single View in your application (in the case above our selector will match any grid on the page).
30045  *
30046  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30047  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
30048  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30049  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30050  *
30051  * You can create any number of refs and control any number of components this way, simply adding more functions to
30052  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
30053  * examples/app/feed-viewer folder in the SDK download.
30054  *
30055  * <u>Generated getter methods</u>
30056  *
30057  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
30058  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30059  *
30060  *     Ext.define('MyApp.controller.Users', {
30061  *         extend: 'Ext.app.Controller',
30062  *
30063  *         models: ['User'],
30064  *         stores: ['AllUsers', 'AdminUsers'],
30065  *
30066  *         init: function() {
30067  *             var User = this.getUserModel(),
30068  *                 allUsers = this.getAllUsersStore();
30069  *
30070  *             var ed = new User({name: 'Ed'});
30071  *             allUsers.add(ed);
30072  *         }
30073  *     });
30074  *
30075  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30076  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
30077  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30078  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
30079  * functionality.
30080  *
30081  * <u>Further Reading</u>
30082  *
30083  * For more information about writing Ext JS 4 applications, please see the
30084  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
30085  *
30086  * @docauthor Ed Spencer
30087  */
30088 Ext.define('Ext.app.Controller', {
30089
30090     mixins: {
30091         observable: 'Ext.util.Observable'
30092     },
30093
30094     /**
30095      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30096      */
30097     
30098     /**
30099      * @cfg {String[]} models
30100      * Array of models to require from AppName.model namespace. For example:
30101      * 
30102      *     Ext.define("MyApp.controller.Foo", {
30103      *         extend: "Ext.app.Controller",
30104      *         models: ['User', 'Vehicle']
30105      *     });
30106      * 
30107      * This is equivalent of:
30108      * 
30109      *     Ext.define("MyApp.controller.Foo", {
30110      *         extend: "Ext.app.Controller",
30111      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
30112      *     });
30113      * 
30114      */
30115
30116     /**
30117      * @cfg {String[]} views
30118      * Array of views to require from AppName.view namespace. For example:
30119      * 
30120      *     Ext.define("MyApp.controller.Foo", {
30121      *         extend: "Ext.app.Controller",
30122      *         views: ['List', 'Detail']
30123      *     });
30124      * 
30125      * This is equivalent of:
30126      * 
30127      *     Ext.define("MyApp.controller.Foo", {
30128      *         extend: "Ext.app.Controller",
30129      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
30130      *     });
30131      * 
30132      */
30133
30134     /**
30135      * @cfg {String[]} stores
30136      * Array of stores to require from AppName.store namespace. For example:
30137      * 
30138      *     Ext.define("MyApp.controller.Foo", {
30139      *         extend: "Ext.app.Controller",
30140      *         stores: ['Users', 'Vehicles']
30141      *     });
30142      * 
30143      * This is equivalent of:
30144      * 
30145      *     Ext.define("MyApp.controller.Foo", {
30146      *         extend: "Ext.app.Controller",
30147      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
30148      *     });
30149      * 
30150      */
30151
30152     onClassExtended: function(cls, data) {
30153         var className = Ext.getClassName(cls),
30154             match = className.match(/^(.*)\.controller\./);
30155
30156         if (match !== null) {
30157             var namespace = Ext.Loader.getPrefix(className) || match[1],
30158                 onBeforeClassCreated = data.onBeforeClassCreated,
30159                 requires = [],
30160                 modules = ['model', 'view', 'store'],
30161                 prefix;
30162
30163             data.onBeforeClassCreated = function(cls, data) {
30164                 var i, ln, module,
30165                     items, j, subLn, item;
30166
30167                 for (i = 0,ln = modules.length; i < ln; i++) {
30168                     module = modules[i];
30169
30170                     items = Ext.Array.from(data[module + 's']);
30171
30172                     for (j = 0,subLn = items.length; j < subLn; j++) {
30173                         item = items[j];
30174
30175                         prefix = Ext.Loader.getPrefix(item);
30176
30177                         if (prefix === '' || prefix === item) {
30178                             requires.push(namespace + '.' + module + '.' + item);
30179                         }
30180                         else {
30181                             requires.push(item);
30182                         }
30183                     }
30184                 }
30185
30186                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30187             };
30188         }
30189     },
30190
30191     /**
30192      * Creates new Controller.
30193      * @param {Object} config (optional) Config object.
30194      */
30195     constructor: function(config) {
30196         this.mixins.observable.constructor.call(this, config);
30197
30198         Ext.apply(this, config || {});
30199
30200         this.createGetters('model', this.models);
30201         this.createGetters('store', this.stores);
30202         this.createGetters('view', this.views);
30203
30204         if (this.refs) {
30205             this.ref(this.refs);
30206         }
30207     },
30208
30209     /**
30210      * A template method that is called when your application boots. It is called before the
30211      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30212      * your Viewport is created.
30213      * 
30214      * @param {Ext.app.Application} application
30215      * @template
30216      */
30217     init: function(application) {},
30218
30219     /**
30220      * A template method like {@link #init}, but called after the viewport is created.
30221      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
30222      * 
30223      * @param {Ext.app.Application} application
30224      * @template
30225      */
30226     onLaunch: function(application) {},
30227
30228     createGetters: function(type, refs) {
30229         type = Ext.String.capitalize(type);
30230         Ext.Array.each(refs, function(ref) {
30231             var fn = 'get',
30232                 parts = ref.split('.');
30233
30234             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30235             Ext.Array.each(parts, function(part) {
30236                 fn += Ext.String.capitalize(part);
30237             });
30238             fn += type;
30239
30240             if (!this[fn]) {
30241                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30242             }
30243             // Execute it right away
30244             this[fn](ref);
30245         },
30246         this);
30247     },
30248
30249     ref: function(refs) {
30250         var me = this;
30251         refs = Ext.Array.from(refs);
30252         Ext.Array.each(refs, function(info) {
30253             var ref = info.ref,
30254                 fn = 'get' + Ext.String.capitalize(ref);
30255             if (!me[fn]) {
30256                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30257             }
30258         });
30259     },
30260
30261     getRef: function(ref, info, config) {
30262         this.refCache = this.refCache || {};
30263         info = info || {};
30264         config = config || {};
30265
30266         Ext.apply(info, config);
30267
30268         if (info.forceCreate) {
30269             return Ext.ComponentManager.create(info, 'component');
30270         }
30271
30272         var me = this,
30273             selector = info.selector,
30274             cached = me.refCache[ref];
30275
30276         if (!cached) {
30277             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30278             if (!cached && info.autoCreate) {
30279                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30280             }
30281             if (cached) {
30282                 cached.on('beforedestroy', function() {
30283                     me.refCache[ref] = null;
30284                 });
30285             }
30286         }
30287
30288         return cached;
30289     },
30290
30291     /**
30292      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
30293      * object containing component paths mapped to a hash of listener functions.
30294      *
30295      * In the following example the `updateUser` function is mapped to to the `click`
30296      * event on a button component, which is a child of the `useredit` component.
30297      *
30298      *     Ext.define('AM.controller.Users', {
30299      *         init: function() {
30300      *             this.control({
30301      *                 'useredit button[action=save]': {
30302      *                     click: this.updateUser
30303      *                 }
30304      *             });
30305      *         },
30306      *
30307      *         updateUser: function(button) {
30308      *             console.log('clicked the Save button');
30309      *         }
30310      *     });
30311      *
30312      * See {@link Ext.ComponentQuery} for more information on component selectors.
30313      *
30314      * @param {String/Object} selectors If a String, the second argument is used as the
30315      * listeners, otherwise an object of selectors -> listeners is assumed
30316      * @param {Object} listeners
30317      */
30318     control: function(selectors, listeners) {
30319         this.application.control(selectors, listeners, this);
30320     },
30321
30322     /**
30323      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
30324      * When controller doesn't exist yet, it's created.
30325      * @param {String} name
30326      * @return {Ext.app.Controller} a controller instance.
30327      */
30328     getController: function(name) {
30329         return this.application.getController(name);
30330     },
30331
30332     /**
30333      * Returns instance of a {@link Ext.data.Store Store} with the given name.
30334      * When store doesn't exist yet, it's created.
30335      * @param {String} name
30336      * @return {Ext.data.Store} a store instance.
30337      */
30338     getStore: function(name) {
30339         return this.application.getStore(name);
30340     },
30341
30342     /**
30343      * Returns a {@link Ext.data.Model Model} class with the given name.
30344      * A shorthand for using {@link Ext.ModelManager#getModel}.
30345      * @param {String} name
30346      * @return {Ext.data.Model} a model class.
30347      */
30348     getModel: function(model) {
30349         return this.application.getModel(model);
30350     },
30351
30352     /**
30353      * Returns a View class with the given name.  To create an instance of the view,
30354      * you can use it like it's used by Application to create the Viewport:
30355      * 
30356      *     this.getView('Viewport').create();
30357      * 
30358      * @param {String} name
30359      * @return {Ext.Base} a view class.
30360      */
30361     getView: function(view) {
30362         return this.application.getView(view);
30363     }
30364 });
30365
30366 /**
30367  * @author Don Griffin
30368  *
30369  * This class is a base for all id generators. It also provides lookup of id generators by
30370  * their id.
30371  * 
30372  * Generally, id generators are used to generate a primary key for new model instances. There
30373  * are different approaches to solving this problem, so this mechanism has both simple use
30374  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
30375  * using the {@link Ext.data.Model#idgen} property.
30376  *
30377  * # Identity, Type and Shared IdGenerators
30378  *
30379  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
30380  * This is done by giving IdGenerator instances an id property by which they can be looked
30381  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
30382  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
30383  * assign them the same id:
30384  *
30385  *     Ext.define('MyApp.data.MyModelA', {
30386  *         extend: 'Ext.data.Model',
30387  *         idgen: {
30388  *             type: 'sequential',
30389  *             id: 'foo'
30390  *         }
30391  *     });
30392  *
30393  *     Ext.define('MyApp.data.MyModelB', {
30394  *         extend: 'Ext.data.Model',
30395  *         idgen: {
30396  *             type: 'sequential',
30397  *             id: 'foo'
30398  *         }
30399  *     });
30400  *
30401  * To make this as simple as possible for generator types that are shared by many (or all)
30402  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
30403  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
30404  * to its type ('uuid'). In other words, the following Models share the same generator:
30405  *
30406  *     Ext.define('MyApp.data.MyModelX', {
30407  *         extend: 'Ext.data.Model',
30408  *         idgen: 'uuid'
30409  *     });
30410  *
30411  *     Ext.define('MyApp.data.MyModelY', {
30412  *         extend: 'Ext.data.Model',
30413  *         idgen: 'uuid'
30414  *     });
30415  *
30416  * This can be overridden (by specifying the id explicitly), but there is no particularly
30417  * good reason to do so for this generator type.
30418  *
30419  * # Creating Custom Generators
30420  * 
30421  * An id generator should derive from this class and implement the {@link #generate} method.
30422  * The constructor will apply config properties on new instances, so a constructor is often
30423  * not necessary.
30424  *
30425  * To register an id generator type, a derived class should provide an `alias` like so:
30426  *
30427  *     Ext.define('MyApp.data.CustomIdGenerator', {
30428  *         extend: 'Ext.data.IdGenerator',
30429  *         alias: 'idgen.custom',
30430  *
30431  *         configProp: 42, // some config property w/default value
30432  *
30433  *         generate: function () {
30434  *             return ... // a new id
30435  *         }
30436  *     });
30437  *
30438  * Using the custom id generator is then straightforward:
30439  *
30440  *     Ext.define('MyApp.data.MyModel', {
30441  *         extend: 'Ext.data.Model',
30442  *         idgen: 'custom'
30443  *     });
30444  *     // or...
30445  *
30446  *     Ext.define('MyApp.data.MyModel', {
30447  *         extend: 'Ext.data.Model',
30448  *         idgen: {
30449  *             type: 'custom',
30450  *             configProp: value
30451  *         }
30452  *     });
30453  *
30454  * It is not recommended to mix shared generators with generator configuration. This leads
30455  * to unpredictable results unless all configurations match (which is also redundant). In
30456  * such cases, a custom generator with a default id is the best approach.
30457  *
30458  *     Ext.define('MyApp.data.CustomIdGenerator', {
30459  *         extend: 'Ext.data.SequentialIdGenerator',
30460  *         alias: 'idgen.custom',
30461  *
30462  *         id: 'custom', // shared by default
30463  *
30464  *         prefix: 'ID_',
30465  *         seed: 1000
30466  *     });
30467  *
30468  *     Ext.define('MyApp.data.MyModelX', {
30469  *         extend: 'Ext.data.Model',
30470  *         idgen: 'custom'
30471  *     });
30472  *
30473  *     Ext.define('MyApp.data.MyModelY', {
30474  *         extend: 'Ext.data.Model',
30475  *         idgen: 'custom'
30476  *     });
30477  *
30478  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
30479  *
30480  */
30481 Ext.define('Ext.data.IdGenerator', {
30482
30483     isGenerator: true,
30484
30485     /**
30486      * Initializes a new instance.
30487      * @param {Object} config (optional) Configuration object to be applied to the new instance.
30488      */
30489     constructor: function(config) {
30490         var me = this;
30491
30492         Ext.apply(me, config);
30493
30494         if (me.id) {
30495             Ext.data.IdGenerator.all[me.id] = me;
30496         }
30497     },
30498
30499     /**
30500      * @cfg {String} id
30501      * The id by which to register a new instance. This instance can be found using the
30502      * {@link Ext.data.IdGenerator#get} static method.
30503      */
30504
30505     getRecId: function (rec) {
30506         return rec.modelName + '-' + rec.internalId;
30507     },
30508
30509     /**
30510      * Generates and returns the next id. This method must be implemented by the derived
30511      * class.
30512      *
30513      * @return {String} The next id.
30514      * @method generate
30515      * @abstract
30516      */
30517
30518     statics: {
30519         /**
30520          * @property {Object} all
30521          * This object is keyed by id to lookup instances.
30522          * @private
30523          * @static
30524          */
30525         all: {},
30526
30527         /**
30528          * Returns the IdGenerator given its config description.
30529          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
30530          * simply returned. If this is a string, it is first used as an id for lookup and
30531          * then, if there is no match, as a type to create a new instance. This parameter
30532          * can also be a config object that contains a `type` property (among others) that
30533          * are used to create and configure the instance.
30534          * @static
30535          */
30536         get: function (config) {
30537             var generator,
30538                 id,
30539                 type;
30540
30541             if (typeof config == 'string') {
30542                 id = type = config;
30543                 config = null;
30544             } else if (config.isGenerator) {
30545                 return config;
30546             } else {
30547                 id = config.id || config.type;
30548                 type = config.type;
30549             }
30550
30551             generator = this.all[id];
30552             if (!generator) {
30553                 generator = Ext.create('idgen.' + type, config);
30554             }
30555
30556             return generator;
30557         }
30558     }
30559 });
30560
30561 /**
30562  * @class Ext.data.SortTypes
30563  * This class defines a series of static methods that are used on a
30564  * {@link Ext.data.Field} for performing sorting. The methods cast the 
30565  * underlying values into a data type that is appropriate for sorting on
30566  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
30567  * the sortType will be set to a sane default if the sortType is not 
30568  * explicitly defined on the field. The sortType will make any necessary
30569  * modifications to the value and return it.
30570  * <ul>
30571  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
30572  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
30573  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
30574  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
30575  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
30576  * <li><b>asInt</b> - Converts the value to an integer number</li>
30577  * </ul>
30578  * <p>
30579  * It is also possible to create a custom sortType that can be used throughout
30580  * an application.
30581  * <pre><code>
30582 Ext.apply(Ext.data.SortTypes, {
30583     asPerson: function(person){
30584         // expects an object with a first and last name property
30585         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
30586     }    
30587 });
30588
30589 Ext.define('Employee', {
30590     extend: 'Ext.data.Model',
30591     fields: [{
30592         name: 'person',
30593         sortType: 'asPerson'
30594     }, {
30595         name: 'salary',
30596         type: 'float' // sortType set to asFloat
30597     }]
30598 });
30599  * </code></pre>
30600  * </p>
30601  * @singleton
30602  * @docauthor Evan Trimboli <evan@sencha.com>
30603  */
30604 Ext.define('Ext.data.SortTypes', {
30605     
30606     singleton: true,
30607     
30608     /**
30609      * Default sort that does nothing
30610      * @param {Object} s The value being converted
30611      * @return {Object} The comparison value
30612      */
30613     none : function(s) {
30614         return s;
30615     },
30616
30617     /**
30618      * The regular expression used to strip tags
30619      * @type {RegExp}
30620      * @property
30621      */
30622     stripTagsRE : /<\/?[^>]+>/gi,
30623
30624     /**
30625      * Strips all HTML tags to sort on text only
30626      * @param {Object} s The value being converted
30627      * @return {String} The comparison value
30628      */
30629     asText : function(s) {
30630         return String(s).replace(this.stripTagsRE, "");
30631     },
30632
30633     /**
30634      * Strips all HTML tags to sort on text only - Case insensitive
30635      * @param {Object} s The value being converted
30636      * @return {String} The comparison value
30637      */
30638     asUCText : function(s) {
30639         return String(s).toUpperCase().replace(this.stripTagsRE, "");
30640     },
30641
30642     /**
30643      * Case insensitive string
30644      * @param {Object} s The value being converted
30645      * @return {String} The comparison value
30646      */
30647     asUCString : function(s) {
30648         return String(s).toUpperCase();
30649     },
30650
30651     /**
30652      * Date sorting
30653      * @param {Object} s The value being converted
30654      * @return {Number} The comparison value
30655      */
30656     asDate : function(s) {
30657         if(!s){
30658             return 0;
30659         }
30660         if(Ext.isDate(s)){
30661             return s.getTime();
30662         }
30663         return Date.parse(String(s));
30664     },
30665
30666     /**
30667      * Float sorting
30668      * @param {Object} s The value being converted
30669      * @return {Number} The comparison value
30670      */
30671     asFloat : function(s) {
30672         var val = parseFloat(String(s).replace(/,/g, ""));
30673         return isNaN(val) ? 0 : val;
30674     },
30675
30676     /**
30677      * Integer sorting
30678      * @param {Object} s The value being converted
30679      * @return {Number} The comparison value
30680      */
30681     asInt : function(s) {
30682         var val = parseInt(String(s).replace(/,/g, ""), 10);
30683         return isNaN(val) ? 0 : val;
30684     }
30685 });
30686 /**
30687  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
30688  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
30689  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
30690  * on their records. Example usage:
30691  *
30692  *     //set up a fictional MixedCollection containing a few people to filter on
30693  *     var allNames = new Ext.util.MixedCollection();
30694  *     allNames.addAll([
30695  *         {id: 1, name: 'Ed',    age: 25},
30696  *         {id: 2, name: 'Jamie', age: 37},
30697  *         {id: 3, name: 'Abe',   age: 32},
30698  *         {id: 4, name: 'Aaron', age: 26},
30699  *         {id: 5, name: 'David', age: 32}
30700  *     ]);
30701  *
30702  *     var ageFilter = new Ext.util.Filter({
30703  *         property: 'age',
30704  *         value   : 32
30705  *     });
30706  *
30707  *     var longNameFilter = new Ext.util.Filter({
30708  *         filterFn: function(item) {
30709  *             return item.name.length > 4;
30710  *         }
30711  *     });
30712  *
30713  *     //a new MixedCollection with the 3 names longer than 4 characters
30714  *     var longNames = allNames.filter(longNameFilter);
30715  *
30716  *     //a new MixedCollection with the 2 people of age 24:
30717  *     var youngFolk = allNames.filter(ageFilter);
30718  *
30719  */
30720 Ext.define('Ext.util.Filter', {
30721
30722     /* Begin Definitions */
30723
30724     /* End Definitions */
30725     /**
30726      * @cfg {String} property
30727      * The property to filter on. Required unless a {@link #filterFn} is passed
30728      */
30729     
30730     /**
30731      * @cfg {Function} filterFn
30732      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
30733      * true to accept each item or false to reject it
30734      */
30735     
30736     /**
30737      * @cfg {Boolean} anyMatch
30738      * True to allow any match - no regex start/end line anchors will be added.
30739      */
30740     anyMatch: false,
30741     
30742     /**
30743      * @cfg {Boolean} exactMatch
30744      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
30745      */
30746     exactMatch: false,
30747     
30748     /**
30749      * @cfg {Boolean} caseSensitive
30750      * True to make the regex case sensitive (adds 'i' switch to regex).
30751      */
30752     caseSensitive: false,
30753     
30754     /**
30755      * @cfg {String} root
30756      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
30757      * make the filter pull the {@link #property} out of the data object of each item
30758      */
30759
30760     /**
30761      * Creates new Filter.
30762      * @param {Object} [config] Config object
30763      */
30764     constructor: function(config) {
30765         var me = this;
30766         Ext.apply(me, config);
30767         
30768         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
30769         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
30770         me.filter = me.filter || me.filterFn;
30771         
30772         if (me.filter === undefined) {
30773             if (me.property === undefined || me.value === undefined) {
30774                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
30775                 // Model has been updated to allow string ids
30776                 
30777                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
30778             } else {
30779                 me.filter = me.createFilterFn();
30780             }
30781             
30782             me.filterFn = me.filter;
30783         }
30784     },
30785     
30786     /**
30787      * @private
30788      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
30789      */
30790     createFilterFn: function() {
30791         var me       = this,
30792             matcher  = me.createValueMatcher(),
30793             property = me.property;
30794         
30795         return function(item) {
30796             var value = me.getRoot.call(me, item)[property];
30797             return matcher === null ? value === null : matcher.test(value);
30798         };
30799     },
30800     
30801     /**
30802      * @private
30803      * Returns the root property of the given item, based on the configured {@link #root} property
30804      * @param {Object} item The item
30805      * @return {Object} The root property of the object
30806      */
30807     getRoot: function(item) {
30808         var root = this.root;
30809         return root === undefined ? item : item[root];
30810     },
30811     
30812     /**
30813      * @private
30814      * Returns a regular expression based on the given value and matching options
30815      */
30816     createValueMatcher : function() {
30817         var me            = this,
30818             value         = me.value,
30819             anyMatch      = me.anyMatch,
30820             exactMatch    = me.exactMatch,
30821             caseSensitive = me.caseSensitive,
30822             escapeRe      = Ext.String.escapeRegex;
30823             
30824         if (value === null) {
30825             return value;
30826         }
30827         
30828         if (!value.exec) { // not a regex
30829             value = String(value);
30830
30831             if (anyMatch === true) {
30832                 value = escapeRe(value);
30833             } else {
30834                 value = '^' + escapeRe(value);
30835                 if (exactMatch === true) {
30836                     value += '$';
30837                 }
30838             }
30839             value = new RegExp(value, caseSensitive ? '' : 'i');
30840          }
30841          
30842          return value;
30843     }
30844 });
30845 /**
30846  * Represents a single sorter that can be applied to a Store. The sorter is used
30847  * to compare two values against each other for the purpose of ordering them. Ordering
30848  * is achieved by specifying either:
30849  *
30850  * - {@link #property A sorting property}
30851  * - {@link #sorterFn A sorting function}
30852  *
30853  * As a contrived example, we can specify a custom sorter that sorts by rank:
30854  *
30855  *     Ext.define('Person', {
30856  *         extend: 'Ext.data.Model',
30857  *         fields: ['name', 'rank']
30858  *     });
30859  *
30860  *     Ext.create('Ext.data.Store', {
30861  *         model: 'Person',
30862  *         proxy: 'memory',
30863  *         sorters: [{
30864  *             sorterFn: function(o1, o2){
30865  *                 var getRank = function(o){
30866  *                     var name = o.get('rank');
30867  *                     if (name === 'first') {
30868  *                         return 1;
30869  *                     } else if (name === 'second') {
30870  *                         return 2;
30871  *                     } else {
30872  *                         return 3;
30873  *                     }
30874  *                 },
30875  *                 rank1 = getRank(o1),
30876  *                 rank2 = getRank(o2);
30877  *
30878  *                 if (rank1 === rank2) {
30879  *                     return 0;
30880  *                 }
30881  *
30882  *                 return rank1 < rank2 ? -1 : 1;
30883  *             }
30884  *         }],
30885  *         data: [{
30886  *             name: 'Person1',
30887  *             rank: 'second'
30888  *         }, {
30889  *             name: 'Person2',
30890  *             rank: 'third'
30891  *         }, {
30892  *             name: 'Person3',
30893  *             rank: 'first'
30894  *         }]
30895  *     });
30896  */
30897 Ext.define('Ext.util.Sorter', {
30898
30899     /**
30900      * @cfg {String} property
30901      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
30902      * directly and compared for sorting using the built in comparison operators.
30903      */
30904     
30905     /**
30906      * @cfg {Function} sorterFn
30907      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
30908      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
30909      * function should return:
30910      *
30911      *   - -1 if o1 is "less than" o2
30912      *   - 0 if o1 is "equal" to o2
30913      *   - 1 if o1 is "greater than" o2
30914      */
30915     
30916     /**
30917      * @cfg {String} root
30918      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
30919      * make the filter pull the {@link #property} out of the data object of each item
30920      */
30921     
30922     /**
30923      * @cfg {Function} transform
30924      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
30925      * argument, the value.
30926      */
30927     
30928     /**
30929      * @cfg {String} direction
30930      * The direction to sort by.
30931      */
30932     direction: "ASC",
30933     
30934     constructor: function(config) {
30935         var me = this;
30936         
30937         Ext.apply(me, config);
30938         
30939         
30940         me.updateSortFunction();
30941     },
30942     
30943     /**
30944      * @private
30945      * Creates and returns a function which sorts an array by the given property and direction
30946      * @return {Function} A function which sorts by the property/direction combination provided
30947      */
30948     createSortFunction: function(sorterFn) {
30949         var me        = this,
30950             property  = me.property,
30951             direction = me.direction || "ASC",
30952             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
30953         
30954         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
30955         //-1 if object 2 is greater or 0 if they are equal
30956         return function(o1, o2) {
30957             return modifier * sorterFn.call(me, o1, o2);
30958         };
30959     },
30960     
30961     /**
30962      * @private
30963      * Basic default sorter function that just compares the defined property of each object
30964      */
30965     defaultSorterFn: function(o1, o2) {
30966         var me = this,
30967             transform = me.transform,
30968             v1 = me.getRoot(o1)[me.property],
30969             v2 = me.getRoot(o2)[me.property];
30970             
30971         if (transform) {
30972             v1 = transform(v1);
30973             v2 = transform(v2);
30974         }
30975
30976         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
30977     },
30978     
30979     /**
30980      * @private
30981      * Returns the root property of the given item, based on the configured {@link #root} property
30982      * @param {Object} item The item
30983      * @return {Object} The root property of the object
30984      */
30985     getRoot: function(item) {
30986         return this.root === undefined ? item : item[this.root];
30987     },
30988     
30989     /**
30990      * Set the sorting direction for this sorter.
30991      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
30992      */
30993     setDirection: function(direction) {
30994         var me = this;
30995         me.direction = direction;
30996         me.updateSortFunction();
30997     },
30998     
30999     /**
31000      * Toggles the sorting direction for this sorter.
31001      */
31002     toggle: function() {
31003         var me = this;
31004         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
31005         me.updateSortFunction();
31006     },
31007     
31008     /**
31009      * Update the sort function for this sorter.
31010      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
31011      * sorting function.
31012      */
31013     updateSortFunction: function(fn) {
31014         var me = this;
31015         fn = fn || me.sorterFn || me.defaultSorterFn;
31016         me.sort = me.createSortFunction(fn);
31017     }
31018 });
31019 /**
31020  * @author Ed Spencer
31021  *
31022  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
31023  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
31024  * Operation objects directly.
31025  *
31026  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
31027  */
31028 Ext.define('Ext.data.Operation', {
31029     /**
31030      * @cfg {Boolean} synchronous
31031      * True if this Operation is to be executed synchronously. This property is inspected by a
31032      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
31033      */
31034     synchronous: true,
31035
31036     /**
31037      * @cfg {String} action
31038      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
31039      */
31040     action: undefined,
31041
31042     /**
31043      * @cfg {Ext.util.Filter[]} filters
31044      * Optional array of filter objects. Only applies to 'read' actions.
31045      */
31046     filters: undefined,
31047
31048     /**
31049      * @cfg {Ext.util.Sorter[]} sorters
31050      * Optional array of sorter objects. Only applies to 'read' actions.
31051      */
31052     sorters: undefined,
31053
31054     /**
31055      * @cfg {Ext.util.Grouper} group
31056      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31057      */
31058     group: undefined,
31059
31060     /**
31061      * @cfg {Number} start
31062      * The start index (offset), used in paging when running a 'read' action.
31063      */
31064     start: undefined,
31065
31066     /**
31067      * @cfg {Number} limit
31068      * The number of records to load. Used on 'read' actions when paging is being used.
31069      */
31070     limit: undefined,
31071
31072     /**
31073      * @cfg {Ext.data.Batch} batch
31074      * The batch that this Operation is a part of.
31075      */
31076     batch: undefined,
31077
31078     /**
31079      * @cfg {Function} callback
31080      * Function to execute when operation completed.  Will be called with the following parameters:
31081      *
31082      * - records : Array of Ext.data.Model objects.
31083      * - operation : The Ext.data.Operation itself.
31084      * - success : True when operation completed successfully.
31085      */
31086     callback: undefined,
31087
31088     /**
31089      * @cfg {Object} scope
31090      * Scope for the {@link #callback} function.
31091      */
31092     scope: undefined,
31093
31094     /**
31095      * @property {Boolean} started
31096      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31097      * @private
31098      */
31099     started: false,
31100
31101     /**
31102      * @property {Boolean} running
31103      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31104      * @private
31105      */
31106     running: false,
31107
31108     /**
31109      * @property {Boolean} complete
31110      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31111      * @private
31112      */
31113     complete: false,
31114
31115     /**
31116      * @property {Boolean} success
31117      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31118      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31119      * {@link #wasSuccessful} to query success status.
31120      * @private
31121      */
31122     success: undefined,
31123
31124     /**
31125      * @property {Boolean} exception
31126      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31127      * @private
31128      */
31129     exception: false,
31130
31131     /**
31132      * @property {String/Object} error
31133      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31134      * @private
31135      */
31136     error: undefined,
31137
31138     /**
31139      * @property {RegExp} actionCommitRecordsRe
31140      * The RegExp used to categorize actions that require record commits.
31141      */
31142     actionCommitRecordsRe: /^(?:create|update)$/i,
31143
31144     /**
31145      * @property {RegExp} actionSkipSyncRe
31146      * The RegExp used to categorize actions that skip local record synchronization. This defaults
31147      * to match 'destroy'.
31148      */
31149     actionSkipSyncRe: /^destroy$/i,
31150
31151     /**
31152      * Creates new Operation object.
31153      * @param {Object} config (optional) Config object.
31154      */
31155     constructor: function(config) {
31156         Ext.apply(this, config || {});
31157     },
31158
31159     /**
31160      * This method is called to commit data to this instance's records given the records in
31161      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
31162      * those records (for 'create' and 'update' actions).
31163      *
31164      * If this {@link #action} is 'destroy', any server records are ignored and the
31165      * {@link Ext.data.Model#commit} method is not called.
31166      *
31167      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
31168      * the server.
31169      * @markdown
31170      */
31171     commitRecords: function (serverRecords) {
31172         var me = this,
31173             mc, index, clientRecords, serverRec, clientRec;
31174
31175         if (!me.actionSkipSyncRe.test(me.action)) {
31176             clientRecords = me.records;
31177
31178             if (clientRecords && clientRecords.length) {
31179                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
31180                 mc.addAll(clientRecords);
31181
31182                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
31183                     serverRec = serverRecords[index];
31184                     clientRec = mc.get(serverRec.getId());
31185
31186                     if (clientRec) {
31187                         clientRec.beginEdit();
31188                         clientRec.set(serverRec.data);
31189                         clientRec.endEdit(true);
31190                     }
31191                 }
31192
31193                 if (me.actionCommitRecordsRe.test(me.action)) {
31194                     for (index = clientRecords.length; index--; ) {
31195                         clientRecords[index].commit();
31196                     }
31197                 }
31198             }
31199         }
31200     },
31201
31202     /**
31203      * Marks the Operation as started.
31204      */
31205     setStarted: function() {
31206         this.started = true;
31207         this.running = true;
31208     },
31209
31210     /**
31211      * Marks the Operation as completed.
31212      */
31213     setCompleted: function() {
31214         this.complete = true;
31215         this.running  = false;
31216     },
31217
31218     /**
31219      * Marks the Operation as successful.
31220      */
31221     setSuccessful: function() {
31222         this.success = true;
31223     },
31224
31225     /**
31226      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31227      * @param {String/Object} error (optional) error string/object
31228      */
31229     setException: function(error) {
31230         this.exception = true;
31231         this.success = false;
31232         this.running = false;
31233         this.error = error;
31234     },
31235
31236     /**
31237      * Returns true if this Operation encountered an exception (see also {@link #getError})
31238      * @return {Boolean} True if there was an exception
31239      */
31240     hasException: function() {
31241         return this.exception === true;
31242     },
31243
31244     /**
31245      * Returns the error string or object that was set using {@link #setException}
31246      * @return {String/Object} The error object
31247      */
31248     getError: function() {
31249         return this.error;
31250     },
31251
31252     /**
31253      * Returns an array of Ext.data.Model instances as set by the Proxy.
31254      * @return {Ext.data.Model[]} Any loaded Records
31255      */
31256     getRecords: function() {
31257         var resultSet = this.getResultSet();
31258
31259         return (resultSet === undefined ? this.records : resultSet.records);
31260     },
31261
31262     /**
31263      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
31264      * instances as well as meta data such as number of instances fetched, number available etc
31265      * @return {Ext.data.ResultSet} The ResultSet object
31266      */
31267     getResultSet: function() {
31268         return this.resultSet;
31269     },
31270
31271     /**
31272      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
31273      * {@link #isRunning} to test if the Operation is currently running.
31274      * @return {Boolean} True if the Operation has started
31275      */
31276     isStarted: function() {
31277         return this.started === true;
31278     },
31279
31280     /**
31281      * Returns true if the Operation has been started but has not yet completed.
31282      * @return {Boolean} True if the Operation is currently running
31283      */
31284     isRunning: function() {
31285         return this.running === true;
31286     },
31287
31288     /**
31289      * Returns true if the Operation has been completed
31290      * @return {Boolean} True if the Operation is complete
31291      */
31292     isComplete: function() {
31293         return this.complete === true;
31294     },
31295
31296     /**
31297      * Returns true if the Operation has completed and was successful
31298      * @return {Boolean} True if successful
31299      */
31300     wasSuccessful: function() {
31301         return this.isComplete() && this.success === true;
31302     },
31303
31304     /**
31305      * @private
31306      * Associates this Operation with a Batch
31307      * @param {Ext.data.Batch} batch The batch
31308      */
31309     setBatch: function(batch) {
31310         this.batch = batch;
31311     },
31312
31313     /**
31314      * Checks whether this operation should cause writing to occur.
31315      * @return {Boolean} Whether the operation should cause a write to occur.
31316      */
31317     allowWrite: function() {
31318         return this.action != 'read';
31319     }
31320 });
31321 /**
31322  * @author Ed Spencer
31323  *
31324  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
31325  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
31326  */
31327 Ext.define('Ext.data.validations', {
31328     singleton: true,
31329     
31330     /**
31331      * @property {String} presenceMessage
31332      * The default error message used when a presence validation fails.
31333      */
31334     presenceMessage: 'must be present',
31335     
31336     /**
31337      * @property {String} lengthMessage
31338      * The default error message used when a length validation fails.
31339      */
31340     lengthMessage: 'is the wrong length',
31341     
31342     /**
31343      * @property {Boolean} formatMessage
31344      * The default error message used when a format validation fails.
31345      */
31346     formatMessage: 'is the wrong format',
31347     
31348     /**
31349      * @property {String} inclusionMessage
31350      * The default error message used when an inclusion validation fails.
31351      */
31352     inclusionMessage: 'is not included in the list of acceptable values',
31353     
31354     /**
31355      * @property {String} exclusionMessage
31356      * The default error message used when an exclusion validation fails.
31357      */
31358     exclusionMessage: 'is not an acceptable value',
31359     
31360     /**
31361      * @property {String} emailMessage
31362      * The default error message used when an email validation fails
31363      */
31364     emailMessage: 'is not a valid email address',
31365     
31366     /**
31367      * @property {RegExp} emailRe
31368      * The regular expression used to validate email addresses
31369      */
31370     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
31371     
31372     /**
31373      * Validates that the given value is present.
31374      * For example:
31375      *
31376      *     validations: [{type: 'presence', field: 'age'}]
31377      *
31378      * @param {Object} config Config object
31379      * @param {Object} value The value to validate
31380      * @return {Boolean} True if validation passed
31381      */
31382     presence: function(config, value) {
31383         if (value === undefined) {
31384             value = config;
31385         }
31386         
31387         //we need an additional check for zero here because zero is an acceptable form of present data
31388         return !!value || value === 0;
31389     },
31390     
31391     /**
31392      * Returns true if the given value is between the configured min and max values.
31393      * For example:
31394      *
31395      *     validations: [{type: 'length', field: 'name', min: 2}]
31396      *
31397      * @param {Object} config Config object
31398      * @param {String} value The value to validate
31399      * @return {Boolean} True if the value passes validation
31400      */
31401     length: function(config, value) {
31402         if (value === undefined || value === null) {
31403             return false;
31404         }
31405         
31406         var length = value.length,
31407             min    = config.min,
31408             max    = config.max;
31409         
31410         if ((min && length < min) || (max && length > max)) {
31411             return false;
31412         } else {
31413             return true;
31414         }
31415     },
31416     
31417     /**
31418      * Validates that an email string is in the correct format
31419      * @param {Object} config Config object
31420      * @param {String} email The email address
31421      * @return {Boolean} True if the value passes validation
31422      */
31423     email: function(config, email) {
31424         return Ext.data.validations.emailRe.test(email);
31425     },
31426     
31427     /**
31428      * Returns true if the given value passes validation against the configured `matcher` regex.
31429      * For example:
31430      *
31431      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
31432      *
31433      * @param {Object} config Config object
31434      * @param {String} value The value to validate
31435      * @return {Boolean} True if the value passes the format validation
31436      */
31437     format: function(config, value) {
31438         return !!(config.matcher && config.matcher.test(value));
31439     },
31440     
31441     /**
31442      * Validates that the given value is present in the configured `list`.
31443      * For example:
31444      *
31445      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
31446      *
31447      * @param {Object} config Config object
31448      * @param {String} value The value to validate
31449      * @return {Boolean} True if the value is present in the list
31450      */
31451     inclusion: function(config, value) {
31452         return config.list && Ext.Array.indexOf(config.list,value) != -1;
31453     },
31454     
31455     /**
31456      * Validates that the given value is present in the configured `list`.
31457      * For example:
31458      *
31459      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
31460      *
31461      * @param {Object} config Config object
31462      * @param {String} value The value to validate
31463      * @return {Boolean} True if the value is not present in the list
31464      */
31465     exclusion: function(config, value) {
31466         return config.list && Ext.Array.indexOf(config.list,value) == -1;
31467     }
31468 });
31469 /**
31470  * @author Ed Spencer
31471  *
31472  * Simple wrapper class that represents a set of records returned by a Proxy.
31473  */
31474 Ext.define('Ext.data.ResultSet', {
31475     /**
31476      * @cfg {Boolean} loaded
31477      * True if the records have already been loaded. This is only meaningful when dealing with
31478      * SQL-backed proxies.
31479      */
31480     loaded: true,
31481
31482     /**
31483      * @cfg {Number} count
31484      * The number of records in this ResultSet. Note that total may differ from this number.
31485      */
31486     count: 0,
31487
31488     /**
31489      * @cfg {Number} total
31490      * The total number of records reported by the data source. This ResultSet may form a subset of
31491      * those records (see {@link #count}).
31492      */
31493     total: 0,
31494
31495     /**
31496      * @cfg {Boolean} success
31497      * True if the ResultSet loaded successfully, false if any errors were encountered.
31498      */
31499     success: false,
31500
31501     /**
31502      * @cfg {Ext.data.Model[]} records (required)
31503      * The array of record instances.
31504      */
31505
31506     /**
31507      * Creates the resultSet
31508      * @param {Object} [config] Config object.
31509      */
31510     constructor: function(config) {
31511         Ext.apply(this, config);
31512
31513         /**
31514          * @property {Number} totalRecords
31515          * Copy of this.total.
31516          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
31517          */
31518         this.totalRecords = this.total;
31519
31520         if (config.count === undefined) {
31521             this.count = this.records.length;
31522         }
31523     }
31524 });
31525 /**
31526  * @author Ed Spencer
31527  *
31528  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
31529  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
31530  * the Operations.
31531  *
31532  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
31533  * the config options passed to the JsonWriter's constructor.
31534  *
31535  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
31536  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
31537  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
31538  */
31539 Ext.define('Ext.data.writer.Writer', {
31540     alias: 'writer.base',
31541     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
31542     
31543     /**
31544      * @cfg {Boolean} writeAllFields
31545      * True to write all fields from the record to the server. If set to false it will only send the fields that were
31546      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
31547      */
31548     writeAllFields: true,
31549     
31550     /**
31551      * @cfg {String} nameProperty
31552      * This property is used to read the key for each value that will be sent to the server. For example:
31553      *
31554      *     Ext.define('Person', {
31555      *         extend: 'Ext.data.Model',
31556      *         fields: [{
31557      *             name: 'first',
31558      *             mapping: 'firstName'
31559      *         }, {
31560      *             name: 'last',
31561      *             mapping: 'lastName'
31562      *         }, {
31563      *             name: 'age'
31564      *         }]
31565      *     });
31566      *     new Ext.data.writer.Writer({
31567      *         writeAllFields: true,
31568      *         nameProperty: 'mapping'
31569      *     });
31570      *
31571      *     // This will be sent to the server
31572      *     {
31573      *         firstName: 'first name value',
31574      *         lastName: 'last name value',
31575      *         age: 1
31576      *     }
31577      *
31578      * If the value is not present, the field name will always be used.
31579      */
31580     nameProperty: 'name',
31581
31582     /**
31583      * Creates new Writer.
31584      * @param {Object} [config] Config object.
31585      */
31586     constructor: function(config) {
31587         Ext.apply(this, config);
31588     },
31589
31590     /**
31591      * Prepares a Proxy's Ext.data.Request object
31592      * @param {Ext.data.Request} request The request object
31593      * @return {Ext.data.Request} The modified request object
31594      */
31595     write: function(request) {
31596         var operation = request.operation,
31597             records   = operation.records || [],
31598             len       = records.length,
31599             i         = 0,
31600             data      = [];
31601
31602         for (; i < len; i++) {
31603             data.push(this.getRecordData(records[i]));
31604         }
31605         return this.writeRecords(request, data);
31606     },
31607
31608     /**
31609      * Formats the data for each record before sending it to the server. This method should be overridden to format the
31610      * data in a way that differs from the default.
31611      * @param {Object} record The record that we are writing to the server.
31612      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
31613      * the data property on the record.
31614      */
31615     getRecordData: function(record) {
31616         var isPhantom = record.phantom === true,
31617             writeAll = this.writeAllFields || isPhantom,
31618             nameProperty = this.nameProperty,
31619             fields = record.fields,
31620             data = {},
31621             changes,
31622             name,
31623             field,
31624             key;
31625         
31626         if (writeAll) {
31627             fields.each(function(field){
31628                 if (field.persist) {
31629                     name = field[nameProperty] || field.name;
31630                     data[name] = record.get(field.name);
31631                 }
31632             });
31633         } else {
31634             // Only write the changes
31635             changes = record.getChanges();
31636             for (key in changes) {
31637                 if (changes.hasOwnProperty(key)) {
31638                     field = fields.get(key);
31639                     name = field[nameProperty] || field.name;
31640                     data[name] = changes[key];
31641                 }
31642             }
31643             if (!isPhantom) {
31644                 // always include the id for non phantoms
31645                 data[record.idProperty] = record.getId();
31646             }
31647         }
31648         return data;
31649     }
31650 });
31651
31652 /**
31653  * A mixin to add floating capability to a Component.
31654  */
31655 Ext.define('Ext.util.Floating', {
31656
31657     uses: ['Ext.Layer', 'Ext.window.Window'],
31658
31659     /**
31660      * @cfg {Boolean} focusOnToFront
31661      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
31662      * it is {@link #toFront brought to the front}.
31663      */
31664     focusOnToFront: true,
31665
31666     /**
31667      * @cfg {String/Boolean} shadow
31668      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
31669      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
31670      * shadow.
31671      */
31672     shadow: 'sides',
31673
31674     constructor: function(config) {
31675         var me = this;
31676         
31677         me.floating = true;
31678         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
31679             hideMode: me.hideMode,
31680             hidden: me.hidden,
31681             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
31682             shadowOffset: me.shadowOffset,
31683             constrain: false,
31684             shim: me.shim === false ? false : undefined
31685         }), me.el);
31686     },
31687
31688     onFloatRender: function() {
31689         var me = this;
31690         me.zIndexParent = me.getZIndexParent();
31691         me.setFloatParent(me.ownerCt);
31692         delete me.ownerCt;
31693
31694         if (me.zIndexParent) {
31695             me.zIndexParent.registerFloatingItem(me);
31696         } else {
31697             Ext.WindowManager.register(me);
31698         }
31699     },
31700
31701     setFloatParent: function(floatParent) {
31702         var me = this;
31703
31704         // Remove listeners from previous floatParent
31705         if (me.floatParent) {
31706             me.mun(me.floatParent, {
31707                 hide: me.onFloatParentHide,
31708                 show: me.onFloatParentShow,
31709                 scope: me
31710             });
31711         }
31712
31713         me.floatParent = floatParent;
31714
31715         // Floating Components as children of Containers must hide when their parent hides.
31716         if (floatParent) {
31717             me.mon(me.floatParent, {
31718                 hide: me.onFloatParentHide,
31719                 show: me.onFloatParentShow,
31720                 scope: me
31721             });
31722         }
31723
31724         // If a floating Component is configured to be constrained, but has no configured
31725         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
31726         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
31727             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
31728         }
31729     },
31730
31731     onFloatParentHide: function() {
31732         var me = this;
31733         
31734         if (me.hideOnParentHide !== false) {
31735             me.showOnParentShow = me.isVisible();
31736             me.hide();
31737         }
31738     },
31739
31740     onFloatParentShow: function() {
31741         if (this.showOnParentShow) {
31742             delete this.showOnParentShow;
31743             this.show();
31744         }
31745     },
31746
31747     /**
31748      * @private
31749      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
31750      *
31751      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
31752      *
31753      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
31754      * and the global Ext.WindowManager will be used.
31755      */
31756     getZIndexParent: function() {
31757         var p = this.ownerCt,
31758             c;
31759
31760         if (p) {
31761             while (p) {
31762                 c = p;
31763                 p = p.ownerCt;
31764             }
31765             if (c.floating) {
31766                 return c;
31767             }
31768         }
31769     },
31770
31771     // private
31772     // z-index is managed by the zIndexManager and may be overwritten at any time.
31773     // Returns the next z-index to be used.
31774     // If this is a Container, then it will have rebased any managed floating Components,
31775     // and so the next available z-index will be approximately 10000 above that.
31776     setZIndex: function(index) {
31777         var me = this;
31778         me.el.setZIndex(index);
31779
31780         // Next item goes 10 above;
31781         index += 10;
31782
31783         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
31784         // The returned value is a round number approximately 10000 above the last z-index used.
31785         if (me.floatingItems) {
31786             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
31787         }
31788         return index;
31789     },
31790
31791     /**
31792      * Moves this floating Component into a constrain region.
31793      *
31794      * By default, this Component is constrained to be within the container it was added to, or the element it was
31795      * rendered to.
31796      *
31797      * An alternative constraint may be passed.
31798      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
31799      * to be constrained. Defaults to the element into which this floating Component was rendered.
31800      */
31801     doConstrain: function(constrainTo) {
31802         var me = this,
31803             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
31804             xy;
31805
31806         if (vector) {
31807             xy = me.getPosition();
31808             xy[0] += vector[0];
31809             xy[1] += vector[1];
31810             me.setPosition(xy);
31811         }
31812     },
31813
31814
31815     /**
31816      * Gets the x/y offsets to constrain this float
31817      * @private
31818      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
31819      * @return {Number[]} The x/y constraints
31820      */
31821     getConstrainVector: function(constrainTo){
31822         var me = this,
31823             el;
31824
31825         if (me.constrain || me.constrainHeader) {
31826             el = me.constrainHeader ? me.header.el : me.el;
31827             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
31828             return el.getConstrainVector(constrainTo);
31829         }
31830     },
31831
31832     /**
31833      * Aligns this floating Component to the specified element
31834      *
31835      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
31836      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
31837      * omponent instance. If a string id is passed, it will be used as an element id.
31838      * @param {String} [position="tl-bl?"] The position to align to (see {@link
31839      * Ext.Element#alignTo} for more details).
31840      * @param {Number[]} [offsets] Offset the positioning by [x, y]
31841      * @return {Ext.Component} this
31842      */
31843     alignTo: function(element, position, offsets) {
31844         if (element.isComponent) {
31845             element = element.getEl();
31846         }
31847         var xy = this.el.getAlignToXY(element, position, offsets);
31848         this.setPagePosition(xy);
31849         return this;
31850     },
31851
31852     /**
31853      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
31854      * Ext.ZIndexManager ZIndexManager}
31855      *
31856      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
31857      *
31858      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
31859      * @return {Ext.Component} this
31860      */
31861     toFront: function(preventFocus) {
31862         var me = this;
31863
31864         // Find the floating Component which provides the base for this Component's zIndexing.
31865         // That must move to front to then be able to rebase its zIndex stack and move this to the front
31866         if (me.zIndexParent) {
31867             me.zIndexParent.toFront(true);
31868         }
31869         if (me.zIndexManager.bringToFront(me)) {
31870             if (!Ext.isDefined(preventFocus)) {
31871                 preventFocus = !me.focusOnToFront;
31872             }
31873             if (!preventFocus) {
31874                 // Kick off a delayed focus request.
31875                 // If another floating Component is toFronted before the delay expires
31876                 // this will not receive focus.
31877                 me.focus(false, true);
31878             }
31879         }
31880         return me;
31881     },
31882
31883     /**
31884      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
31885      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
31886      *
31887      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
31888      *
31889      * This method also fires the {@link Ext.Component#activate activate} or
31890      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
31891      *
31892      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
31893      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
31894      */
31895     setActive: function(active, newActive) {
31896         var me = this;
31897         
31898         if (active) {
31899             if (me.el.shadow && !me.maximized) {
31900                 me.el.enableShadow(true);
31901             }
31902             me.fireEvent('activate', me);
31903         } else {
31904             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
31905             // can keep their shadows all the time
31906             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
31907                 me.el.disableShadow();
31908             }
31909             me.fireEvent('deactivate', me);
31910         }
31911     },
31912
31913     /**
31914      * Sends this Component to the back of (lower z-index than) any other visible windows
31915      * @return {Ext.Component} this
31916      */
31917     toBack: function() {
31918         this.zIndexManager.sendToBack(this);
31919         return this;
31920     },
31921
31922     /**
31923      * Center this Component in its container.
31924      * @return {Ext.Component} this
31925      */
31926     center: function() {
31927         var me = this,
31928             xy = me.el.getAlignToXY(me.container, 'c-c');
31929         me.setPagePosition(xy);
31930         return me;
31931     },
31932
31933     // private
31934     syncShadow : function(){
31935         if (this.floating) {
31936             this.el.sync(true);
31937         }
31938     },
31939
31940     // private
31941     fitContainer: function() {
31942         var parent = this.floatParent,
31943             container = parent ? parent.getTargetEl() : this.container,
31944             size = container.getViewSize(false);
31945
31946         this.setSize(size);
31947     }
31948 });
31949 /**
31950  * Base Layout class - extended by ComponentLayout and ContainerLayout
31951  */
31952 Ext.define('Ext.layout.Layout', {
31953
31954     /* Begin Definitions */
31955
31956     /* End Definitions */
31957
31958     isLayout: true,
31959     initialized: false,
31960
31961     statics: {
31962         create: function(layout, defaultType) {
31963             var type;
31964             if (layout instanceof Ext.layout.Layout) {
31965                 return Ext.createByAlias('layout.' + layout);
31966             } else {
31967                 if (!layout || typeof layout === 'string') {
31968                     type = layout || defaultType;
31969                     layout = {};                    
31970                 }
31971                 else {
31972                     type = layout.type || defaultType;
31973                 }
31974                 return Ext.createByAlias('layout.' + type, layout || {});
31975             }
31976         }
31977     },
31978
31979     constructor : function(config) {
31980         this.id = Ext.id(null, this.type + '-');
31981         Ext.apply(this, config);
31982     },
31983
31984     /**
31985      * @private
31986      */
31987     layout : function() {
31988         var me = this;
31989         me.layoutBusy = true;
31990         me.initLayout();
31991
31992         if (me.beforeLayout.apply(me, arguments) !== false) {
31993             me.layoutCancelled = false;
31994             me.onLayout.apply(me, arguments);
31995             me.childrenChanged = false;
31996             me.owner.needsLayout = false;
31997             me.layoutBusy = false;
31998             me.afterLayout.apply(me, arguments);
31999         }
32000         else {
32001             me.layoutCancelled = true;
32002         }
32003         me.layoutBusy = false;
32004         me.doOwnerCtLayouts();
32005     },
32006
32007     beforeLayout : function() {
32008         this.renderChildren();
32009         return true;
32010     },
32011
32012     renderChildren: function () {
32013         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
32014     },
32015
32016     /**
32017      * @private
32018      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
32019      * also determines if the items are in the proper place dom.
32020      */
32021     renderItems : function(items, target) {
32022         var me = this,
32023             ln = items.length,
32024             i = 0,
32025             item;
32026
32027         for (; i < ln; i++) {
32028             item = items[i];
32029             if (item && !item.rendered) {
32030                 me.renderItem(item, target, i);
32031             } else if (!me.isValidParent(item, target, i)) {
32032                 me.moveItem(item, target, i);
32033             } else {
32034                 // still need to configure the item, it may have moved in the container.
32035                 me.configureItem(item);
32036             }
32037         }
32038     },
32039
32040     // @private - Validates item is in the proper place in the dom.
32041     isValidParent : function(item, target, position) {
32042         var dom = item.el ? item.el.dom : Ext.getDom(item);
32043         if (dom && target && target.dom) {
32044             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
32045                 return false;
32046             }
32047             return (dom.parentNode == (target.dom || target));
32048         }
32049         return false;
32050     },
32051
32052     /**
32053      * @private
32054      * Renders the given Component into the target Element.
32055      * @param {Ext.Component} item The Component to render
32056      * @param {Ext.Element} target The target Element
32057      * @param {Number} position The position within the target to render the item to
32058      */
32059     renderItem : function(item, target, position) {
32060         var me = this;
32061         if (!item.rendered) {
32062             if (me.itemCls) {
32063                 item.addCls(me.itemCls);
32064             }
32065             if (me.owner.itemCls) {
32066                 item.addCls(me.owner.itemCls);
32067             }
32068             item.render(target, position);
32069             me.configureItem(item);
32070             me.childrenChanged = true;
32071         }
32072     },
32073
32074     /**
32075      * @private
32076      * Moved Component to the provided target instead.
32077      */
32078     moveItem : function(item, target, position) {
32079         // Make sure target is a dom element
32080         target = target.dom || target;
32081         if (typeof position == 'number') {
32082             position = target.childNodes[position];
32083         }
32084         target.insertBefore(item.el.dom, position || null);
32085         item.container = Ext.get(target);
32086         this.configureItem(item);
32087         this.childrenChanged = true;
32088     },
32089
32090     /**
32091      * @private
32092      * Adds the layout's targetCls if necessary and sets
32093      * initialized flag when complete.
32094      */
32095     initLayout : function() {
32096         var me = this,
32097             targetCls = me.targetCls;
32098             
32099         if (!me.initialized && !Ext.isEmpty(targetCls)) {
32100             me.getTarget().addCls(targetCls);
32101         }
32102         me.initialized = true;
32103     },
32104
32105     // @private Sets the layout owner
32106     setOwner : function(owner) {
32107         this.owner = owner;
32108     },
32109
32110     // @private - Returns empty array
32111     getLayoutItems : function() {
32112         return [];
32113     },
32114
32115     /**
32116      * @private
32117      * Applies itemCls
32118      * Empty template method
32119      */
32120     configureItem: Ext.emptyFn,
32121     
32122     // Placeholder empty functions for subclasses to extend
32123     onLayout : Ext.emptyFn,
32124     afterLayout : Ext.emptyFn,
32125     onRemove : Ext.emptyFn,
32126     onDestroy : Ext.emptyFn,
32127     doOwnerCtLayouts : Ext.emptyFn,
32128
32129     /**
32130      * @private
32131      * Removes itemCls
32132      */
32133     afterRemove : function(item) {
32134         var el = item.el,
32135             owner = this.owner,
32136             itemCls = this.itemCls,
32137             ownerCls = owner.itemCls;
32138             
32139         // Clear managed dimensions flag when removed from the layout.
32140         if (item.rendered && !item.isDestroyed) {
32141             if (itemCls) {
32142                 el.removeCls(itemCls);
32143             }
32144             if (ownerCls) {
32145                 el.removeCls(ownerCls);
32146             }
32147         }
32148
32149         // These flags are set at the time a child item is added to a layout.
32150         // The layout must decide if it is managing the item's width, or its height, or both.
32151         // See AbstractComponent for docs on these properties.
32152         delete item.layoutManagedWidth;
32153         delete item.layoutManagedHeight;
32154     },
32155
32156     /**
32157      * Destroys this layout. This is a template method that is empty by default, but should be implemented
32158      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
32159      * @template
32160      */
32161     destroy : function() {
32162         var targetCls = this.targetCls,
32163             target;
32164         
32165         if (!Ext.isEmpty(targetCls)) {
32166             target = this.getTarget();
32167             if (target) {
32168                 target.removeCls(targetCls);
32169             }
32170         }
32171         this.onDestroy();
32172     }
32173 });
32174 /**
32175  * @class Ext.ZIndexManager
32176  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32177  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32178  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
32179  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32180  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32181  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32182  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
32183  * guaranteed to be correct, relative to the Window.</p>
32184  */
32185 Ext.define('Ext.ZIndexManager', {
32186
32187     alternateClassName: 'Ext.WindowGroup',
32188
32189     statics: {
32190         zBase : 9000
32191     },
32192
32193     constructor: function(container) {
32194         var me = this;
32195
32196         me.list = {};
32197         me.zIndexStack = [];
32198         me.front = null;
32199
32200         if (container) {
32201
32202             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32203             if (container.isContainer) {
32204                 container.on('resize', me._onContainerResize, me);
32205                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32206                 // The containing element we will be dealing with (eg masking) is the content target
32207                 me.targetEl = container.getTargetEl();
32208                 me.container = container;
32209             }
32210             // This is the ZIndexManager for a DOM element
32211             else {
32212                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32213                 me.zseed = me.getNextZSeed();
32214                 me.targetEl = Ext.get(container);
32215             }
32216         }
32217         // No container passed means we are the global WindowManager. Our target is the doc body.
32218         // DOM must be ready to collect that ref.
32219         else {
32220             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32221             me.zseed = me.getNextZSeed();
32222             Ext.onDocumentReady(function() {
32223                 me.targetEl = Ext.getBody();
32224             });
32225         }
32226     },
32227
32228     getNextZSeed: function() {
32229         return (Ext.ZIndexManager.zBase += 10000);
32230     },
32231
32232     setBase: function(baseZIndex) {
32233         this.zseed = baseZIndex;
32234         return this.assignZIndices();
32235     },
32236
32237     // private
32238     assignZIndices: function() {
32239         var a = this.zIndexStack,
32240             len = a.length,
32241             i = 0,
32242             zIndex = this.zseed,
32243             comp;
32244
32245         for (; i < len; i++) {
32246             comp = a[i];
32247             if (comp && !comp.hidden) {
32248
32249                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32250                 // that Component.
32251                 // If it's just a plain floating Component such as a BoundList, then the
32252                 // return value is the passed value plus 10, ready for the next item.
32253                 // If a floating *Container* has its zIndex set, it re-orders its managed
32254                 // floating children, starting from that new base, and returns a value 10000 above
32255                 // the highest zIndex which it allocates.
32256                 zIndex = comp.setZIndex(zIndex);
32257             }
32258         }
32259         this._activateLast();
32260         return zIndex;
32261     },
32262
32263     // private
32264     _setActiveChild: function(comp) {
32265         if (comp !== this.front) {
32266
32267             if (this.front) {
32268                 this.front.setActive(false, comp);
32269             }
32270             this.front = comp;
32271             if (comp) {
32272                 comp.setActive(true);
32273                 if (comp.modal) {
32274                     this._showModalMask(comp);
32275                 }
32276             }
32277         }
32278     },
32279
32280     // private
32281     _activateLast: function(justHidden) {
32282         var comp,
32283             lastActivated = false,
32284             i;
32285
32286         // Go down through the z-index stack.
32287         // Activate the next visible one down.
32288         // Keep going down to find the next visible modal one to shift the modal mask down under
32289         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32290             comp = this.zIndexStack[i];
32291             if (!comp.hidden) {
32292                 if (!lastActivated) {
32293                     this._setActiveChild(comp);
32294                     lastActivated = true;
32295                 }
32296
32297                 // Move any modal mask down to just under the next modal floater down the stack
32298                 if (comp.modal) {
32299                     this._showModalMask(comp);
32300                     return;
32301                 }
32302             }
32303         }
32304
32305         // none to activate, so there must be no modal mask.
32306         // And clear the currently active property
32307         this._hideModalMask();
32308         if (!lastActivated) {
32309             this._setActiveChild(null);
32310         }
32311     },
32312
32313     _showModalMask: function(comp) {
32314         var zIndex = comp.el.getStyle('zIndex') - 4,
32315             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
32316             parentBox;
32317         
32318         if (!maskTarget) {
32319             return;
32320         }
32321         
32322         parentBox = maskTarget.getBox();
32323
32324         if (!this.mask) {
32325             this.mask = Ext.getBody().createChild({
32326                 cls: Ext.baseCSSPrefix + 'mask'
32327             });
32328             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
32329             this.mask.on('click', this._onMaskClick, this);
32330         }
32331         if (maskTarget.dom === document.body) {
32332             parentBox.height = Ext.Element.getViewHeight();
32333         }
32334         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
32335         this.mask.setBox(parentBox);
32336         this.mask.setStyle('zIndex', zIndex);
32337         this.mask.show();
32338     },
32339
32340     _hideModalMask: function() {
32341         if (this.mask && this.mask.dom.parentNode) {
32342             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
32343             this.mask.hide();
32344         }
32345     },
32346
32347     _onMaskClick: function() {
32348         if (this.front) {
32349             this.front.focus();
32350         }
32351     },
32352
32353     _onContainerResize: function() {
32354         if (this.mask && this.mask.isVisible()) {
32355             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
32356         }
32357     },
32358
32359     /**
32360      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32361      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32362      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32363      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32364      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32365      * ZIndexManager in the desktop sample app:</p><code><pre>
32366 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32367 </pre></code>
32368      * @param {Ext.Component} comp The Component to register.
32369      */
32370     register : function(comp) {
32371         if (comp.zIndexManager) {
32372             comp.zIndexManager.unregister(comp);
32373         }
32374         comp.zIndexManager = this;
32375
32376         this.list[comp.id] = comp;
32377         this.zIndexStack.push(comp);
32378         comp.on('hide', this._activateLast, this);
32379     },
32380
32381     /**
32382      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32383      * need to be called. Components are automatically unregistered upon destruction.
32384      * See {@link #register}.</p>
32385      * @param {Ext.Component} comp The Component to unregister.
32386      */
32387     unregister : function(comp) {
32388         delete comp.zIndexManager;
32389         if (this.list && this.list[comp.id]) {
32390             delete this.list[comp.id];
32391             comp.un('hide', this._activateLast);
32392             Ext.Array.remove(this.zIndexStack, comp);
32393
32394             // Destruction requires that the topmost visible floater be activated. Same as hiding.
32395             this._activateLast(comp);
32396         }
32397     },
32398
32399     /**
32400      * Gets a registered Component by id.
32401      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
32402      * @return {Ext.Component}
32403      */
32404     get : function(id) {
32405         return typeof id == "object" ? id : this.list[id];
32406     },
32407
32408    /**
32409      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
32410      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32411      * @return {Boolean} True if the dialog was brought to the front, else false
32412      * if it was already in front
32413      */
32414     bringToFront : function(comp) {
32415         comp = this.get(comp);
32416         if (comp !== this.front) {
32417             Ext.Array.remove(this.zIndexStack, comp);
32418             this.zIndexStack.push(comp);
32419             this.assignZIndices();
32420             return true;
32421         }
32422         if (comp.modal) {
32423             this._showModalMask(comp);
32424         }
32425         return false;
32426     },
32427
32428     /**
32429      * Sends the specified Component to the back of other active Components in this ZIndexManager.
32430      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32431      * @return {Ext.Component} The Component
32432      */
32433     sendToBack : function(comp) {
32434         comp = this.get(comp);
32435         Ext.Array.remove(this.zIndexStack, comp);
32436         this.zIndexStack.unshift(comp);
32437         this.assignZIndices();
32438         return comp;
32439     },
32440
32441     /**
32442      * Hides all Components managed by this ZIndexManager.
32443      */
32444     hideAll : function() {
32445         for (var id in this.list) {
32446             if (this.list[id].isComponent && this.list[id].isVisible()) {
32447                 this.list[id].hide();
32448             }
32449         }
32450     },
32451
32452     /**
32453      * @private
32454      * Temporarily hides all currently visible managed Components. This is for when
32455      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
32456      * they should all be hidden just for the duration of the drag.
32457      */
32458     hide: function() {
32459         var i = 0,
32460             ln = this.zIndexStack.length,
32461             comp;
32462
32463         this.tempHidden = [];
32464         for (; i < ln; i++) {
32465             comp = this.zIndexStack[i];
32466             if (comp.isVisible()) {
32467                 this.tempHidden.push(comp);
32468                 comp.hide();
32469             }
32470         }
32471     },
32472
32473     /**
32474      * @private
32475      * Restores temporarily hidden managed Components to visibility.
32476      */
32477     show: function() {
32478         var i = 0,
32479             ln = this.tempHidden.length,
32480             comp,
32481             x,
32482             y;
32483
32484         for (; i < ln; i++) {
32485             comp = this.tempHidden[i];
32486             x = comp.x;
32487             y = comp.y;
32488             comp.show();
32489             comp.setPosition(x, y);
32490         }
32491         delete this.tempHidden;
32492     },
32493
32494     /**
32495      * Gets the currently-active Component in this ZIndexManager.
32496      * @return {Ext.Component} The active Component
32497      */
32498     getActive : function() {
32499         return this.front;
32500     },
32501
32502     /**
32503      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
32504      * The function should accept a single {@link Ext.Component} reference as its only argument and should
32505      * return true if the Component matches the search criteria, otherwise it should return false.
32506      * @param {Function} fn The search function
32507      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
32508      * that gets passed to the function if not specified)
32509      * @return {Array} An array of zero or more matching windows
32510      */
32511     getBy : function(fn, scope) {
32512         var r = [],
32513             i = 0,
32514             len = this.zIndexStack.length,
32515             comp;
32516
32517         for (; i < len; i++) {
32518             comp = this.zIndexStack[i];
32519             if (fn.call(scope||comp, comp) !== false) {
32520                 r.push(comp);
32521             }
32522         }
32523         return r;
32524     },
32525
32526     /**
32527      * Executes the specified function once for every Component in this ZIndexManager, passing each
32528      * Component as the only parameter. Returning false from the function will stop the iteration.
32529      * @param {Function} fn The function to execute for each item
32530      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
32531      */
32532     each : function(fn, scope) {
32533         var comp;
32534         for (var id in this.list) {
32535             comp = this.list[id];
32536             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32537                 return;
32538             }
32539         }
32540     },
32541
32542     /**
32543      * Executes the specified function once for every Component in this ZIndexManager, passing each
32544      * Component as the only parameter. Returning false from the function will stop the iteration.
32545      * The components are passed to the function starting at the bottom and proceeding to the top.
32546      * @param {Function} fn The function to execute for each item
32547      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32548      * is executed. Defaults to the current Component in the iteration.
32549      */
32550     eachBottomUp: function (fn, scope) {
32551         var comp,
32552             stack = this.zIndexStack,
32553             i, n;
32554
32555         for (i = 0, n = stack.length ; i < n; i++) {
32556             comp = stack[i];
32557             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32558                 return;
32559             }
32560         }
32561     },
32562
32563     /**
32564      * Executes the specified function once for every Component in this ZIndexManager, passing each
32565      * Component as the only parameter. Returning false from the function will stop the iteration.
32566      * The components are passed to the function starting at the top and proceeding to the bottom.
32567      * @param {Function} fn The function to execute for each item
32568      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32569      * is executed. Defaults to the current Component in the iteration.
32570      */
32571     eachTopDown: function (fn, scope) {
32572         var comp,
32573             stack = this.zIndexStack,
32574             i;
32575
32576         for (i = stack.length ; i-- > 0; ) {
32577             comp = stack[i];
32578             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32579                 return;
32580             }
32581         }
32582     },
32583
32584     destroy: function() {
32585         this.each(function(c) {
32586             c.destroy();
32587         });
32588         delete this.zIndexStack;
32589         delete this.list;
32590         delete this.container;
32591         delete this.targetEl;
32592     }
32593 }, function() {
32594     /**
32595      * @class Ext.WindowManager
32596      * @extends Ext.ZIndexManager
32597      * <p>The default global floating Component group that is available automatically.</p>
32598      * <p>This manages instances of floating Components which were rendered programatically without
32599      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
32600      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
32601      * there are managed by that ZIndexManager.</p>
32602      * @singleton
32603      */
32604     Ext.WindowManager = Ext.WindowMgr = new this();
32605 });
32606
32607 /**
32608  * @private
32609  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
32610  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
32611  * for its container.
32612  */
32613 Ext.define('Ext.layout.container.boxOverflow.None', {
32614     
32615     alternateClassName: 'Ext.layout.boxOverflow.None',
32616     
32617     constructor: function(layout, config) {
32618         this.layout = layout;
32619         Ext.apply(this, config || {});
32620     },
32621
32622     handleOverflow: Ext.emptyFn,
32623
32624     clearOverflow: Ext.emptyFn,
32625     
32626     onRemove: Ext.emptyFn,
32627
32628     /**
32629      * @private
32630      * Normalizes an item reference, string id or numerical index into a reference to the item
32631      * @param {Ext.Component/String/Number} item The item reference, id or index
32632      * @return {Ext.Component} The item
32633      */
32634     getItem: function(item) {
32635         return this.layout.owner.getComponent(item);
32636     },
32637     
32638     onRemove: Ext.emptyFn
32639 });
32640 /**
32641  * @class Ext.util.KeyMap
32642  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
32643  * The constructor accepts the same config object as defined by {@link #addBinding}.
32644  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
32645  * combination it will call the function with this signature (if the match is a multi-key
32646  * combination the callback will still be called only once): (String key, Ext.EventObject e)
32647  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
32648  * Usage:
32649  <pre><code>
32650 // map one key by key code
32651 var map = new Ext.util.KeyMap("my-element", {
32652     key: 13, // or Ext.EventObject.ENTER
32653     fn: myHandler,
32654     scope: myObject
32655 });
32656
32657 // map multiple keys to one action by string
32658 var map = new Ext.util.KeyMap("my-element", {
32659     key: "a\r\n\t",
32660     fn: myHandler,
32661     scope: myObject
32662 });
32663
32664 // map multiple keys to multiple actions by strings and array of codes
32665 var map = new Ext.util.KeyMap("my-element", [
32666     {
32667         key: [10,13],
32668         fn: function(){ alert("Return was pressed"); }
32669     }, {
32670         key: "abc",
32671         fn: function(){ alert('a, b or c was pressed'); }
32672     }, {
32673         key: "\t",
32674         ctrl:true,
32675         shift:true,
32676         fn: function(){ alert('Control + shift + tab was pressed.'); }
32677     }
32678 ]);
32679 </code></pre>
32680  */
32681 Ext.define('Ext.util.KeyMap', {
32682     alternateClassName: 'Ext.KeyMap',
32683
32684     /**
32685      * Creates new KeyMap.
32686      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
32687      * @param {Object} binding The binding (see {@link #addBinding})
32688      * @param {String} [eventName="keydown"] The event to bind to
32689      */
32690     constructor: function(el, binding, eventName){
32691         var me = this;
32692
32693         Ext.apply(me, {
32694             el: Ext.get(el),
32695             eventName: eventName || me.eventName,
32696             bindings: []
32697         });
32698         if (binding) {
32699             me.addBinding(binding);
32700         }
32701         me.enable();
32702     },
32703
32704     eventName: 'keydown',
32705
32706     /**
32707      * Add a new binding to this KeyMap. The following config object properties are supported:
32708      * <pre>
32709 Property            Type             Description
32710 ----------          ---------------  ----------------------------------------------------------------------
32711 key                 String/Array     A single keycode or an array of keycodes to handle
32712 shift               Boolean          True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
32713 ctrl                Boolean          True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
32714 alt                 Boolean          True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
32715 handler             Function         The function to call when KeyMap finds the expected key combination
32716 fn                  Function         Alias of handler (for backwards-compatibility)
32717 scope               Object           The scope of the callback function
32718 defaultEventAction  String           A default action to apply to the event. Possible values are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
32719 </pre>
32720      *
32721      * Usage:
32722      * <pre><code>
32723 // Create a KeyMap
32724 var map = new Ext.util.KeyMap(document, {
32725     key: Ext.EventObject.ENTER,
32726     fn: handleKey,
32727     scope: this
32728 });
32729
32730 //Add a new binding to the existing KeyMap later
32731 map.addBinding({
32732     key: 'abc',
32733     shift: true,
32734     fn: handleKey,
32735     scope: this
32736 });
32737 </code></pre>
32738      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
32739      */
32740     addBinding : function(binding){
32741         if (Ext.isArray(binding)) {
32742             Ext.each(binding, this.addBinding, this);
32743             return;
32744         }
32745
32746         var keyCode = binding.key,
32747             processed = false,
32748             key,
32749             keys,
32750             keyString,
32751             i,
32752             len;
32753
32754         if (Ext.isString(keyCode)) {
32755             keys = [];
32756             keyString = keyCode.toUpperCase();
32757
32758             for (i = 0, len = keyString.length; i < len; ++i){
32759                 keys.push(keyString.charCodeAt(i));
32760             }
32761             keyCode = keys;
32762             processed = true;
32763         }
32764
32765         if (!Ext.isArray(keyCode)) {
32766             keyCode = [keyCode];
32767         }
32768
32769         if (!processed) {
32770             for (i = 0, len = keyCode.length; i < len; ++i) {
32771                 key = keyCode[i];
32772                 if (Ext.isString(key)) {
32773                     keyCode[i] = key.toUpperCase().charCodeAt(0);
32774                 }
32775             }
32776         }
32777
32778         this.bindings.push(Ext.apply({
32779             keyCode: keyCode
32780         }, binding));
32781     },
32782
32783     /**
32784      * Process any keydown events on the element
32785      * @private
32786      * @param {Ext.EventObject} event
32787      */
32788     handleKeyDown: function(event) {
32789         if (this.enabled) { //just in case
32790             var bindings = this.bindings,
32791                 i = 0,
32792                 len = bindings.length;
32793
32794             event = this.processEvent(event);
32795             for(; i < len; ++i){
32796                 this.processBinding(bindings[i], event);
32797             }
32798         }
32799     },
32800
32801     /**
32802      * Ugly hack to allow this class to be tested. Currently WebKit gives
32803      * no way to raise a key event properly with both
32804      * a) A keycode
32805      * b) The alt/ctrl/shift modifiers
32806      * So we have to simulate them here. Yuk!
32807      * This is a stub method intended to be overridden by tests.
32808      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
32809      * @private
32810      */
32811     processEvent: function(event){
32812         return event;
32813     },
32814
32815     /**
32816      * Process a particular binding and fire the handler if necessary.
32817      * @private
32818      * @param {Object} binding The binding information
32819      * @param {Ext.EventObject} event
32820      */
32821     processBinding: function(binding, event){
32822         if (this.checkModifiers(binding, event)) {
32823             var key = event.getKey(),
32824                 handler = binding.fn || binding.handler,
32825                 scope = binding.scope || this,
32826                 keyCode = binding.keyCode,
32827                 defaultEventAction = binding.defaultEventAction,
32828                 i,
32829                 len,
32830                 keydownEvent = new Ext.EventObjectImpl(event);
32831
32832
32833             for (i = 0, len = keyCode.length; i < len; ++i) {
32834                 if (key === keyCode[i]) {
32835                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
32836                         keydownEvent[defaultEventAction]();
32837                     }
32838                     break;
32839                 }
32840             }
32841         }
32842     },
32843
32844     /**
32845      * Check if the modifiers on the event match those on the binding
32846      * @private
32847      * @param {Object} binding
32848      * @param {Ext.EventObject} event
32849      * @return {Boolean} True if the event matches the binding
32850      */
32851     checkModifiers: function(binding, e){
32852         var keys = ['shift', 'ctrl', 'alt'],
32853             i = 0,
32854             len = keys.length,
32855             val, key;
32856
32857         for (; i < len; ++i){
32858             key = keys[i];
32859             val = binding[key];
32860             if (!(val === undefined || (val === e[key + 'Key']))) {
32861                 return false;
32862             }
32863         }
32864         return true;
32865     },
32866
32867     /**
32868      * Shorthand for adding a single key listener
32869      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
32870      * following options:
32871      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
32872      * @param {Function} fn The function to call
32873      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
32874      */
32875     on: function(key, fn, scope) {
32876         var keyCode, shift, ctrl, alt;
32877         if (Ext.isObject(key) && !Ext.isArray(key)) {
32878             keyCode = key.key;
32879             shift = key.shift;
32880             ctrl = key.ctrl;
32881             alt = key.alt;
32882         } else {
32883             keyCode = key;
32884         }
32885         this.addBinding({
32886             key: keyCode,
32887             shift: shift,
32888             ctrl: ctrl,
32889             alt: alt,
32890             fn: fn,
32891             scope: scope
32892         });
32893     },
32894
32895     /**
32896      * Returns true if this KeyMap is enabled
32897      * @return {Boolean}
32898      */
32899     isEnabled : function(){
32900         return this.enabled;
32901     },
32902
32903     /**
32904      * Enables this KeyMap
32905      */
32906     enable: function(){
32907         var me = this;
32908         
32909         if (!me.enabled) {
32910             me.el.on(me.eventName, me.handleKeyDown, me);
32911             me.enabled = true;
32912         }
32913     },
32914
32915     /**
32916      * Disable this KeyMap
32917      */
32918     disable: function(){
32919         var me = this;
32920         
32921         if (me.enabled) {
32922             me.el.removeListener(me.eventName, me.handleKeyDown, me);
32923             me.enabled = false;
32924         }
32925     },
32926
32927     /**
32928      * Convenience function for setting disabled/enabled by boolean.
32929      * @param {Boolean} disabled
32930      */
32931     setDisabled : function(disabled){
32932         if (disabled) {
32933             this.disable();
32934         } else {
32935             this.enable();
32936         }
32937     },
32938
32939     /**
32940      * Destroys the KeyMap instance and removes all handlers.
32941      * @param {Boolean} removeEl True to also remove the attached element
32942      */
32943     destroy: function(removeEl){
32944         var me = this;
32945
32946         me.bindings = [];
32947         me.disable();
32948         if (removeEl === true) {
32949             me.el.remove();
32950         }
32951         delete me.el;
32952     }
32953 });
32954 /**
32955  * @class Ext.util.ClickRepeater
32956  * @extends Ext.util.Observable
32957  *
32958  * A wrapper class which can be applied to any element. Fires a "click" event while the
32959  * mouse is pressed. The interval between firings may be specified in the config but
32960  * defaults to 20 milliseconds.
32961  *
32962  * Optionally, a CSS class may be applied to the element during the time it is pressed.
32963  *
32964  */
32965 Ext.define('Ext.util.ClickRepeater', {
32966     extend: 'Ext.util.Observable',
32967
32968     /**
32969      * Creates new ClickRepeater.
32970      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
32971      * @param {Object} config (optional) Config object.
32972      */
32973     constructor : function(el, config){
32974         this.el = Ext.get(el);
32975         this.el.unselectable();
32976
32977         Ext.apply(this, config);
32978
32979         this.addEvents(
32980         /**
32981          * @event mousedown
32982          * Fires when the mouse button is depressed.
32983          * @param {Ext.util.ClickRepeater} this
32984          * @param {Ext.EventObject} e
32985          */
32986         "mousedown",
32987         /**
32988          * @event click
32989          * Fires on a specified interval during the time the element is pressed.
32990          * @param {Ext.util.ClickRepeater} this
32991          * @param {Ext.EventObject} e
32992          */
32993         "click",
32994         /**
32995          * @event mouseup
32996          * Fires when the mouse key is released.
32997          * @param {Ext.util.ClickRepeater} this
32998          * @param {Ext.EventObject} e
32999          */
33000         "mouseup"
33001         );
33002
33003         if(!this.disabled){
33004             this.disabled = true;
33005             this.enable();
33006         }
33007
33008         // allow inline handler
33009         if(this.handler){
33010             this.on("click", this.handler,  this.scope || this);
33011         }
33012
33013         this.callParent();
33014     },
33015
33016     /**
33017      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
33018      */
33019
33020     /**
33021      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
33022      */
33023
33024     /**
33025      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
33026      * "interval" and "delay" are ignored.
33027      */
33028
33029     /**
33030      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
33031      */
33032     interval : 20,
33033
33034     /**
33035      * @cfg {Number} delay The initial delay before the repeating event begins firing.
33036      * Similar to an autorepeat key delay.
33037      */
33038     delay: 250,
33039
33040     /**
33041      * @cfg {Boolean} preventDefault True to prevent the default click event
33042      */
33043     preventDefault : true,
33044     /**
33045      * @cfg {Boolean} stopDefault True to stop the default click event
33046      */
33047     stopDefault : false,
33048
33049     timer : 0,
33050
33051     /**
33052      * Enables the repeater and allows events to fire.
33053      */
33054     enable: function(){
33055         if(this.disabled){
33056             this.el.on('mousedown', this.handleMouseDown, this);
33057             if (Ext.isIE){
33058                 this.el.on('dblclick', this.handleDblClick, this);
33059             }
33060             if(this.preventDefault || this.stopDefault){
33061                 this.el.on('click', this.eventOptions, this);
33062             }
33063         }
33064         this.disabled = false;
33065     },
33066
33067     /**
33068      * Disables the repeater and stops events from firing.
33069      */
33070     disable: function(/* private */ force){
33071         if(force || !this.disabled){
33072             clearTimeout(this.timer);
33073             if(this.pressedCls){
33074                 this.el.removeCls(this.pressedCls);
33075             }
33076             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33077             this.el.removeAllListeners();
33078         }
33079         this.disabled = true;
33080     },
33081
33082     /**
33083      * Convenience function for setting disabled/enabled by boolean.
33084      * @param {Boolean} disabled
33085      */
33086     setDisabled: function(disabled){
33087         this[disabled ? 'disable' : 'enable']();
33088     },
33089
33090     eventOptions: function(e){
33091         if(this.preventDefault){
33092             e.preventDefault();
33093         }
33094         if(this.stopDefault){
33095             e.stopEvent();
33096         }
33097     },
33098
33099     // private
33100     destroy : function() {
33101         this.disable(true);
33102         Ext.destroy(this.el);
33103         this.clearListeners();
33104     },
33105
33106     handleDblClick : function(e){
33107         clearTimeout(this.timer);
33108         this.el.blur();
33109
33110         this.fireEvent("mousedown", this, e);
33111         this.fireEvent("click", this, e);
33112     },
33113
33114     // private
33115     handleMouseDown : function(e){
33116         clearTimeout(this.timer);
33117         this.el.blur();
33118         if(this.pressedCls){
33119             this.el.addCls(this.pressedCls);
33120         }
33121         this.mousedownTime = new Date();
33122
33123         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33124         this.el.on("mouseout", this.handleMouseOut, this);
33125
33126         this.fireEvent("mousedown", this, e);
33127         this.fireEvent("click", this, e);
33128
33129         // Do not honor delay or interval if acceleration wanted.
33130         if (this.accelerate) {
33131             this.delay = 400;
33132         }
33133
33134         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33135         // the global shared EventObject gets a new Event put into it before the timer fires.
33136         e = new Ext.EventObjectImpl(e);
33137
33138         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33139     },
33140
33141     // private
33142     click : function(e){
33143         this.fireEvent("click", this, e);
33144         this.timer =  Ext.defer(this.click, this.accelerate ?
33145             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33146                 400,
33147                 -390,
33148                 12000) :
33149             this.interval, this, [e]);
33150     },
33151
33152     easeOutExpo : function (t, b, c, d) {
33153         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33154     },
33155
33156     // private
33157     handleMouseOut : function(){
33158         clearTimeout(this.timer);
33159         if(this.pressedCls){
33160             this.el.removeCls(this.pressedCls);
33161         }
33162         this.el.on("mouseover", this.handleMouseReturn, this);
33163     },
33164
33165     // private
33166     handleMouseReturn : function(){
33167         this.el.un("mouseover", this.handleMouseReturn, this);
33168         if(this.pressedCls){
33169             this.el.addCls(this.pressedCls);
33170         }
33171         this.click();
33172     },
33173
33174     // private
33175     handleMouseUp : function(e){
33176         clearTimeout(this.timer);
33177         this.el.un("mouseover", this.handleMouseReturn, this);
33178         this.el.un("mouseout", this.handleMouseOut, this);
33179         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33180         if(this.pressedCls){
33181             this.el.removeCls(this.pressedCls);
33182         }
33183         this.fireEvent("mouseup", this, e);
33184     }
33185 });
33186
33187 /**
33188  * @class Ext.layout.component.Component
33189  * @extends Ext.layout.Layout
33190  *
33191  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
33192  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
33193  *
33194  * @private
33195  */
33196 Ext.define('Ext.layout.component.Component', {
33197
33198     /* Begin Definitions */
33199
33200     extend: 'Ext.layout.Layout',
33201
33202     /* End Definitions */
33203
33204     type: 'component',
33205
33206     monitorChildren: true,
33207
33208     initLayout : function() {
33209         var me = this,
33210             owner = me.owner,
33211             ownerEl = owner.el;
33212
33213         if (!me.initialized) {
33214             if (owner.frameSize) {
33215                 me.frameSize = owner.frameSize;
33216             }
33217             else {
33218                 owner.frameSize = me.frameSize = {
33219                     top: 0,
33220                     left: 0,
33221                     bottom: 0,
33222                     right: 0
33223                 };
33224             }
33225         }
33226         me.callParent(arguments);
33227     },
33228
33229     beforeLayout : function(width, height, isSetSize, callingContainer) {
33230         this.callParent(arguments);
33231
33232         var me = this,
33233             owner = me.owner,
33234             ownerCt = owner.ownerCt,
33235             layout = owner.layout,
33236             isVisible = owner.isVisible(true),
33237             ownerElChild = owner.el.child,
33238             layoutCollection;
33239
33240         // Cache the size we began with so we can see if there has been any effect.
33241         me.previousComponentSize = me.lastComponentSize;
33242
33243         // Do not allow autoing of any dimensions which are fixed
33244         if (!isSetSize
33245             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
33246                 (!Ext.isNumber(height) && owner.isFixedHeight()))
33247             // unless we are being told to do so by the ownerCt's layout
33248             && callingContainer && callingContainer !== ownerCt) {
33249             
33250             me.doContainerLayout();
33251             return false;
33252         }
33253
33254         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
33255         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
33256         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
33257             if (owner.hiddenAncestor) {
33258                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
33259                 layoutCollection.remove(owner);
33260                 layoutCollection.add(owner);
33261             }
33262             owner.needsLayout = {
33263                 width: width,
33264                 height: height,
33265                 isSetSize: false
33266             };
33267         }
33268
33269         if (isVisible && this.needsLayout(width, height)) {
33270             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
33271         }
33272         else {
33273             return false;
33274         }
33275     },
33276
33277     /**
33278     * Check if the new size is different from the current size and only
33279     * trigger a layout if it is necessary.
33280     * @param {Number} width The new width to set.
33281     * @param {Number} height The new height to set.
33282     */
33283     needsLayout : function(width, height) {
33284         var me = this,
33285             widthBeingChanged,
33286             heightBeingChanged;
33287             me.lastComponentSize = me.lastComponentSize || {
33288                 width: -Infinity,
33289                 height: -Infinity
33290             };
33291
33292         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
33293         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
33294
33295         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
33296         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
33297
33298
33299         // isSizing flag added to prevent redundant layouts when going up the layout chain
33300         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
33301     },
33302
33303     /**
33304     * Set the size of any element supporting undefined, null, and values.
33305     * @param {Number} width The new width to set.
33306     * @param {Number} height The new height to set.
33307     */
33308     setElementSize: function(el, width, height) {
33309         if (width !== undefined && height !== undefined) {
33310             el.setSize(width, height);
33311         }
33312         else if (height !== undefined) {
33313             el.setHeight(height);
33314         }
33315         else if (width !== undefined) {
33316             el.setWidth(width);
33317         }
33318     },
33319
33320     /**
33321      * Returns the owner component's resize element.
33322      * @return {Ext.Element}
33323      */
33324      getTarget : function() {
33325          return this.owner.el;
33326      },
33327
33328     /**
33329      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
33330      * May be overridden in Component layout managers which implement an inner element.
33331      * @return {Ext.Element}
33332      */
33333     getRenderTarget : function() {
33334         return this.owner.el;
33335     },
33336
33337     /**
33338     * Set the size of the target element.
33339     * @param {Number} width The new width to set.
33340     * @param {Number} height The new height to set.
33341     */
33342     setTargetSize : function(width, height) {
33343         var me = this;
33344         me.setElementSize(me.owner.el, width, height);
33345
33346         if (me.owner.frameBody) {
33347             var targetInfo = me.getTargetInfo(),
33348                 padding = targetInfo.padding,
33349                 border = targetInfo.border,
33350                 frameSize = me.frameSize;
33351
33352             me.setElementSize(me.owner.frameBody,
33353                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
33354                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
33355             );
33356         }
33357
33358         me.autoSized = {
33359             width: !Ext.isNumber(width),
33360             height: !Ext.isNumber(height)
33361         };
33362
33363         me.lastComponentSize = {
33364             width: width,
33365             height: height
33366         };
33367     },
33368
33369     getTargetInfo : function() {
33370         if (!this.targetInfo) {
33371             var target = this.getTarget(),
33372                 body = this.owner.getTargetEl();
33373
33374             this.targetInfo = {
33375                 padding: {
33376                     top: target.getPadding('t'),
33377                     right: target.getPadding('r'),
33378                     bottom: target.getPadding('b'),
33379                     left: target.getPadding('l')
33380                 },
33381                 border: {
33382                     top: target.getBorderWidth('t'),
33383                     right: target.getBorderWidth('r'),
33384                     bottom: target.getBorderWidth('b'),
33385                     left: target.getBorderWidth('l')
33386                 },
33387                 bodyMargin: {
33388                     top: body.getMargin('t'),
33389                     right: body.getMargin('r'),
33390                     bottom: body.getMargin('b'),
33391                     left: body.getMargin('l')
33392                 }
33393             };
33394         }
33395         return this.targetInfo;
33396     },
33397
33398     // Start laying out UP the ownerCt's layout when flagged to do so.
33399     doOwnerCtLayouts: function() {
33400         var owner = this.owner,
33401             ownerCt = owner.ownerCt,
33402             ownerCtComponentLayout, ownerCtContainerLayout,
33403             curSize = this.lastComponentSize,
33404             prevSize = this.previousComponentSize,
33405             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
33406             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
33407
33408         // If size has not changed, do not inform upstream layouts
33409         if (!ownerCt || (!widthChange && !heightChange)) {
33410             return;
33411         }
33412
33413         ownerCtComponentLayout = ownerCt.componentLayout;
33414         ownerCtContainerLayout = ownerCt.layout;
33415
33416         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33417             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
33418
33419                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
33420                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
33421                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
33422                     this.isSizing = true;
33423                     ownerCt.doComponentLayout();
33424                     this.isSizing = false;
33425                 }
33426                 // Execute upstream Container layout
33427                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
33428                     ownerCtContainerLayout.layout();
33429                 }
33430             }
33431         }
33432     },
33433
33434     doContainerLayout: function() {
33435         var me = this,
33436             owner = me.owner,
33437             ownerCt = owner.ownerCt,
33438             layout = owner.layout,
33439             ownerCtComponentLayout;
33440
33441         // Run the container layout if it exists (layout for child items)
33442         // **Unless automatic laying out is suspended, or the layout is currently running**
33443         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
33444             layout.layout();
33445         }
33446
33447         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
33448         if (ownerCt && ownerCt.componentLayout) {
33449             ownerCtComponentLayout = ownerCt.componentLayout;
33450             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33451                 ownerCtComponentLayout.childrenChanged = true;
33452             }
33453         }
33454     },
33455
33456     afterLayout : function(width, height, isSetSize, layoutOwner) {
33457         this.doContainerLayout();
33458         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
33459     }
33460 });
33461
33462 /**
33463  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
33464  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
33465  * should not contain any HTML, otherwise it may not be measured correctly.
33466  *
33467  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
33468  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
33469  * provide a **fixed width** when doing the measurement.
33470  *
33471  * If multiple measurements are being done on the same element, you create a new instance to initialize 
33472  * to avoid the overhead of copying the styles to the element repeatedly.
33473  */
33474 Ext.define('Ext.util.TextMetrics', {
33475     statics: {
33476         shared: null,
33477         /**
33478          * Measures the size of the specified text
33479          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
33480          * that can affect the size of the rendered text
33481          * @param {String} text The text to measure
33482          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
33483          * in order to accurately measure the text height
33484          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33485          */
33486         measure: function(el, text, fixedWidth){
33487             var me = this,
33488                 shared = me.shared;
33489             
33490             if(!shared){
33491                 shared = me.shared = new me(el, fixedWidth);
33492             }
33493             shared.bind(el);
33494             shared.setFixedWidth(fixedWidth || 'auto');
33495             return shared.getSize(text);
33496         },
33497         
33498         /**
33499           * Destroy the TextMetrics instance created by {@link #measure}.
33500           */
33501          destroy: function(){
33502              var me = this;
33503              Ext.destroy(me.shared);
33504              me.shared = null;
33505          }
33506     },
33507     
33508     /**
33509      * Creates new TextMetrics.
33510      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
33511      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
33512      */
33513     constructor: function(bindTo, fixedWidth){
33514         var measure = this.measure = Ext.getBody().createChild({
33515             cls: 'x-textmetrics'
33516         });
33517         this.el = Ext.get(bindTo);
33518         
33519         measure.position('absolute');
33520         measure.setLeftTop(-1000, -1000);
33521         measure.hide();
33522
33523         if (fixedWidth) {
33524            measure.setWidth(fixedWidth);
33525         }
33526     },
33527     
33528     /**
33529      * Returns the size of the specified text based on the internal element's style and width properties
33530      * @param {String} text The text to measure
33531      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33532      */
33533     getSize: function(text){
33534         var measure = this.measure,
33535             size;
33536         
33537         measure.update(text);
33538         size = measure.getSize();
33539         measure.update('');
33540         return size;
33541     },
33542     
33543     /**
33544      * Binds this TextMetrics instance to a new element
33545      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
33546      */
33547     bind: function(el){
33548         var me = this;
33549         
33550         me.el = Ext.get(el);
33551         me.measure.setStyle(
33552             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
33553         );
33554     },
33555     
33556     /**
33557      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
33558      * to set a fixed width in order to accurately measure the text height.
33559      * @param {Number} width The width to set on the element
33560      */
33561      setFixedWidth : function(width){
33562          this.measure.setWidth(width);
33563      },
33564      
33565      /**
33566       * Returns the measured width of the specified text
33567       * @param {String} text The text to measure
33568       * @return {Number} width The width in pixels
33569       */
33570      getWidth : function(text){
33571          this.measure.dom.style.width = 'auto';
33572          return this.getSize(text).width;
33573      },
33574      
33575      /**
33576       * Returns the measured height of the specified text
33577       * @param {String} text The text to measure
33578       * @return {Number} height The height in pixels
33579       */
33580      getHeight : function(text){
33581          return this.getSize(text).height;
33582      },
33583      
33584      /**
33585       * Destroy this instance
33586       */
33587      destroy: function(){
33588          var me = this;
33589          me.measure.remove();
33590          delete me.el;
33591          delete me.measure;
33592      }
33593 }, function(){
33594     Ext.Element.addMethods({
33595         /**
33596          * Returns the width in pixels of the passed text, or the width of the text in this Element.
33597          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
33598          * @param {Number} min (optional) The minumum value to return.
33599          * @param {Number} max (optional) The maximum value to return.
33600          * @return {Number} The text width in pixels.
33601          * @member Ext.Element
33602          */
33603         getTextWidth : function(text, min, max){
33604             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
33605         }
33606     });
33607 });
33608
33609 /**
33610  * @class Ext.layout.container.boxOverflow.Scroller
33611  * @extends Ext.layout.container.boxOverflow.None
33612  * @private
33613  */
33614 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
33615
33616     /* Begin Definitions */
33617
33618     extend: 'Ext.layout.container.boxOverflow.None',
33619     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
33620     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
33621     mixins: {
33622         observable: 'Ext.util.Observable'
33623     },
33624     
33625     /* End Definitions */
33626
33627     /**
33628      * @cfg {Boolean} animateScroll
33629      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
33630      */
33631     animateScroll: false,
33632
33633     /**
33634      * @cfg {Number} scrollIncrement
33635      * The number of pixels to scroll by on scroller click
33636      */
33637     scrollIncrement: 20,
33638
33639     /**
33640      * @cfg {Number} wheelIncrement
33641      * The number of pixels to increment on mouse wheel scrolling.
33642      */
33643     wheelIncrement: 10,
33644
33645     /**
33646      * @cfg {Number} scrollRepeatInterval
33647      * Number of milliseconds between each scroll while a scroller button is held down
33648      */
33649     scrollRepeatInterval: 60,
33650
33651     /**
33652      * @cfg {Number} scrollDuration
33653      * Number of milliseconds that each scroll animation lasts
33654      */
33655     scrollDuration: 400,
33656
33657     /**
33658      * @cfg {String} beforeCtCls
33659      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
33660      * which must always be present at the leftmost edge of the Container
33661      */
33662
33663     /**
33664      * @cfg {String} afterCtCls
33665      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
33666      * which must always be present at the rightmost edge of the Container
33667      */
33668
33669     /**
33670      * @cfg {String} [scrollerCls='x-box-scroller']
33671      * CSS class added to both scroller elements if enableScroll is used
33672      */
33673     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
33674
33675     /**
33676      * @cfg {String} beforeScrollerCls
33677      * CSS class added to the left scroller element if enableScroll is used
33678      */
33679
33680     /**
33681      * @cfg {String} afterScrollerCls
33682      * CSS class added to the right scroller element if enableScroll is used
33683      */
33684     
33685     constructor: function(layout, config) {
33686         this.layout = layout;
33687         Ext.apply(this, config || {});
33688         
33689         this.addEvents(
33690             /**
33691              * @event scroll
33692              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
33693              * @param {Number} newPosition The new position of the scroller
33694              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
33695              */
33696             'scroll'
33697         );
33698     },
33699     
33700     initCSSClasses: function() {
33701         var me = this,
33702         layout = me.layout;
33703
33704         if (!me.CSSinitialized) {
33705             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
33706             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
33707             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
33708             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
33709             me.CSSinitializes = true;
33710         }
33711     },
33712
33713     handleOverflow: function(calculations, targetSize) {
33714         var me = this,
33715             layout = me.layout,
33716             methodName = 'get' + layout.parallelPrefixCap,
33717             newSize = {};
33718
33719         me.initCSSClasses();
33720         me.callParent(arguments);
33721         this.createInnerElements();
33722         this.showScrollers();
33723         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
33724         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
33725         return { targetSize: newSize };
33726     },
33727
33728     /**
33729      * @private
33730      * Creates the beforeCt and afterCt elements if they have not already been created
33731      */
33732     createInnerElements: function() {
33733         var me = this,
33734             target = me.layout.getRenderTarget();
33735
33736         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
33737         //special items such as scrollers or dropdown menu triggers
33738         if (!me.beforeCt) {
33739             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
33740             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
33741             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
33742             me.createWheelListener();
33743         }
33744     },
33745
33746     /**
33747      * @private
33748      * Sets up an listener to scroll on the layout's innerCt mousewheel event
33749      */
33750     createWheelListener: function() {
33751         this.layout.innerCt.on({
33752             scope     : this,
33753             mousewheel: function(e) {
33754                 e.stopEvent();
33755
33756                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
33757             }
33758         });
33759     },
33760
33761     /**
33762      * @private
33763      */
33764     clearOverflow: function() {
33765         this.hideScrollers();
33766     },
33767
33768     /**
33769      * @private
33770      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
33771      * present. 
33772      */
33773     showScrollers: function() {
33774         this.createScrollers();
33775         this.beforeScroller.show();
33776         this.afterScroller.show();
33777         this.updateScrollButtons();
33778         
33779         this.layout.owner.addClsWithUI('scroller');
33780     },
33781
33782     /**
33783      * @private
33784      * Hides the scroller elements in the beforeCt and afterCt
33785      */
33786     hideScrollers: function() {
33787         if (this.beforeScroller != undefined) {
33788             this.beforeScroller.hide();
33789             this.afterScroller.hide();
33790             
33791             this.layout.owner.removeClsWithUI('scroller');
33792         }
33793     },
33794
33795     /**
33796      * @private
33797      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
33798      */
33799     createScrollers: function() {
33800         if (!this.beforeScroller && !this.afterScroller) {
33801             var before = this.beforeCt.createChild({
33802                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
33803             });
33804
33805             var after = this.afterCt.createChild({
33806                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
33807             });
33808
33809             before.addClsOnOver(this.beforeScrollerCls + '-hover');
33810             after.addClsOnOver(this.afterScrollerCls + '-hover');
33811
33812             before.setVisibilityMode(Ext.Element.DISPLAY);
33813             after.setVisibilityMode(Ext.Element.DISPLAY);
33814
33815             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
33816                 interval: this.scrollRepeatInterval,
33817                 handler : this.scrollLeft,
33818                 scope   : this
33819             });
33820
33821             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
33822                 interval: this.scrollRepeatInterval,
33823                 handler : this.scrollRight,
33824                 scope   : this
33825             });
33826
33827             /**
33828              * @property beforeScroller
33829              * @type Ext.Element
33830              * The left scroller element. Only created when needed.
33831              */
33832             this.beforeScroller = before;
33833
33834             /**
33835              * @property afterScroller
33836              * @type Ext.Element
33837              * The left scroller element. Only created when needed.
33838              */
33839             this.afterScroller = after;
33840         }
33841     },
33842
33843     /**
33844      * @private
33845      */
33846     destroy: function() {
33847         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
33848     },
33849
33850     /**
33851      * @private
33852      * Scrolls left or right by the number of pixels specified
33853      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
33854      */
33855     scrollBy: function(delta, animate) {
33856         this.scrollTo(this.getScrollPosition() + delta, animate);
33857     },
33858
33859     /**
33860      * @private
33861      * @return {Object} Object passed to scrollTo when scrolling
33862      */
33863     getScrollAnim: function() {
33864         return {
33865             duration: this.scrollDuration, 
33866             callback: this.updateScrollButtons, 
33867             scope   : this
33868         };
33869     },
33870
33871     /**
33872      * @private
33873      * Enables or disables each scroller button based on the current scroll position
33874      */
33875     updateScrollButtons: function() {
33876         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
33877             return;
33878         }
33879
33880         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
33881             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
33882             beforeCls  = this.beforeScrollerCls + '-disabled',
33883             afterCls   = this.afterScrollerCls  + '-disabled';
33884         
33885         this.beforeScroller[beforeMeth](beforeCls);
33886         this.afterScroller[afterMeth](afterCls);
33887         this.scrolling = false;
33888     },
33889
33890     /**
33891      * @private
33892      * Returns true if the innerCt scroll is already at its left-most point
33893      * @return {Boolean} True if already at furthest left point
33894      */
33895     atExtremeBefore: function() {
33896         return this.getScrollPosition() === 0;
33897     },
33898
33899     /**
33900      * @private
33901      * Scrolls to the left by the configured amount
33902      */
33903     scrollLeft: function() {
33904         this.scrollBy(-this.scrollIncrement, false);
33905     },
33906
33907     /**
33908      * @private
33909      * Scrolls to the right by the configured amount
33910      */
33911     scrollRight: function() {
33912         this.scrollBy(this.scrollIncrement, false);
33913     },
33914
33915     /**
33916      * Returns the current scroll position of the innerCt element
33917      * @return {Number} The current scroll position
33918      */
33919     getScrollPosition: function(){
33920         var layout = this.layout;
33921         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
33922     },
33923
33924     /**
33925      * @private
33926      * Returns the maximum value we can scrollTo
33927      * @return {Number} The max scroll value
33928      */
33929     getMaxScrollPosition: function() {
33930         var layout = this.layout;
33931         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
33932     },
33933
33934     /**
33935      * @private
33936      * Returns true if the innerCt scroll is already at its right-most point
33937      * @return {Boolean} True if already at furthest right point
33938      */
33939     atExtremeAfter: function() {
33940         return this.getScrollPosition() >= this.getMaxScrollPosition();
33941     },
33942
33943     /**
33944      * @private
33945      * Scrolls to the given position. Performs bounds checking.
33946      * @param {Number} position The position to scroll to. This is constrained.
33947      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
33948      */
33949     scrollTo: function(position, animate) {
33950         var me = this,
33951             layout = me.layout,
33952             oldPosition = me.getScrollPosition(),
33953             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
33954
33955         if (newPosition != oldPosition && !me.scrolling) {
33956             if (animate == undefined) {
33957                 animate = me.animateScroll;
33958             }
33959
33960             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
33961             if (animate) {
33962                 me.scrolling = true;
33963             } else {
33964                 me.scrolling = false;
33965                 me.updateScrollButtons();
33966             }
33967             
33968             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
33969         }
33970     },
33971
33972     /**
33973      * Scrolls to the given component.
33974      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
33975      * or a reference to the component itself.
33976      * @param {Boolean} animate True to animate the scrolling
33977      */
33978     scrollToItem: function(item, animate) {
33979         var me = this,
33980             layout = me.layout,
33981             visibility,
33982             box,
33983             newPos;
33984
33985         item = me.getItem(item);
33986         if (item != undefined) {
33987             visibility = this.getItemVisibility(item);
33988             if (!visibility.fullyVisible) {
33989                 box  = item.getBox(true, true);
33990                 newPos = box[layout.parallelPosition];
33991                 if (visibility.hiddenEnd) {
33992                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
33993                 }
33994                 this.scrollTo(newPos, animate);
33995             }
33996         }
33997     },
33998
33999     /**
34000      * @private
34001      * For a given item in the container, return an object with information on whether the item is visible
34002      * with the current innerCt scroll value.
34003      * @param {Ext.Component} item The item
34004      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
34005      */
34006     getItemVisibility: function(item) {
34007         var me          = this,
34008             box         = me.getItem(item).getBox(true, true),
34009             layout      = me.layout,
34010             itemStart   = box[layout.parallelPosition],
34011             itemEnd     = itemStart + box[layout.parallelPrefix],
34012             scrollStart = me.getScrollPosition(),
34013             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
34014
34015         return {
34016             hiddenStart : itemStart < scrollStart,
34017             hiddenEnd   : itemEnd > scrollEnd,
34018             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
34019         };
34020     }
34021 });
34022 /**
34023  * @class Ext.util.Offset
34024  * @ignore
34025  */
34026 Ext.define('Ext.util.Offset', {
34027
34028     /* Begin Definitions */
34029
34030     statics: {
34031         fromObject: function(obj) {
34032             return new this(obj.x, obj.y);
34033         }
34034     },
34035
34036     /* End Definitions */
34037
34038     constructor: function(x, y) {
34039         this.x = (x != null && !isNaN(x)) ? x : 0;
34040         this.y = (y != null && !isNaN(y)) ? y : 0;
34041
34042         return this;
34043     },
34044
34045     copy: function() {
34046         return new Ext.util.Offset(this.x, this.y);
34047     },
34048
34049     copyFrom: function(p) {
34050         this.x = p.x;
34051         this.y = p.y;
34052     },
34053
34054     toString: function() {
34055         return "Offset[" + this.x + "," + this.y + "]";
34056     },
34057
34058     equals: function(offset) {
34059
34060         return (this.x == offset.x && this.y == offset.y);
34061     },
34062
34063     round: function(to) {
34064         if (!isNaN(to)) {
34065             var factor = Math.pow(10, to);
34066             this.x = Math.round(this.x * factor) / factor;
34067             this.y = Math.round(this.y * factor) / factor;
34068         } else {
34069             this.x = Math.round(this.x);
34070             this.y = Math.round(this.y);
34071         }
34072     },
34073
34074     isZero: function() {
34075         return this.x == 0 && this.y == 0;
34076     }
34077 });
34078
34079 /**
34080  * @class Ext.util.KeyNav
34081  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
34082  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
34083  * way to implement custom navigation schemes for any UI component.</p>
34084  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
34085  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
34086  <pre><code>
34087 var nav = new Ext.util.KeyNav("my-element", {
34088     "left" : function(e){
34089         this.moveLeft(e.ctrlKey);
34090     },
34091     "right" : function(e){
34092         this.moveRight(e.ctrlKey);
34093     },
34094     "enter" : function(e){
34095         this.save();
34096     },
34097     scope : this
34098 });
34099 </code></pre>
34100  */
34101 Ext.define('Ext.util.KeyNav', {
34102     
34103     alternateClassName: 'Ext.KeyNav',
34104     
34105     requires: ['Ext.util.KeyMap'],
34106     
34107     statics: {
34108         keyOptions: {
34109             left: 37,
34110             right: 39,
34111             up: 38,
34112             down: 40,
34113             space: 32,
34114             pageUp: 33,
34115             pageDown: 34,
34116             del: 46,
34117             backspace: 8,
34118             home: 36,
34119             end: 35,
34120             enter: 13,
34121             esc: 27,
34122             tab: 9
34123         }
34124     },
34125
34126     /**
34127      * Creates new KeyNav.
34128      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34129      * @param {Object} config The config
34130      */
34131     constructor: function(el, config){
34132         this.setConfig(el, config || {});
34133     },
34134     
34135     /**
34136      * Sets up a configuration for the KeyNav.
34137      * @private
34138      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34139      * @param {Object} config A configuration object as specified in the constructor.
34140      */
34141     setConfig: function(el, config) {
34142         if (this.map) {
34143             this.map.destroy();
34144         }
34145         
34146         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
34147             keys = Ext.util.KeyNav.keyOptions,
34148             scope = config.scope || this,
34149             key;
34150         
34151         this.map = map;
34152         for (key in keys) {
34153             if (keys.hasOwnProperty(key)) {
34154                 if (config[key]) {
34155                     map.addBinding({
34156                         scope: scope,
34157                         key: keys[key],
34158                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
34159                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
34160                     });
34161                 }
34162             }
34163         }
34164         
34165         map.disable();
34166         if (!config.disabled) {
34167             map.enable();
34168         }
34169     },
34170     
34171     /**
34172      * Method for filtering out the map argument
34173      * @private
34174      * @param {Ext.util.KeyMap} map
34175      * @param {Ext.EventObject} event
34176      * @param {Object} options Contains the handler to call
34177      */
34178     handleEvent: function(map, event, handler){
34179         return handler.call(this, event);
34180     },
34181     
34182     /**
34183      * @cfg {Boolean} disabled
34184      * True to disable this KeyNav instance.
34185      */
34186     disabled: false,
34187     
34188     /**
34189      * @cfg {String} defaultEventAction
34190      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
34191      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
34192      * {@link Ext.EventObject#stopPropagation}.
34193      */
34194     defaultEventAction: "stopEvent",
34195     
34196     /**
34197      * @cfg {Boolean} forceKeyDown
34198      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
34199      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34200      * handle keydown instead of keypress.
34201      */
34202     forceKeyDown: false,
34203     
34204     /**
34205      * Destroy this KeyNav (this is the same as calling disable).
34206      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34207      */
34208     destroy: function(removeEl){
34209         this.map.destroy(removeEl);
34210         delete this.map;
34211     },
34212
34213     /**
34214      * Enable this KeyNav
34215      */
34216     enable: function() {
34217         this.map.enable();
34218         this.disabled = false;
34219     },
34220
34221     /**
34222      * Disable this KeyNav
34223      */
34224     disable: function() {
34225         this.map.disable();
34226         this.disabled = true;
34227     },
34228     
34229     /**
34230      * Convenience function for setting disabled/enabled by boolean.
34231      * @param {Boolean} disabled
34232      */
34233     setDisabled : function(disabled){
34234         this.map.setDisabled(disabled);
34235         this.disabled = disabled;
34236     },
34237     
34238     /**
34239      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34240      * as well as the useKeyDown option on the EventManager.
34241      * @return {String} The type of event to listen for.
34242      */
34243     getKeyEvent: function(forceKeyDown){
34244         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34245     }
34246 });
34247
34248 /**
34249  * @class Ext.fx.Queue
34250  * Animation Queue mixin to handle chaining and queueing by target.
34251  * @private
34252  */
34253
34254 Ext.define('Ext.fx.Queue', {
34255
34256     requires: ['Ext.util.HashMap'],
34257
34258     constructor: function() {
34259         this.targets = Ext.create('Ext.util.HashMap');
34260         this.fxQueue = {};
34261     },
34262
34263     // @private
34264     getFxDefaults: function(targetId) {
34265         var target = this.targets.get(targetId);
34266         if (target) {
34267             return target.fxDefaults;
34268         }
34269         return {};
34270     },
34271
34272     // @private
34273     setFxDefaults: function(targetId, obj) {
34274         var target = this.targets.get(targetId);
34275         if (target) {
34276             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34277         }
34278     },
34279
34280     // @private
34281     stopAnimation: function(targetId) {
34282         var me = this,
34283             queue = me.getFxQueue(targetId),
34284             ln = queue.length;
34285         while (ln) {
34286             queue[ln - 1].end();
34287             ln--;
34288         }
34289     },
34290
34291     /**
34292      * @private
34293      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34294      */
34295     getActiveAnimation: function(targetId) {
34296         var queue = this.getFxQueue(targetId);
34297         return (queue && !!queue.length) ? queue[0] : false;
34298     },
34299
34300     // @private
34301     hasFxBlock: function(targetId) {
34302         var queue = this.getFxQueue(targetId);
34303         return queue && queue[0] && queue[0].block;
34304     },
34305
34306     // @private get fx queue for passed target, create if needed.
34307     getFxQueue: function(targetId) {
34308         if (!targetId) {
34309             return false;
34310         }
34311         var me = this,
34312             queue = me.fxQueue[targetId],
34313             target = me.targets.get(targetId);
34314
34315         if (!target) {
34316             return false;
34317         }
34318
34319         if (!queue) {
34320             me.fxQueue[targetId] = [];
34321             // GarbageCollector will need to clean up Elements since they aren't currently observable
34322             if (target.type != 'element') {
34323                 target.target.on('destroy', function() {
34324                     me.fxQueue[targetId] = [];
34325                 });
34326             }
34327         }
34328         return me.fxQueue[targetId];
34329     },
34330
34331     // @private
34332     queueFx: function(anim) {
34333         var me = this,
34334             target = anim.target,
34335             queue, ln;
34336
34337         if (!target) {
34338             return;
34339         }
34340
34341         queue = me.getFxQueue(target.getId());
34342         ln = queue.length;
34343
34344         if (ln) {
34345             if (anim.concurrent) {
34346                 anim.paused = false;
34347             }
34348             else {
34349                 queue[ln - 1].on('afteranimate', function() {
34350                     anim.paused = false;
34351                 });
34352             }
34353         }
34354         else {
34355             anim.paused = false;
34356         }
34357         anim.on('afteranimate', function() {
34358             Ext.Array.remove(queue, anim);
34359             if (anim.remove) {
34360                 if (target.type == 'element') {
34361                     var el = Ext.get(target.id);
34362                     if (el) {
34363                         el.remove();
34364                     }
34365                 }
34366             }
34367         }, this);
34368         queue.push(anim);
34369     }
34370 });
34371 /**
34372  * @class Ext.fx.target.Target
34373
34374 This class specifies a generic target for an animation. It provides a wrapper around a
34375 series of different types of objects to allow for a generic animation API.
34376 A target can be a single object or a Composite object containing other objects that are 
34377 to be animated. This class and it's subclasses are generally not created directly, the 
34378 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34379 the instance to be animated.
34380
34381 The following types of objects can be animated:
34382
34383 - {@link Ext.fx.target.Component Components}
34384 - {@link Ext.fx.target.Element Elements}
34385 - {@link Ext.fx.target.Sprite Sprites}
34386
34387  * @markdown
34388  * @abstract
34389  */
34390 Ext.define('Ext.fx.target.Target', {
34391
34392     isAnimTarget: true,
34393
34394     /**
34395      * Creates new Target.
34396      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
34397      */
34398     constructor: function(target) {
34399         this.target = target;
34400         this.id = this.getId();
34401     },
34402     
34403     getId: function() {
34404         return this.target.id;
34405     }
34406 });
34407
34408 /**
34409  * @class Ext.fx.target.Sprite
34410  * @extends Ext.fx.target.Target
34411
34412 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
34413 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
34414 and the appropriate target will be created.
34415
34416  * @markdown
34417  */
34418
34419 Ext.define('Ext.fx.target.Sprite', {
34420
34421     /* Begin Definitions */
34422
34423     extend: 'Ext.fx.target.Target',
34424
34425     /* End Definitions */
34426
34427     type: 'draw',
34428
34429     getFromPrim: function(sprite, attr) {
34430         var o;
34431         if (attr == 'translate') {
34432             o = {
34433                 x: sprite.attr.translation.x || 0,
34434                 y: sprite.attr.translation.y || 0
34435             };
34436         }
34437         else if (attr == 'rotate') {
34438             o = {
34439                 degrees: sprite.attr.rotation.degrees || 0,
34440                 x: sprite.attr.rotation.x,
34441                 y: sprite.attr.rotation.y
34442             };
34443         }
34444         else {
34445             o = sprite.attr[attr];
34446         }
34447         return o;
34448     },
34449
34450     getAttr: function(attr, val) {
34451         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
34452     },
34453
34454     setAttr: function(targetData) {
34455         var ln = targetData.length,
34456             spriteArr = [],
34457             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
34458         for (i = 0; i < ln; i++) {
34459             attrs = targetData[i].attrs;
34460             for (attr in attrs) {
34461                 attrArr = attrs[attr];
34462                 ln2 = attrArr.length;
34463                 for (j = 0; j < ln2; j++) {
34464                     spritePtr = attrArr[j][0];
34465                     attPtr = attrArr[j][1];
34466                     if (attr === 'translate') {
34467                         value = {
34468                             x: attPtr.x,
34469                             y: attPtr.y
34470                         };
34471                     }
34472                     else if (attr === 'rotate') {
34473                         x = attPtr.x;
34474                         if (isNaN(x)) {
34475                             x = null;
34476                         }
34477                         y = attPtr.y;
34478                         if (isNaN(y)) {
34479                             y = null;
34480                         }
34481                         value = {
34482                             degrees: attPtr.degrees,
34483                             x: x,
34484                             y: y
34485                         };
34486                     }
34487                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
34488                         value = parseFloat(attPtr);
34489                     }
34490                     else {
34491                         value = attPtr;
34492                     }
34493                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
34494                     if (idx == -1) {
34495                         spriteArr.push([spritePtr, {}]);
34496                         idx = spriteArr.length - 1;
34497                     }
34498                     spriteArr[idx][1][attr] = value;
34499                 }
34500             }
34501         }
34502         ln = spriteArr.length;
34503         for (i = 0; i < ln; i++) {
34504             spritePtr = spriteArr[i];
34505             spritePtr[0].setAttributes(spritePtr[1]);
34506         }
34507         this.target.redraw();
34508     }
34509 });
34510
34511 /**
34512  * @class Ext.fx.target.CompositeSprite
34513  * @extends Ext.fx.target.Sprite
34514
34515 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
34516 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
34517 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
34518 and the appropriate target will be created.
34519
34520  * @markdown
34521  */
34522
34523 Ext.define('Ext.fx.target.CompositeSprite', {
34524
34525     /* Begin Definitions */
34526
34527     extend: 'Ext.fx.target.Sprite',
34528
34529     /* End Definitions */
34530
34531     getAttr: function(attr, val) {
34532         var out = [],
34533             target = this.target;
34534         target.each(function(sprite) {
34535             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
34536         }, this);
34537         return out;
34538     }
34539 });
34540
34541 /**
34542  * @class Ext.fx.target.Component
34543  * @extends Ext.fx.target.Target
34544  * 
34545  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
34546  * created directly, the {@link Ext.Component} will be passed to the animation and
34547  * and the appropriate target will be created.
34548  */
34549 Ext.define('Ext.fx.target.Component', {
34550
34551     /* Begin Definitions */
34552    
34553     extend: 'Ext.fx.target.Target',
34554     
34555     /* End Definitions */
34556
34557     type: 'component',
34558
34559     // Methods to call to retrieve unspecified "from" values from a target Component
34560     getPropMethod: {
34561         top: function() {
34562             return this.getPosition(true)[1];
34563         },
34564         left: function() {
34565             return this.getPosition(true)[0];
34566         },
34567         x: function() {
34568             return this.getPosition()[0];
34569         },
34570         y: function() {
34571             return this.getPosition()[1];
34572         },
34573         height: function() {
34574             return this.getHeight();
34575         },
34576         width: function() {
34577             return this.getWidth();
34578         },
34579         opacity: function() {
34580             return this.el.getStyle('opacity');
34581         }
34582     },
34583
34584     compMethod: {
34585         top: 'setPosition',
34586         left: 'setPosition',
34587         x: 'setPagePosition',
34588         y: 'setPagePosition',
34589         height: 'setSize',
34590         width: 'setSize',
34591         opacity: 'setOpacity'
34592     },
34593
34594     // Read the named attribute from the target Component. Use the defined getter for the attribute
34595     getAttr: function(attr, val) {
34596         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
34597     },
34598
34599     setAttr: function(targetData, isFirstFrame, isLastFrame) {
34600         var me = this,
34601             target = me.target,
34602             ln = targetData.length,
34603             attrs, attr, o, i, j, meth, targets, left, top, w, h;
34604         for (i = 0; i < ln; i++) {
34605             attrs = targetData[i].attrs;
34606             for (attr in attrs) {
34607                 targets = attrs[attr].length;
34608                 meth = {
34609                     setPosition: {},
34610                     setPagePosition: {},
34611                     setSize: {},
34612                     setOpacity: {}
34613                 };
34614                 for (j = 0; j < targets; j++) {
34615                     o = attrs[attr][j];
34616                     // We REALLY want a single function call, so push these down to merge them: eg
34617                     // meth.setPagePosition.target = <targetComponent>
34618                     // meth.setPagePosition['x'] = 100
34619                     // meth.setPagePosition['y'] = 100
34620                     meth[me.compMethod[attr]].target = o[0];
34621                     meth[me.compMethod[attr]][attr] = o[1];
34622                 }
34623                 if (meth.setPosition.target) {
34624                     o = meth.setPosition;
34625                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
34626                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
34627                     o.target.setPosition(left, top);
34628                 }
34629                 if (meth.setPagePosition.target) {
34630                     o = meth.setPagePosition;
34631                     o.target.setPagePosition(o.x, o.y);
34632                 }
34633                 if (meth.setSize.target && meth.setSize.target.el) {
34634                     o = meth.setSize;
34635                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
34636                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
34637                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
34638
34639                     // Only set the size of the Component on the last frame, or if the animation was
34640                     // configured with dynamic: true.
34641                     // In other cases, we just set the target element size.
34642                     // This will result in either clipping if animating a reduction in size, or the revealing of
34643                     // the inner elements of the Component if animating an increase in size.
34644                     // Component's animate function initially resizes to the larger size before resizing the
34645                     // outer element to clip the contents.
34646                     if (isLastFrame || me.dynamic) {
34647                         o.target.componentLayout.childrenChanged = true;
34648
34649                         // Flag if we are being called by an animating layout: use setCalculatedSize
34650                         if (me.layoutAnimation) {
34651                             o.target.setCalculatedSize(w, h);
34652                         } else {
34653                             o.target.setSize(w, h);
34654                         }
34655                     }
34656                     else {
34657                         o.target.el.setSize(w, h);
34658                     }
34659                 }
34660                 if (meth.setOpacity.target) {
34661                     o = meth.setOpacity;
34662                     o.target.el.setStyle('opacity', o.opacity);
34663                 }
34664             }
34665         }
34666     }
34667 });
34668
34669 /**
34670  * @class Ext.fx.CubicBezier
34671  * @ignore
34672  */
34673 Ext.define('Ext.fx.CubicBezier', {
34674
34675     /* Begin Definitions */
34676
34677     singleton: true,
34678
34679     /* End Definitions */
34680
34681     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
34682         var cx = 3 * p1x,
34683             bx = 3 * (p2x - p1x) - cx,
34684             ax = 1 - cx - bx,
34685             cy = 3 * p1y,
34686             by = 3 * (p2y - p1y) - cy,
34687             ay = 1 - cy - by;
34688         function sampleCurveX(t) {
34689             return ((ax * t + bx) * t + cx) * t;
34690         }
34691         function solve(x, epsilon) {
34692             var t = solveCurveX(x, epsilon);
34693             return ((ay * t + by) * t + cy) * t;
34694         }
34695         function solveCurveX(x, epsilon) {
34696             var t0, t1, t2, x2, d2, i;
34697             for (t2 = x, i = 0; i < 8; i++) {
34698                 x2 = sampleCurveX(t2) - x;
34699                 if (Math.abs(x2) < epsilon) {
34700                     return t2;
34701                 }
34702                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
34703                 if (Math.abs(d2) < 1e-6) {
34704                     break;
34705                 }
34706                 t2 = t2 - x2 / d2;
34707             }
34708             t0 = 0;
34709             t1 = 1;
34710             t2 = x;
34711             if (t2 < t0) {
34712                 return t0;
34713             }
34714             if (t2 > t1) {
34715                 return t1;
34716             }
34717             while (t0 < t1) {
34718                 x2 = sampleCurveX(t2);
34719                 if (Math.abs(x2 - x) < epsilon) {
34720                     return t2;
34721                 }
34722                 if (x > x2) {
34723                     t0 = t2;
34724                 } else {
34725                     t1 = t2;
34726                 }
34727                 t2 = (t1 - t0) / 2 + t0;
34728             }
34729             return t2;
34730         }
34731         return solve(t, 1 / (200 * duration));
34732     },
34733
34734     cubicBezier: function(x1, y1, x2, y2) {
34735         var fn = function(pos) {
34736             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
34737         };
34738         fn.toCSS3 = function() {
34739             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
34740         };
34741         fn.reverse = function() {
34742             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
34743         };
34744         return fn;
34745     }
34746 });
34747 /**
34748  * Represents an RGB color and provides helper functions get
34749  * color components in HSL color space.
34750  */
34751 Ext.define('Ext.draw.Color', {
34752
34753     /* Begin Definitions */
34754
34755     /* End Definitions */
34756
34757     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
34758     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
34759     hexRe: /\s*#([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)\s*/,
34760
34761     /**
34762      * @cfg {Number} lightnessFactor
34763      *
34764      * The default factor to compute the lighter or darker color. Defaults to 0.2.
34765      */
34766     lightnessFactor: 0.2,
34767
34768     /**
34769      * Creates new Color.
34770      * @param {Number} red Red component (0..255)
34771      * @param {Number} green Green component (0..255)
34772      * @param {Number} blue Blue component (0..255)
34773      */
34774     constructor : function(red, green, blue) {
34775         var me = this,
34776             clamp = Ext.Number.constrain;
34777         me.r = clamp(red, 0, 255);
34778         me.g = clamp(green, 0, 255);
34779         me.b = clamp(blue, 0, 255);
34780     },
34781
34782     /**
34783      * Get the red component of the color, in the range 0..255.
34784      * @return {Number}
34785      */
34786     getRed: function() {
34787         return this.r;
34788     },
34789
34790     /**
34791      * Get the green component of the color, in the range 0..255.
34792      * @return {Number}
34793      */
34794     getGreen: function() {
34795         return this.g;
34796     },
34797
34798     /**
34799      * Get the blue component of the color, in the range 0..255.
34800      * @return {Number}
34801      */
34802     getBlue: function() {
34803         return this.b;
34804     },
34805
34806     /**
34807      * Get the RGB values.
34808      * @return {Number[]}
34809      */
34810     getRGB: function() {
34811         var me = this;
34812         return [me.r, me.g, me.b];
34813     },
34814
34815     /**
34816      * Get the equivalent HSL components of the color.
34817      * @return {Number[]}
34818      */
34819     getHSL: function() {
34820         var me = this,
34821             r = me.r / 255,
34822             g = me.g / 255,
34823             b = me.b / 255,
34824             max = Math.max(r, g, b),
34825             min = Math.min(r, g, b),
34826             delta = max - min,
34827             h,
34828             s = 0,
34829             l = 0.5 * (max + min);
34830
34831         // min==max means achromatic (hue is undefined)
34832         if (min != max) {
34833             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
34834             if (r == max) {
34835                 h = 60 * (g - b) / delta;
34836             } else if (g == max) {
34837                 h = 120 + 60 * (b - r) / delta;
34838             } else {
34839                 h = 240 + 60 * (r - g) / delta;
34840             }
34841             if (h < 0) {
34842                 h += 360;
34843             }
34844             if (h >= 360) {
34845                 h -= 360;
34846             }
34847         }
34848         return [h, s, l];
34849     },
34850
34851     /**
34852      * Return a new color that is lighter than this color.
34853      * @param {Number} factor Lighter factor (0..1), default to 0.2
34854      * @return Ext.draw.Color
34855      */
34856     getLighter: function(factor) {
34857         var hsl = this.getHSL();
34858         factor = factor || this.lightnessFactor;
34859         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
34860         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
34861     },
34862
34863     /**
34864      * Return a new color that is darker than this color.
34865      * @param {Number} factor Darker factor (0..1), default to 0.2
34866      * @return Ext.draw.Color
34867      */
34868     getDarker: function(factor) {
34869         factor = factor || this.lightnessFactor;
34870         return this.getLighter(-factor);
34871     },
34872
34873     /**
34874      * Return the color in the hex format, i.e. '#rrggbb'.
34875      * @return {String}
34876      */
34877     toString: function() {
34878         var me = this,
34879             round = Math.round,
34880             r = round(me.r).toString(16),
34881             g = round(me.g).toString(16),
34882             b = round(me.b).toString(16);
34883         r = (r.length == 1) ? '0' + r : r;
34884         g = (g.length == 1) ? '0' + g : g;
34885         b = (b.length == 1) ? '0' + b : b;
34886         return ['#', r, g, b].join('');
34887     },
34888
34889     /**
34890      * Convert a color to hexadecimal format.
34891      *
34892      * **Note:** This method is both static and instance.
34893      *
34894      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
34895      * Can also be an Array, in this case the function handles the first member.
34896      * @returns {String} The color in hexadecimal format.
34897      * @static
34898      */
34899     toHex: function(color) {
34900         if (Ext.isArray(color)) {
34901             color = color[0];
34902         }
34903         if (!Ext.isString(color)) {
34904             return '';
34905         }
34906         if (color.substr(0, 1) === '#') {
34907             return color;
34908         }
34909         var digits = this.colorToHexRe.exec(color);
34910
34911         if (Ext.isArray(digits)) {
34912             var red = parseInt(digits[2], 10),
34913                 green = parseInt(digits[3], 10),
34914                 blue = parseInt(digits[4], 10),
34915                 rgb = blue | (green << 8) | (red << 16);
34916             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
34917         }
34918         else {
34919             return '';
34920         }
34921     },
34922
34923     /**
34924      * Parse the string and create a new color.
34925      *
34926      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
34927      *
34928      * If the string is not recognized, an undefined will be returned instead.
34929      *
34930      * **Note:** This method is both static and instance.
34931      *
34932      * @param {String} str Color in string.
34933      * @returns Ext.draw.Color
34934      * @static
34935      */
34936     fromString: function(str) {
34937         var values, r, g, b,
34938             parse = parseInt;
34939
34940         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
34941             values = str.match(this.hexRe);
34942             if (values) {
34943                 r = parse(values[1], 16) >> 0;
34944                 g = parse(values[2], 16) >> 0;
34945                 b = parse(values[3], 16) >> 0;
34946                 if (str.length == 4) {
34947                     r += (r * 16);
34948                     g += (g * 16);
34949                     b += (b * 16);
34950                 }
34951             }
34952         }
34953         else {
34954             values = str.match(this.rgbRe);
34955             if (values) {
34956                 r = values[1];
34957                 g = values[2];
34958                 b = values[3];
34959             }
34960         }
34961
34962         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
34963     },
34964
34965     /**
34966      * Returns the gray value (0 to 255) of the color.
34967      *
34968      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
34969      *
34970      * @returns {Number}
34971      */
34972     getGrayscale: function() {
34973         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
34974         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
34975     },
34976
34977     /**
34978      * Create a new color based on the specified HSL values.
34979      *
34980      * **Note:** This method is both static and instance.
34981      *
34982      * @param {Number} h Hue component (0..359)
34983      * @param {Number} s Saturation component (0..1)
34984      * @param {Number} l Lightness component (0..1)
34985      * @returns Ext.draw.Color
34986      * @static
34987      */
34988     fromHSL: function(h, s, l) {
34989         var C, X, m, i, rgb = [],
34990             abs = Math.abs,
34991             floor = Math.floor;
34992
34993         if (s == 0 || h == null) {
34994             // achromatic
34995             rgb = [l, l, l];
34996         }
34997         else {
34998             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
34999             // C is the chroma
35000             // X is the second largest component
35001             // m is the lightness adjustment
35002             h /= 60;
35003             C = s * (1 - abs(2 * l - 1));
35004             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
35005             m = l - C / 2;
35006             switch (floor(h)) {
35007                 case 0:
35008                     rgb = [C, X, 0];
35009                     break;
35010                 case 1:
35011                     rgb = [X, C, 0];
35012                     break;
35013                 case 2:
35014                     rgb = [0, C, X];
35015                     break;
35016                 case 3:
35017                     rgb = [0, X, C];
35018                     break;
35019                 case 4:
35020                     rgb = [X, 0, C];
35021                     break;
35022                 case 5:
35023                     rgb = [C, 0, X];
35024                     break;
35025             }
35026             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
35027         }
35028         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
35029     }
35030 }, function() {
35031     var prototype = this.prototype;
35032
35033     //These functions are both static and instance. TODO: find a more elegant way of copying them
35034     this.addStatics({
35035         fromHSL: function() {
35036             return prototype.fromHSL.apply(prototype, arguments);
35037         },
35038         fromString: function() {
35039             return prototype.fromString.apply(prototype, arguments);
35040         },
35041         toHex: function() {
35042             return prototype.toHex.apply(prototype, arguments);
35043         }
35044     });
35045 });
35046
35047 /**
35048  * @class Ext.dd.StatusProxy
35049  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
35050  * default drag proxy used by all Ext.dd components.
35051  */
35052 Ext.define('Ext.dd.StatusProxy', {
35053     animRepair: false,
35054
35055     /**
35056      * Creates new StatusProxy.
35057      * @param {Object} config (optional) Config object.
35058      */
35059     constructor: function(config){
35060         Ext.apply(this, config);
35061         this.id = this.id || Ext.id();
35062         this.proxy = Ext.createWidget('component', {
35063             floating: true,
35064             stateful: false,
35065             id: this.id,
35066             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
35067                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
35068             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
35069             shadow: !config || config.shadow !== false,
35070             renderTo: document.body
35071         });
35072
35073         this.el = this.proxy.el;
35074         this.el.show();
35075         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
35076         this.el.hide();
35077
35078         this.ghost = Ext.get(this.el.dom.childNodes[1]);
35079         this.dropStatus = this.dropNotAllowed;
35080     },
35081     /**
35082      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
35083      * The CSS class to apply to the status element when drop is allowed.
35084      */
35085     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
35086     /**
35087      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
35088      * The CSS class to apply to the status element when drop is not allowed.
35089      */
35090     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
35091
35092     /**
35093      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
35094      * over the current target element.
35095      * @param {String} cssClass The css class for the new drop status indicator image
35096      */
35097     setStatus : function(cssClass){
35098         cssClass = cssClass || this.dropNotAllowed;
35099         if(this.dropStatus != cssClass){
35100             this.el.replaceCls(this.dropStatus, cssClass);
35101             this.dropStatus = cssClass;
35102         }
35103     },
35104
35105     /**
35106      * Resets the status indicator to the default dropNotAllowed value
35107      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
35108      */
35109     reset : function(clearGhost){
35110         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
35111         this.dropStatus = this.dropNotAllowed;
35112         if(clearGhost){
35113             this.ghost.update("");
35114         }
35115     },
35116
35117     /**
35118      * Updates the contents of the ghost element
35119      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
35120      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
35121      */
35122     update : function(html){
35123         if(typeof html == "string"){
35124             this.ghost.update(html);
35125         }else{
35126             this.ghost.update("");
35127             html.style.margin = "0";
35128             this.ghost.dom.appendChild(html);
35129         }
35130         var el = this.ghost.dom.firstChild;
35131         if(el){
35132             Ext.fly(el).setStyle('float', 'none');
35133         }
35134     },
35135
35136     /**
35137      * Returns the underlying proxy {@link Ext.Layer}
35138      * @return {Ext.Layer} el
35139     */
35140     getEl : function(){
35141         return this.el;
35142     },
35143
35144     /**
35145      * Returns the ghost element
35146      * @return {Ext.Element} el
35147      */
35148     getGhost : function(){
35149         return this.ghost;
35150     },
35151
35152     /**
35153      * Hides the proxy
35154      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
35155      */
35156     hide : function(clear) {
35157         this.proxy.hide();
35158         if (clear) {
35159             this.reset(true);
35160         }
35161     },
35162
35163     /**
35164      * Stops the repair animation if it's currently running
35165      */
35166     stop : function(){
35167         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
35168             this.anim.stop();
35169         }
35170     },
35171
35172     /**
35173      * Displays this proxy
35174      */
35175     show : function() {
35176         this.proxy.show();
35177         this.proxy.toFront();
35178     },
35179
35180     /**
35181      * Force the Layer to sync its shadow and shim positions to the element
35182      */
35183     sync : function(){
35184         this.proxy.el.sync();
35185     },
35186
35187     /**
35188      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
35189      * invalid drop operation by the item being dragged.
35190      * @param {Number[]} xy The XY position of the element ([x, y])
35191      * @param {Function} callback The function to call after the repair is complete.
35192      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
35193      */
35194     repair : function(xy, callback, scope){
35195         this.callback = callback;
35196         this.scope = scope;
35197         if (xy && this.animRepair !== false) {
35198             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
35199             this.el.hideUnders(true);
35200             this.anim = this.el.animate({
35201                 duration: this.repairDuration || 500,
35202                 easing: 'ease-out',
35203                 to: {
35204                     x: xy[0],
35205                     y: xy[1]
35206                 },
35207                 stopAnimation: true,
35208                 callback: this.afterRepair,
35209                 scope: this
35210             });
35211         } else {
35212             this.afterRepair();
35213         }
35214     },
35215
35216     // private
35217     afterRepair : function(){
35218         this.hide(true);
35219         if(typeof this.callback == "function"){
35220             this.callback.call(this.scope || this);
35221         }
35222         this.callback = null;
35223         this.scope = null;
35224     },
35225
35226     destroy: function(){
35227         Ext.destroy(this.ghost, this.proxy, this.el);
35228     }
35229 });
35230 /**
35231  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35232  * is primarily used internally for the Panel's drag drop implementation, and
35233  * should never need to be created directly.
35234  * @private
35235  */
35236 Ext.define('Ext.panel.Proxy', {
35237
35238     alternateClassName: 'Ext.dd.PanelProxy',
35239
35240     /**
35241      * Creates new panel proxy.
35242      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
35243      * @param {Object} [config] Config object
35244      */
35245     constructor: function(panel, config){
35246         /**
35247          * @property panel
35248          * @type Ext.panel.Panel
35249          */
35250         this.panel = panel;
35251         this.id = this.panel.id +'-ddproxy';
35252         Ext.apply(this, config);
35253     },
35254
35255     /**
35256      * @cfg {Boolean} insertProxy
35257      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
35258      * Most Panels are not absolute positioned and therefore we need to reserve this space.
35259      */
35260     insertProxy: true,
35261
35262     // private overrides
35263     setStatus: Ext.emptyFn,
35264     reset: Ext.emptyFn,
35265     update: Ext.emptyFn,
35266     stop: Ext.emptyFn,
35267     sync: Ext.emptyFn,
35268
35269     /**
35270      * Gets the proxy's element
35271      * @return {Ext.Element} The proxy's element
35272      */
35273     getEl: function(){
35274         return this.ghost.el;
35275     },
35276
35277     /**
35278      * Gets the proxy's ghost Panel
35279      * @return {Ext.panel.Panel} The proxy's ghost Panel
35280      */
35281     getGhost: function(){
35282         return this.ghost;
35283     },
35284
35285     /**
35286      * Gets the proxy element. This is the element that represents where the
35287      * Panel was before we started the drag operation.
35288      * @return {Ext.Element} The proxy's element
35289      */
35290     getProxy: function(){
35291         return this.proxy;
35292     },
35293
35294     /**
35295      * Hides the proxy
35296      */
35297     hide : function(){
35298         if (this.ghost) {
35299             if (this.proxy) {
35300                 this.proxy.remove();
35301                 delete this.proxy;
35302             }
35303
35304             // Unghost the Panel, do not move the Panel to where the ghost was
35305             this.panel.unghost(null, false);
35306             delete this.ghost;
35307         }
35308     },
35309
35310     /**
35311      * Shows the proxy
35312      */
35313     show: function(){
35314         if (!this.ghost) {
35315             var panelSize = this.panel.getSize();
35316             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
35317             this.ghost = this.panel.ghost();
35318             if (this.insertProxy) {
35319                 // bc Panels aren't absolute positioned we need to take up the space
35320                 // of where the panel previously was
35321                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35322                 this.proxy.setSize(panelSize);
35323             }
35324         }
35325     },
35326
35327     // private
35328     repair: function(xy, callback, scope) {
35329         this.hide();
35330         if (typeof callback == "function") {
35331             callback.call(scope || this);
35332         }
35333     },
35334
35335     /**
35336      * Moves the proxy to a different position in the DOM.  This is typically
35337      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35338      * location.
35339      * @param {HTMLElement} parentNode The proxy's parent DOM node
35340      * @param {HTMLElement} [before] The sibling node before which the
35341      * proxy should be inserted (defaults to the parent's last child if not
35342      * specified)
35343      */
35344     moveProxy : function(parentNode, before){
35345         if (this.proxy) {
35346             parentNode.insertBefore(this.proxy.dom, before);
35347         }
35348     }
35349 });
35350 /**
35351  * @class Ext.layout.component.AbstractDock
35352  * @extends Ext.layout.component.Component
35353  * @private
35354  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35355  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35356  * an AutoContainerLayout in which this panel get his height based of the CSS or
35357  * or its content.
35358  */
35359
35360 Ext.define('Ext.layout.component.AbstractDock', {
35361
35362     /* Begin Definitions */
35363
35364     extend: 'Ext.layout.component.Component',
35365
35366     /* End Definitions */
35367
35368     type: 'dock',
35369
35370     /**
35371      * @private
35372      * @property autoSizing
35373      * @type Boolean
35374      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35375      */
35376     autoSizing: true,
35377
35378     beforeLayout: function() {
35379         var returnValue = this.callParent(arguments);
35380         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35381             this.handleItemBorders();
35382             this.initializedBorders = true;
35383         }
35384         return returnValue;
35385     },
35386     
35387     handleItemBorders: function() {
35388         var owner = this.owner,
35389             body = owner.body,
35390             docked = this.getLayoutItems(),
35391             borders = {
35392                 top: [],
35393                 right: [],
35394                 bottom: [],
35395                 left: []
35396             },
35397             oldBorders = this.borders,
35398             opposites = {
35399                 top: 'bottom',
35400                 right: 'left',
35401                 bottom: 'top',
35402                 left: 'right'
35403             },
35404             i, ln, item, dock, side;
35405
35406         for (i = 0, ln = docked.length; i < ln; i++) {
35407             item = docked[i];
35408             dock = item.dock;
35409             
35410             if (item.ignoreBorderManagement) {
35411                 continue;
35412             }
35413             
35414             if (!borders[dock].satisfied) {
35415                 borders[dock].push(item);
35416                 borders[dock].satisfied = true;
35417             }
35418             
35419             if (!borders.top.satisfied && opposites[dock] !== 'top') {
35420                 borders.top.push(item);
35421             }
35422             if (!borders.right.satisfied && opposites[dock] !== 'right') {
35423                 borders.right.push(item);
35424             }            
35425             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
35426                 borders.bottom.push(item);
35427             }            
35428             if (!borders.left.satisfied && opposites[dock] !== 'left') {
35429                 borders.left.push(item);
35430             }
35431         }
35432
35433         if (oldBorders) {
35434             for (side in oldBorders) {
35435                 if (oldBorders.hasOwnProperty(side)) {
35436                     ln = oldBorders[side].length;
35437                     if (!owner.manageBodyBorders) {
35438                         for (i = 0; i < ln; i++) {
35439                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35440                         }
35441                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
35442                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35443                         }                    
35444                     }
35445                     else if (oldBorders[side].satisfied) {
35446                         body.setStyle('border-' + side + '-width', '');
35447                     }
35448                 }
35449             }
35450         }
35451                 
35452         for (side in borders) {
35453             if (borders.hasOwnProperty(side)) {
35454                 ln = borders[side].length;
35455                 if (!owner.manageBodyBorders) {
35456                     for (i = 0; i < ln; i++) {
35457                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35458                     }
35459                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
35460                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35461                     }                    
35462                 }
35463                 else if (borders[side].satisfied) {
35464                     body.setStyle('border-' + side + '-width', '1px');
35465                 }
35466             }
35467         }
35468         
35469         this.borders = borders;
35470     },
35471     
35472     /**
35473      * @protected
35474      * @param {Ext.Component} owner The Panel that owns this DockLayout
35475      * @param {Ext.Element} target The target in which we are going to render the docked items
35476      * @param {Array} args The arguments passed to the ComponentLayout.layout method
35477      */
35478     onLayout: function(width, height) {
35479         if (this.onLayout_running) {
35480             return;
35481         }
35482         this.onLayout_running = true;
35483         var me = this,
35484             owner = me.owner,
35485             body = owner.body,
35486             layout = owner.layout,
35487             target = me.getTarget(),
35488             autoWidth = false,
35489             autoHeight = false,
35490             padding, border, frameSize;
35491
35492         // We start of by resetting all the layouts info
35493         var info = me.info = {
35494             boxes: [],
35495             size: {
35496                 width: width,
35497                 height: height
35498             },
35499             bodyBox: {}
35500         };
35501         // Clear isAutoDock flag
35502         delete layout.isAutoDock;
35503
35504         Ext.applyIf(info, me.getTargetInfo());
35505
35506         // We need to bind to the ownerCt whenever we do not have a user set height or width.
35507         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
35508             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
35509                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
35510             }
35511             else {
35512                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
35513             }
35514         }
35515
35516         // Determine if we have an autoHeight or autoWidth.
35517         if (height == null || width == null) {
35518             padding = info.padding;
35519             border = info.border;
35520             frameSize = me.frameSize;
35521
35522             // Auto-everything, clear out any style height/width and read from css
35523             if ((height == null) && (width == null)) {
35524                 autoHeight = true;
35525                 autoWidth = true;
35526                 me.setTargetSize(null);
35527                 me.setBodyBox({width: null, height: null});
35528             }
35529             // Auto-height
35530             else if (height == null) {
35531                 autoHeight = true;
35532                 // Clear any sizing that we already set in a previous layout
35533                 me.setTargetSize(width);
35534                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
35535             // Auto-width
35536             }
35537             else {
35538                 autoWidth = true;
35539                 // Clear any sizing that we already set in a previous layout
35540                 me.setTargetSize(null, height);
35541                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
35542             }
35543
35544             // Run the container
35545             if (layout && layout.isLayout) {
35546                 // Auto-Sized so have the container layout notify the component layout.
35547                 layout.bindToOwnerCtComponent = true;
35548                 // Set flag so we don't do a redundant container layout
35549                 layout.isAutoDock = layout.autoSize !== true;
35550                 layout.layout();
35551
35552                 // If this is an autosized container layout, then we must compensate for a
35553                 // body that is being autosized.  We do not want to adjust the body's size
35554                 // to accommodate the dock items, but rather we will want to adjust the
35555                 // target's size.
35556                 //
35557                 // This is necessary because, particularly in a Box layout, all child items
35558                 // are set with absolute dimensions that are not flexible to the size of its
35559                 // innerCt/target.  So once they are laid out, they are sized for good. By
35560                 // shrinking the body box to accommodate dock items, we're merely cutting off
35561                 // parts of the body.  Not good.  Instead, the target's size should expand
35562                 // to fit the dock items in.  This is valid because the target container is
35563                 // suppose to be autosized to fit everything accordingly.
35564                 info.autoSizedCtLayout = layout.autoSize === true;
35565                 info.autoHeight = autoHeight;
35566                 info.autoWidth = autoWidth;
35567             }
35568
35569             // The dockItems method will add all the top and bottom docked items height
35570             // to the info.panelSize height. That's why we have to call setSize after
35571             // we dock all the items to actually set the panel's width and height.
35572             // We have to do this because the panel body and docked items will be position
35573             // absolute which doesn't stretch the panel.
35574             me.dockItems();
35575             me.setTargetSize(info.size.width, info.size.height);
35576         }
35577         else {
35578             me.setTargetSize(width, height);
35579             me.dockItems();
35580         }
35581         me.callParent(arguments);
35582         this.onLayout_running = false;
35583     },
35584
35585     /**
35586      * @protected
35587      * This method will first update all the information about the docked items,
35588      * body dimensions and position, the panel's total size. It will then
35589      * set all these values on the docked items and panel body.
35590      * @param {Array} items Array containing all the docked items
35591      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
35592      * AutoContainerLayout
35593      */
35594     dockItems : function() {
35595         this.calculateDockBoxes();
35596
35597         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
35598         // information about the body, panel size, and boxes for docked items
35599         // inside a property called info.
35600         var info = this.info,
35601             autoWidth = info.autoWidth,
35602             autoHeight = info.autoHeight,
35603             boxes = info.boxes,
35604             ln = boxes.length,
35605             dock, i, item;
35606
35607         // We are going to loop over all the boxes that were calculated
35608         // and set the position of each item the box belongs to.
35609         for (i = 0; i < ln; i++) {
35610             dock = boxes[i];
35611             item = dock.item;
35612             item.setPosition(dock.x, dock.y);
35613             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
35614                 // Auto-Sized so have the container layout notify the component layout.
35615                 item.layout.bindToOwnerCtComponent = true;
35616             }
35617         }
35618
35619         // Don't adjust body width/height if the target is using an auto container layout.
35620         // But, we do want to adjust the body size if the container layout is auto sized.
35621         if (!info.autoSizedCtLayout) {
35622             if (autoWidth) {
35623                 info.bodyBox.width = null;
35624             }
35625             if (autoHeight) {
35626                 info.bodyBox.height = null;
35627             }
35628         }
35629
35630         // If the bodyBox has been adjusted because of the docked items
35631         // we will update the dimensions and position of the panel's body.
35632         this.setBodyBox(info.bodyBox);
35633     },
35634
35635     /**
35636      * @protected
35637      * This method will set up some initial information about the panel size and bodybox
35638      * and then loop over all the items you pass it to take care of stretching, aligning,
35639      * dock position and all calculations involved with adjusting the body box.
35640      * @param {Array} items Array containing all the docked items we have to layout
35641      */
35642     calculateDockBoxes : function() {
35643         if (this.calculateDockBoxes_running) {
35644             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
35645             return;
35646         }
35647         this.calculateDockBoxes_running = true;
35648         // We want to use the Panel's el width, and the Panel's body height as the initial
35649         // size we are going to use in calculateDockBoxes. We also want to account for
35650         // the border of the panel.
35651         var me = this,
35652             target = me.getTarget(),
35653             items = me.getLayoutItems(),
35654             owner = me.owner,
35655             bodyEl = owner.body,
35656             info = me.info,
35657             autoWidth = info.autoWidth,
35658             autoHeight = info.autoHeight,
35659             size = info.size,
35660             ln = items.length,
35661             padding = info.padding,
35662             border = info.border,
35663             frameSize = me.frameSize,
35664             item, i, box, rect;
35665
35666         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
35667         // around the height of the body and the width of the panel.
35668         if (autoHeight) {
35669             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
35670         }
35671         else {
35672             size.height = target.getHeight();
35673         }
35674         if (autoWidth) {
35675             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
35676         }
35677         else {
35678             size.width = target.getWidth();
35679         }
35680
35681         info.bodyBox = {
35682             x: padding.left + frameSize.left,
35683             y: padding.top + frameSize.top,
35684             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
35685             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
35686         };
35687
35688         // Loop over all the docked items
35689         for (i = 0; i < ln; i++) {
35690             item = items[i];
35691             // The initBox method will take care of stretching and alignment
35692             // In some cases it will also layout the dock items to be able to
35693             // get a width or height measurement
35694             box = me.initBox(item);
35695
35696             if (autoHeight === true) {
35697                 box = me.adjustAutoBox(box, i);
35698             }
35699             else {
35700                 box = me.adjustSizedBox(box, i);
35701             }
35702
35703             // Save our box. This allows us to loop over all docked items and do all
35704             // calculations first. Then in one loop we will actually size and position
35705             // all the docked items that have changed.
35706             info.boxes.push(box);
35707         }
35708         this.calculateDockBoxes_running = false;
35709     },
35710
35711     /**
35712      * @protected
35713      * This method will adjust the position of the docked item and adjust the body box
35714      * accordingly.
35715      * @param {Object} box The box containing information about the width and height
35716      * of this docked item
35717      * @param {Number} index The index position of this docked item
35718      * @return {Object} The adjusted box
35719      */
35720     adjustSizedBox : function(box, index) {
35721         var bodyBox = this.info.bodyBox,
35722             frameSize = this.frameSize,
35723             info = this.info,
35724             padding = info.padding,
35725             pos = box.type,
35726             border = info.border;
35727
35728         switch (pos) {
35729             case 'top':
35730                 box.y = bodyBox.y;
35731                 break;
35732
35733             case 'left':
35734                 box.x = bodyBox.x;
35735                 break;
35736
35737             case 'bottom':
35738                 box.y = (bodyBox.y + bodyBox.height) - box.height;
35739                 break;
35740
35741             case 'right':
35742                 box.x = (bodyBox.x + bodyBox.width) - box.width;
35743                 break;
35744         }
35745
35746         if (box.ignoreFrame) {
35747             if (pos == 'bottom') {
35748                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35749             }
35750             else {
35751                 box.y -= (frameSize.top + padding.top + border.top);
35752             }
35753             if (pos == 'right') {
35754                 box.x += (frameSize.right + padding.right + border.right);
35755             }
35756             else {
35757                 box.x -= (frameSize.left + padding.left + border.left);
35758             }
35759         }
35760
35761         // If this is not an overlaying docked item, we have to adjust the body box
35762         if (!box.overlay) {
35763             switch (pos) {
35764                 case 'top':
35765                     bodyBox.y += box.height;
35766                     bodyBox.height -= box.height;
35767                     break;
35768
35769                 case 'left':
35770                     bodyBox.x += box.width;
35771                     bodyBox.width -= box.width;
35772                     break;
35773
35774                 case 'bottom':
35775                     bodyBox.height -= box.height;
35776                     break;
35777
35778                 case 'right':
35779                     bodyBox.width -= box.width;
35780                     break;
35781             }
35782         }
35783         return box;
35784     },
35785
35786     /**
35787      * @protected
35788      * This method will adjust the position of the docked item inside an AutoContainerLayout
35789      * and adjust the body box accordingly.
35790      * @param {Object} box The box containing information about the width and height
35791      * of this docked item
35792      * @param {Number} index The index position of this docked item
35793      * @return {Object} The adjusted box
35794      */
35795     adjustAutoBox : function (box, index) {
35796         var info = this.info,
35797             owner = this.owner,
35798             bodyBox = info.bodyBox,
35799             size = info.size,
35800             boxes = info.boxes,
35801             boxesLn = boxes.length,
35802             pos = box.type,
35803             frameSize = this.frameSize,
35804             padding = info.padding,
35805             border = info.border,
35806             autoSizedCtLayout = info.autoSizedCtLayout,
35807             ln = (boxesLn < index) ? boxesLn : index,
35808             i, adjustBox;
35809
35810         if (pos == 'top' || pos == 'bottom') {
35811             // This can affect the previously set left and right and bottom docked items
35812             for (i = 0; i < ln; i++) {
35813                 adjustBox = boxes[i];
35814                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
35815                     adjustBox.height += box.height;
35816                 }
35817                 else if (adjustBox.type == 'bottom') {
35818                     adjustBox.y += box.height;
35819                 }
35820             }
35821         }
35822
35823         switch (pos) {
35824             case 'top':
35825                 box.y = bodyBox.y;
35826                 if (!box.overlay) {
35827                     bodyBox.y += box.height;
35828                     if (info.autoHeight) {
35829                         size.height += box.height;
35830                     } else {
35831                         bodyBox.height -= box.height;
35832                     }
35833                 }
35834                 break;
35835
35836             case 'bottom':
35837                 if (!box.overlay) {
35838                     if (info.autoHeight) {
35839                         size.height += box.height;
35840                     } else {
35841                         bodyBox.height -= box.height;
35842                     }
35843                 }
35844                 box.y = (bodyBox.y + bodyBox.height);
35845                 break;
35846
35847             case 'left':
35848                 box.x = bodyBox.x;
35849                 if (!box.overlay) {
35850                     bodyBox.x += box.width;
35851                     if (info.autoWidth) {
35852                         size.width += box.width;
35853                     } else {
35854                         bodyBox.width -= box.width;
35855                     }
35856                 }
35857                 break;
35858
35859             case 'right':
35860                 if (!box.overlay) {
35861                     if (info.autoWidth) {
35862                         size.width += box.width;
35863                     } else {
35864                         bodyBox.width -= box.width;
35865                     }
35866                 }
35867                 box.x = (bodyBox.x + bodyBox.width);
35868                 break;
35869         }
35870
35871         if (box.ignoreFrame) {
35872             if (pos == 'bottom') {
35873                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35874             }
35875             else {
35876                 box.y -= (frameSize.top + padding.top + border.top);
35877             }
35878             if (pos == 'right') {
35879                 box.x += (frameSize.right + padding.right + border.right);
35880             }
35881             else {
35882                 box.x -= (frameSize.left + padding.left + border.left);
35883             }
35884         }
35885         return box;
35886     },
35887
35888     /**
35889      * @protected
35890      * This method will create a box object, with a reference to the item, the type of dock
35891      * (top, left, bottom, right). It will also take care of stretching and aligning of the
35892      * docked items.
35893      * @param {Ext.Component} item The docked item we want to initialize the box for
35894      * @return {Object} The initial box containing width and height and other useful information
35895      */
35896     initBox : function(item) {
35897         var me = this,
35898             bodyBox = me.info.bodyBox,
35899             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
35900             owner = me.owner,
35901             frameSize = me.frameSize,
35902             info = me.info,
35903             padding = info.padding,
35904             border = info.border,
35905             box = {
35906                 item: item,
35907                 overlay: item.overlay,
35908                 type: item.dock,
35909                 offsets: Ext.Element.parseBox(item.offsets || {}),
35910                 ignoreFrame: item.ignoreParentFrame
35911             };
35912         // First we are going to take care of stretch and align properties for all four dock scenarios.
35913         if (item.stretch !== false) {
35914             box.stretched = true;
35915             if (horizontal) {
35916                 box.x = bodyBox.x + box.offsets.left;
35917                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
35918                 if (box.ignoreFrame) {
35919                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
35920                 }
35921                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
35922             }
35923             else {
35924                 box.y = bodyBox.y + box.offsets.top;
35925                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
35926                 if (box.ignoreFrame) {
35927                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
35928                 }
35929                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
35930
35931                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
35932                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
35933                 if (!Ext.supports.ComputedStyle) {
35934                     item.el.repaint();
35935                 }
35936             }
35937         }
35938         else {
35939             item.doComponentLayout();
35940             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
35941             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
35942             box.y += box.offsets.top;
35943             if (horizontal) {
35944                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
35945                 box.x += box.offsets.left;
35946             }
35947         }
35948
35949         // If we haven't calculated the width or height of the docked item yet
35950         // do so, since we need this for our upcoming calculations
35951         if (box.width === undefined) {
35952             box.width = item.getWidth() + item.el.getMargin('lr');
35953         }
35954         if (box.height === undefined) {
35955             box.height = item.getHeight() + item.el.getMargin('tb');
35956         }
35957
35958         return box;
35959     },
35960
35961     /**
35962      * @protected
35963      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
35964      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
35965      */
35966     getLayoutItems : function() {
35967         var it = this.owner.getDockedItems(),
35968             ln = it.length,
35969             i = 0,
35970             result = [];
35971         for (; i < ln; i++) {
35972             if (it[i].isVisible(true)) {
35973                 result.push(it[i]);
35974             }
35975         }
35976         return result;
35977     },
35978
35979     /**
35980      * @protected
35981      * Render the top and left docked items before any existing DOM nodes in our render target,
35982      * and then render the right and bottom docked items after. This is important, for such things
35983      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
35984      * Our collection of docked items will already be ordered via Panel.getDockedItems().
35985      */
35986     renderItems: function(items, target) {
35987         var cns = target.dom.childNodes,
35988             cnsLn = cns.length,
35989             ln = items.length,
35990             domLn = 0,
35991             i, j, cn, item;
35992
35993         // Calculate the number of DOM nodes in our target that are not our docked items
35994         for (i = 0; i < cnsLn; i++) {
35995             cn = Ext.get(cns[i]);
35996             for (j = 0; j < ln; j++) {
35997                 item = items[j];
35998                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
35999                     break;
36000                 }
36001             }
36002
36003             if (j === ln) {
36004                 domLn++;
36005             }
36006         }
36007
36008         // Now we go through our docked items and render/move them
36009         for (i = 0, j = 0; i < ln; i++, j++) {
36010             item = items[i];
36011
36012             // If we're now at the right/bottom docked item, we jump ahead in our
36013             // DOM position, just past the existing DOM nodes.
36014             //
36015             // TODO: This is affected if users provide custom weight values to their
36016             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
36017             // sort operation here, for now, in the name of performance. getDockedItems()
36018             // needs the sort operation not just for this layout-time rendering, but
36019             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
36020             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
36021                 j += domLn;
36022             }
36023
36024             // Same logic as Layout.renderItems()
36025             if (item && !item.rendered) {
36026                 this.renderItem(item, target, j);
36027             }
36028             else if (!this.isValidParent(item, target, j)) {
36029                 this.moveItem(item, target, j);
36030             }
36031         }
36032     },
36033
36034     /**
36035      * @protected
36036      * This function will be called by the dockItems method. Since the body is positioned absolute,
36037      * we need to give it dimensions and a position so that it is in the middle surrounded by
36038      * docked items
36039      * @param {Object} box An object containing new x, y, width and height values for the
36040      * Panel's body
36041      */
36042     setBodyBox : function(box) {
36043         var me = this,
36044             owner = me.owner,
36045             body = owner.body,
36046             info = me.info,
36047             bodyMargin = info.bodyMargin,
36048             padding = info.padding,
36049             border = info.border,
36050             frameSize = me.frameSize;
36051         
36052         // Panel collapse effectively hides the Panel's body, so this is a no-op.
36053         if (owner.collapsed) {
36054             return;
36055         }
36056         
36057         if (Ext.isNumber(box.width)) {
36058             box.width -= bodyMargin.left + bodyMargin.right;
36059         }
36060         
36061         if (Ext.isNumber(box.height)) {
36062             box.height -= bodyMargin.top + bodyMargin.bottom;
36063         }
36064         
36065         me.setElementSize(body, box.width, box.height);
36066         if (Ext.isNumber(box.x)) {
36067             body.setLeft(box.x - padding.left - frameSize.left);
36068         }
36069         if (Ext.isNumber(box.y)) {
36070             body.setTop(box.y - padding.top - frameSize.top);
36071         }
36072     },
36073
36074     /**
36075      * @protected
36076      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
36077      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
36078      * An example of a class added to a dock: right item is x-docked-right
36079      * @param {Ext.Component} item The item we are configuring
36080      */
36081     configureItem : function(item, pos) {
36082         this.callParent(arguments);
36083         if (item.dock == 'top' || item.dock == 'bottom') {
36084             item.layoutManagedWidth = 1;
36085             item.layoutManagedHeight = 2;
36086         } else {
36087             item.layoutManagedWidth = 2;
36088             item.layoutManagedHeight = 1;
36089         }
36090         
36091         item.addCls(Ext.baseCSSPrefix + 'docked');
36092         item.addClsWithUI('docked-' + item.dock);
36093     },
36094
36095     afterRemove : function(item) {
36096         this.callParent(arguments);
36097         if (this.itemCls) {
36098             item.el.removeCls(this.itemCls + '-' + item.dock);
36099         }
36100         var dom = item.el.dom;
36101
36102         if (!item.destroying && dom) {
36103             dom.parentNode.removeChild(dom);
36104         }
36105         this.childrenChanged = true;
36106     }
36107 });
36108 /**
36109  * @class Ext.util.Memento
36110  * This class manages a set of captured properties from an object. These captured properties
36111  * can later be restored to an object.
36112  */
36113 Ext.define('Ext.util.Memento', function () {
36114
36115     function captureOne (src, target, prop) {
36116         src[prop] = target[prop];
36117     }
36118
36119     function removeOne (src, target, prop) {
36120         delete src[prop];
36121     }
36122
36123     function restoreOne (src, target, prop) {
36124         var value = src[prop];
36125         if (value || src.hasOwnProperty(prop)) {
36126             restoreValue(target, prop, value);
36127         }
36128     }
36129
36130     function restoreValue (target, prop, value) {
36131         if (Ext.isDefined(value)) {
36132             target[prop] = value;
36133         } else {
36134             delete target[prop];
36135         }
36136     }
36137
36138     function doMany (doOne, src, target, props) {
36139         if (src) {
36140             if (Ext.isArray(props)) {
36141                 Ext.each(props, function (prop) {
36142                     doOne(src, target, prop);
36143                 });
36144             } else {
36145                 doOne(src, target, props);
36146             }
36147         }
36148     }
36149
36150     return {
36151         /**
36152          * @property data
36153          * The collection of captured properties.
36154          * @private
36155          */
36156         data: null,
36157
36158         /**
36159          * @property target
36160          * The default target object for capture/restore (passed to the constructor).
36161          */
36162         target: null,
36163
36164         /**
36165          * Creates a new memento and optionally captures properties from the target object.
36166          * @param {Object} target The target from which to capture properties. If specified in the
36167          * constructor, this target becomes the default target for all other operations.
36168          * @param {String/String[]} props The property or array of properties to capture.
36169          */
36170         constructor: function (target, props) {
36171             if (target) {
36172                 this.target = target;
36173                 if (props) {
36174                     this.capture(props);
36175                 }
36176             }
36177         },
36178
36179         /**
36180          * Captures the specified properties from the target object in this memento.
36181          * @param {String/String[]} props The property or array of properties to capture.
36182          * @param {Object} target The object from which to capture properties.
36183          */
36184         capture: function (props, target) {
36185             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
36186         },
36187
36188         /**
36189          * Removes the specified properties from this memento. These properties will not be
36190          * restored later without re-capturing their values.
36191          * @param {String/String[]} props The property or array of properties to remove.
36192          */
36193         remove: function (props) {
36194             doMany(removeOne, this.data, null, props);
36195         },
36196
36197         /**
36198          * Restores the specified properties from this memento to the target object.
36199          * @param {String/String[]} props The property or array of properties to restore.
36200          * @param {Boolean} clear True to remove the restored properties from this memento or
36201          * false to keep them (default is true).
36202          * @param {Object} target The object to which to restore properties.
36203          */
36204         restore: function (props, clear, target) {
36205             doMany(restoreOne, this.data, target || this.target, props);
36206             if (clear !== false) {
36207                 this.remove(props);
36208             }
36209         },
36210
36211         /**
36212          * Restores all captured properties in this memento to the target object.
36213          * @param {Boolean} clear True to remove the restored properties from this memento or
36214          * false to keep them (default is true).
36215          * @param {Object} target The object to which to restore properties.
36216          */
36217         restoreAll: function (clear, target) {
36218             var me = this,
36219                 t = target || this.target;
36220
36221             Ext.Object.each(me.data, function (prop, value) {
36222                 restoreValue(t, prop, value);
36223             });
36224
36225             if (clear !== false) {
36226                 delete me.data;
36227             }
36228         }
36229     };
36230 }());
36231
36232 /**
36233  * @class Ext.app.EventBus
36234  * @private
36235  */
36236 Ext.define('Ext.app.EventBus', {
36237     requires: [
36238         'Ext.util.Event'
36239     ],
36240     mixins: {
36241         observable: 'Ext.util.Observable'
36242     },
36243
36244     constructor: function() {
36245         this.mixins.observable.constructor.call(this);
36246
36247         this.bus = {};
36248
36249         var me = this;
36250         Ext.override(Ext.Component, {
36251             fireEvent: function(ev) {
36252                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
36253                     return me.dispatch.call(me, ev, this, arguments);
36254                 }
36255                 return false;
36256             }
36257         });
36258     },
36259
36260     dispatch: function(ev, target, args) {
36261         var bus = this.bus,
36262             selectors = bus[ev],
36263             selector, controllers, id, events, event, i, ln;
36264
36265         if (selectors) {
36266             // Loop over all the selectors that are bound to this event
36267             for (selector in selectors) {
36268                 // Check if the target matches the selector
36269                 if (target.is(selector)) {
36270                     // Loop over all the controllers that are bound to this selector
36271                     controllers = selectors[selector];
36272                     for (id in controllers) {
36273                         // Loop over all the events that are bound to this selector on this controller
36274                         events = controllers[id];
36275                         for (i = 0, ln = events.length; i < ln; i++) {
36276                             event = events[i];
36277                             // Fire the event!
36278                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
36279                                 return false;
36280                             };
36281                         }
36282                     }
36283                 }
36284             }
36285         }
36286     },
36287
36288     control: function(selectors, listeners, controller) {
36289         var bus = this.bus,
36290             selector, fn;
36291
36292         if (Ext.isString(selectors)) {
36293             selector = selectors;
36294             selectors = {};
36295             selectors[selector] = listeners;
36296             this.control(selectors, null, controller);
36297             return;
36298         }
36299
36300         Ext.Object.each(selectors, function(selector, listeners) {
36301             Ext.Object.each(listeners, function(ev, listener) {
36302                 var options = {},
36303                     scope = controller,
36304                     event = Ext.create('Ext.util.Event', controller, ev);
36305
36306                 // Normalize the listener
36307                 if (Ext.isObject(listener)) {
36308                     options = listener;
36309                     listener = options.fn;
36310                     scope = options.scope || controller;
36311                     delete options.fn;
36312                     delete options.scope;
36313                 }
36314
36315                 event.addListener(listener, scope, options);
36316
36317                 // Create the bus tree if it is not there yet
36318                 bus[ev] = bus[ev] || {};
36319                 bus[ev][selector] = bus[ev][selector] || {};
36320                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
36321
36322                 // Push our listener in our bus
36323                 bus[ev][selector][controller.id].push(event);
36324             });
36325         });
36326     }
36327 });
36328 /**
36329  * @class Ext.data.Types
36330  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
36331  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
36332  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
36333  * of this class.</p>
36334  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
36335  * each type definition must contain three properties:</p>
36336  * <div class="mdetail-params"><ul>
36337  * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
36338  * to be stored in the Field. The function is passed the collowing parameters:
36339  * <div class="mdetail-params"><ul>
36340  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36341  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
36342  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
36343  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
36344  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
36345  * </ul></div></div></li>
36346  * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
36347  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
36348  * </ul></div>
36349  * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
36350  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
36351  *<pre><code>
36352 // Add a new Field data type which stores a VELatLong object in the Record.
36353 Ext.data.Types.VELATLONG = {
36354     convert: function(v, data) {
36355         return new VELatLong(data.lat, data.long);
36356     },
36357     sortType: function(v) {
36358         return v.Latitude;  // When sorting, order by latitude
36359     },
36360     type: 'VELatLong'
36361 };
36362 </code></pre>
36363  * <p>Then, when declaring a Model, use: <pre><code>
36364 var types = Ext.data.Types; // allow shorthand type access
36365 Ext.define('Unit',
36366     extend: 'Ext.data.Model',
36367     fields: [
36368         { name: 'unitName', mapping: 'UnitName' },
36369         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
36370         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
36371         { name: 'longitude', mapping: 'long', type: types.FLOAT },
36372         { name: 'position', type: types.VELATLONG }
36373     ]
36374 });
36375 </code></pre>
36376  * @singleton
36377  */
36378 Ext.define('Ext.data.Types', {
36379     singleton: true,
36380     requires: ['Ext.data.SortTypes']
36381 }, function() {
36382     var st = Ext.data.SortTypes;
36383
36384     Ext.apply(Ext.data.Types, {
36385         /**
36386          * @property {RegExp} stripRe
36387          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
36388          * This should be overridden for localization.
36389          */
36390         stripRe: /[\$,%]/g,
36391
36392         /**
36393          * @property {Object} AUTO
36394          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
36395          */
36396         AUTO: {
36397             convert: function(v) {
36398                 return v;
36399             },
36400             sortType: st.none,
36401             type: 'auto'
36402         },
36403
36404         /**
36405          * @property {Object} STRING
36406          * This data type means that the raw data is converted into a String before it is placed into a Record.
36407          */
36408         STRING: {
36409             convert: function(v) {
36410                 var defaultValue = this.useNull ? null : '';
36411                 return (v === undefined || v === null) ? defaultValue : String(v);
36412             },
36413             sortType: st.asUCString,
36414             type: 'string'
36415         },
36416
36417         /**
36418          * @property {Object} INT
36419          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36420          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
36421          */
36422         INT: {
36423             convert: function(v) {
36424                 return v !== undefined && v !== null && v !== '' ?
36425                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36426             },
36427             sortType: st.none,
36428             type: 'int'
36429         },
36430
36431         /**
36432          * @property {Object} FLOAT
36433          * This data type means that the raw data is converted into a number before it is placed into a Record.
36434          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
36435          */
36436         FLOAT: {
36437             convert: function(v) {
36438                 return v !== undefined && v !== null && v !== '' ?
36439                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36440             },
36441             sortType: st.none,
36442             type: 'float'
36443         },
36444
36445         /**
36446          * @property {Object} BOOL
36447          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36448          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36449          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
36450          */
36451         BOOL: {
36452             convert: function(v) {
36453                 if (this.useNull && (v === undefined || v === null || v === '')) {
36454                     return null;
36455                 }
36456                 return v === true || v === 'true' || v == 1;
36457             },
36458             sortType: st.none,
36459             type: 'bool'
36460         },
36461
36462         /**
36463          * @property {Object} DATE
36464          * This data type means that the raw data is converted into a Date before it is placed into a Record.
36465          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
36466          * being applied.
36467          */
36468         DATE: {
36469             convert: function(v) {
36470                 var df = this.dateFormat,
36471                     parsed;
36472
36473                 if (!v) {
36474                     return null;
36475                 }
36476                 if (Ext.isDate(v)) {
36477                     return v;
36478                 }
36479                 if (df) {
36480                     if (df == 'timestamp') {
36481                         return new Date(v*1000);
36482                     }
36483                     if (df == 'time') {
36484                         return new Date(parseInt(v, 10));
36485                     }
36486                     return Ext.Date.parse(v, df);
36487                 }
36488
36489                 parsed = Date.parse(v);
36490                 return parsed ? new Date(parsed) : null;
36491             },
36492             sortType: st.asDate,
36493             type: 'date'
36494         }
36495     });
36496
36497     Ext.apply(Ext.data.Types, {
36498         /**
36499          * @property {Object} BOOLEAN
36500          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36501          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36502          * <p>The synonym <code>BOOL</code> is equivalent.</p>
36503          */
36504         BOOLEAN: this.BOOL,
36505
36506         /**
36507          * @property {Object} INTEGER
36508          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36509          * <p>The synonym <code>INT</code> is equivalent.</p>
36510          */
36511         INTEGER: this.INT,
36512
36513         /**
36514          * @property {Object} NUMBER
36515          * This data type means that the raw data is converted into a number before it is placed into a Record.
36516          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
36517          */
36518         NUMBER: this.FLOAT
36519     });
36520 });
36521
36522 /**
36523  * @author Ed Spencer
36524  *
36525  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
36526  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
36527  * Ext.data.Model Model}. For example, we might set up a model like this:
36528  *
36529  *     Ext.define('User', {
36530  *         extend: 'Ext.data.Model',
36531  *         fields: [
36532  *             'name', 'email',
36533  *             {name: 'age', type: 'int'},
36534  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36535  *         ]
36536  *     });
36537  *
36538  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
36539  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
36540  * up with the 'auto' type. It's as if we'd done this instead:
36541  *
36542  *     Ext.define('User', {
36543  *         extend: 'Ext.data.Model',
36544  *         fields: [
36545  *             {name: 'name', type: 'auto'},
36546  *             {name: 'email', type: 'auto'},
36547  *             {name: 'age', type: 'int'},
36548  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36549  *         ]
36550  *     });
36551  *
36552  * # Types and conversion
36553  *
36554  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
36555  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
36556  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
36557  *
36558  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
36559  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
36560  *
36561  *     Ext.define('User', {
36562  *         extend: 'Ext.data.Model',
36563  *         fields: [
36564  *             'name', 'email',
36565  *             {name: 'age', type: 'int'},
36566  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
36567  *
36568  *             {
36569  *                 name: 'firstName',
36570  *                 convert: function(value, record) {
36571  *                     var fullName  = record.get('name'),
36572  *                         splits    = fullName.split(" "),
36573  *                         firstName = splits[0];
36574  *
36575  *                     return firstName;
36576  *                 }
36577  *             }
36578  *         ]
36579  *     });
36580  *
36581  * Now when we create a new User, the firstName is populated automatically based on the name:
36582  *
36583  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
36584  *
36585  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
36586  *
36587  * In fact, if we log out all of the data inside ed, we'll see this:
36588  *
36589  *     console.log(ed.data);
36590  *
36591  *     //outputs this:
36592  *     {
36593  *         age: 0,
36594  *         email: "",
36595  *         firstName: "Ed",
36596  *         gender: "Unknown",
36597  *         name: "Ed Spencer"
36598  *     }
36599  *
36600  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
36601  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
36602  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
36603  *
36604  *     ed.set('gender', 'Male');
36605  *     ed.get('gender'); //returns 'Male'
36606  *
36607  *     ed.set('age', 25.4);
36608  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
36609  */
36610 Ext.define('Ext.data.Field', {
36611     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
36612     alias: 'data.field',
36613     
36614     constructor : function(config) {
36615         if (Ext.isString(config)) {
36616             config = {name: config};
36617         }
36618         Ext.apply(this, config);
36619         
36620         var types = Ext.data.Types,
36621             st = this.sortType,
36622             t;
36623
36624         if (this.type) {
36625             if (Ext.isString(this.type)) {
36626                 this.type = types[this.type.toUpperCase()] || types.AUTO;
36627             }
36628         } else {
36629             this.type = types.AUTO;
36630         }
36631
36632         // named sortTypes are supported, here we look them up
36633         if (Ext.isString(st)) {
36634             this.sortType = Ext.data.SortTypes[st];
36635         } else if(Ext.isEmpty(st)) {
36636             this.sortType = this.type.sortType;
36637         }
36638
36639         if (!this.convert) {
36640             this.convert = this.type.convert;
36641         }
36642     },
36643     
36644     /**
36645      * @cfg {String} name
36646      *
36647      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
36648      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
36649      *
36650      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
36651      * just a String for the field name.
36652      */
36653     
36654     /**
36655      * @cfg {String/Object} type
36656      *
36657      * The data type for automatic conversion from received data to the *stored* value if
36658      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
36659      * Possible values are
36660      *
36661      * - auto (Default, implies no conversion)
36662      * - string
36663      * - int
36664      * - float
36665      * - boolean
36666      * - date
36667      *
36668      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
36669      *
36670      * Developers may create their own application-specific data types by defining new members of the {@link
36671      * Ext.data.Types} class.
36672      */
36673     
36674     /**
36675      * @cfg {Function} convert
36676      *
36677      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
36678      * It is passed the following parameters:
36679      *
36680      * - **v** : Mixed
36681      *
36682      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
36683      *   defaultValue}`.
36684      *
36685      * - **rec** : Ext.data.Model
36686      *
36687      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
36688      *   at this point as the fields are read in the order that they are defined in your
36689      *   {@link Ext.data.Model#fields fields} array.
36690      *
36691      * Example of convert functions:
36692      *
36693      *     function fullName(v, record){
36694      *         return record.name.last + ', ' + record.name.first;
36695      *     }
36696      *
36697      *     function location(v, record){
36698      *         return !record.city ? '' : (record.city + ', ' + record.state);
36699      *     }
36700      *
36701      *     Ext.define('Dude', {
36702      *         extend: 'Ext.data.Model',
36703      *         fields: [
36704      *             {name: 'fullname',  convert: fullName},
36705      *             {name: 'firstname', mapping: 'name.first'},
36706      *             {name: 'lastname',  mapping: 'name.last'},
36707      *             {name: 'city', defaultValue: 'homeless'},
36708      *             'state',
36709      *             {name: 'location',  convert: location}
36710      *         ]
36711      *     });
36712      *
36713      *     // create the data store
36714      *     var store = Ext.create('Ext.data.Store', {
36715      *         reader: {
36716      *             type: 'json',
36717      *             model: 'Dude',
36718      *             idProperty: 'key',
36719      *             root: 'daRoot',
36720      *             totalProperty: 'total'
36721      *         }
36722      *     });
36723      *
36724      *     var myData = [
36725      *         { key: 1,
36726      *           name: { first: 'Fat',    last:  'Albert' }
36727      *           // notice no city, state provided in data object
36728      *         },
36729      *         { key: 2,
36730      *           name: { first: 'Barney', last:  'Rubble' },
36731      *           city: 'Bedrock', state: 'Stoneridge'
36732      *         },
36733      *         { key: 3,
36734      *           name: { first: 'Cliff',  last:  'Claven' },
36735      *           city: 'Boston',  state: 'MA'
36736      *         }
36737      *     ];
36738      */
36739
36740     /**
36741      * @cfg {String} dateFormat
36742      *
36743      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
36744      *
36745      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
36746      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
36747      * timestamp. See {@link Ext.Date}.
36748      */
36749     dateFormat: null,
36750     
36751     /**
36752      * @cfg {Boolean} useNull
36753      *
36754      * Use when converting received data into a Number type (either int or float). If the value cannot be
36755      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
36756      */
36757     useNull: false,
36758     
36759     /**
36760      * @cfg {Object} defaultValue
36761      *
36762      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
36763      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
36764      * (i.e. undefined). Defaults to "".
36765      */
36766     defaultValue: "",
36767
36768     /**
36769      * @cfg {String/Number} mapping
36770      *
36771      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
36772      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
36773      * as the field name, the mapping may be omitted.
36774      *
36775      * The form of the mapping expression depends on the Reader being used.
36776      *
36777      * - {@link Ext.data.reader.Json}
36778      *
36779      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
36780      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
36781      *
36782      * - {@link Ext.data.reader.Xml}
36783      *
36784      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
36785      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
36786      *
36787      * - {@link Ext.data.reader.Array}
36788      *
36789      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
36790      *   Array position.
36791      *
36792      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
36793      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
36794      * return the desired data.
36795      */
36796     mapping: null,
36797
36798     /**
36799      * @cfg {Function} sortType
36800      *
36801      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
36802      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
36803      *
36804      *     // current sort     after sort we want
36805      *     // +-+------+          +-+------+
36806      *     // |1|First |          |1|First |
36807      *     // |2|Last  |          |3|Second|
36808      *     // |3|Second|          |2|Last  |
36809      *     // +-+------+          +-+------+
36810      *
36811      *     sortType: function(value) {
36812      *        switch (value.toLowerCase()) // native toLowerCase():
36813      *        {
36814      *           case 'first': return 1;
36815      *           case 'second': return 2;
36816      *           default: return 3;
36817      *        }
36818      *     }
36819      */
36820     sortType : null,
36821
36822     /**
36823      * @cfg {String} sortDir
36824      *
36825      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
36826      */
36827     sortDir : "ASC",
36828
36829     /**
36830      * @cfg {Boolean} allowBlank
36831      * @private
36832      *
36833      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
36834      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
36835      */
36836     allowBlank : true,
36837
36838     /**
36839      * @cfg {Boolean} persist
36840      *
36841      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
36842      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
36843      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
36844      */
36845     persist: true
36846 });
36847
36848 /**
36849  * @class Ext.util.AbstractMixedCollection
36850  * @private
36851  */
36852 Ext.define('Ext.util.AbstractMixedCollection', {
36853     requires: ['Ext.util.Filter'],
36854
36855     mixins: {
36856         observable: 'Ext.util.Observable'
36857     },
36858
36859     constructor: function(allowFunctions, keyFn) {
36860         var me = this;
36861
36862         me.items = [];
36863         me.map = {};
36864         me.keys = [];
36865         me.length = 0;
36866
36867         me.addEvents(
36868             /**
36869              * @event clear
36870              * Fires when the collection is cleared.
36871              */
36872             'clear',
36873
36874             /**
36875              * @event add
36876              * Fires when an item is added to the collection.
36877              * @param {Number} index The index at which the item was added.
36878              * @param {Object} o The item added.
36879              * @param {String} key The key associated with the added item.
36880              */
36881             'add',
36882
36883             /**
36884              * @event replace
36885              * Fires when an item is replaced in the collection.
36886              * @param {String} key he key associated with the new added.
36887              * @param {Object} old The item being replaced.
36888              * @param {Object} new The new item.
36889              */
36890             'replace',
36891
36892             /**
36893              * @event remove
36894              * Fires when an item is removed from the collection.
36895              * @param {Object} o The item being removed.
36896              * @param {String} key (optional) The key associated with the removed item.
36897              */
36898             'remove'
36899         );
36900
36901         me.allowFunctions = allowFunctions === true;
36902
36903         if (keyFn) {
36904             me.getKey = keyFn;
36905         }
36906
36907         me.mixins.observable.constructor.call(me);
36908     },
36909
36910     /**
36911      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
36912      * function should add function references to the collection. Defaults to
36913      * <tt>false</tt>.
36914      */
36915     allowFunctions : false,
36916
36917     /**
36918      * Adds an item to the collection. Fires the {@link #add} event when complete.
36919      * @param {String} key <p>The key to associate with the item, or the new item.</p>
36920      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
36921      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
36922      * the MixedCollection will be able to <i>derive</i> the key for the new item.
36923      * In this case just pass the new item in this parameter.</p>
36924      * @param {Object} o The item to add.
36925      * @return {Object} The item added.
36926      */
36927     add : function(key, obj){
36928         var me = this,
36929             myObj = obj,
36930             myKey = key,
36931             old;
36932
36933         if (arguments.length == 1) {
36934             myObj = myKey;
36935             myKey = me.getKey(myObj);
36936         }
36937         if (typeof myKey != 'undefined' && myKey !== null) {
36938             old = me.map[myKey];
36939             if (typeof old != 'undefined') {
36940                 return me.replace(myKey, myObj);
36941             }
36942             me.map[myKey] = myObj;
36943         }
36944         me.length++;
36945         me.items.push(myObj);
36946         me.keys.push(myKey);
36947         me.fireEvent('add', me.length - 1, myObj, myKey);
36948         return myObj;
36949     },
36950
36951     /**
36952       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
36953       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
36954       * to return a different value as in the following examples:<pre><code>
36955 // normal way
36956 var mc = new Ext.util.MixedCollection();
36957 mc.add(someEl.dom.id, someEl);
36958 mc.add(otherEl.dom.id, otherEl);
36959 //and so on
36960
36961 // using getKey
36962 var mc = new Ext.util.MixedCollection();
36963 mc.getKey = function(el){
36964    return el.dom.id;
36965 };
36966 mc.add(someEl);
36967 mc.add(otherEl);
36968
36969 // or via the constructor
36970 var mc = new Ext.util.MixedCollection(false, function(el){
36971    return el.dom.id;
36972 });
36973 mc.add(someEl);
36974 mc.add(otherEl);
36975      * </code></pre>
36976      * @param {Object} item The item for which to find the key.
36977      * @return {Object} The key for the passed item.
36978      */
36979     getKey : function(o){
36980          return o.id;
36981     },
36982
36983     /**
36984      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
36985      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
36986      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
36987      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
36988      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
36989      * with one having the same key value, then just pass the replacement item in this parameter.</p>
36990      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
36991      * with that key.
36992      * @return {Object}  The new item.
36993      */
36994     replace : function(key, o){
36995         var me = this,
36996             old,
36997             index;
36998
36999         if (arguments.length == 1) {
37000             o = arguments[0];
37001             key = me.getKey(o);
37002         }
37003         old = me.map[key];
37004         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
37005              return me.add(key, o);
37006         }
37007         index = me.indexOfKey(key);
37008         me.items[index] = o;
37009         me.map[key] = o;
37010         me.fireEvent('replace', key, old, o);
37011         return o;
37012     },
37013
37014     /**
37015      * Adds all elements of an Array or an Object to the collection.
37016      * @param {Object/Array} objs An Object containing properties which will be added
37017      * to the collection, or an Array of values, each of which are added to the collection.
37018      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
37019      * has been set to <tt>true</tt>.
37020      */
37021     addAll : function(objs){
37022         var me = this,
37023             i = 0,
37024             args,
37025             len,
37026             key;
37027
37028         if (arguments.length > 1 || Ext.isArray(objs)) {
37029             args = arguments.length > 1 ? arguments : objs;
37030             for (len = args.length; i < len; i++) {
37031                 me.add(args[i]);
37032             }
37033         } else {
37034             for (key in objs) {
37035                 if (objs.hasOwnProperty(key)) {
37036                     if (me.allowFunctions || typeof objs[key] != 'function') {
37037                         me.add(key, objs[key]);
37038                     }
37039                 }
37040             }
37041         }
37042     },
37043
37044     /**
37045      * Executes the specified function once for every item in the collection, passing the following arguments:
37046      * <div class="mdetail-params"><ul>
37047      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
37048      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
37049      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
37050      * </ul></div>
37051      * The function should return a boolean value. Returning false from the function will stop the iteration.
37052      * @param {Function} fn The function to execute for each item.
37053      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
37054      */
37055     each : function(fn, scope){
37056         var items = [].concat(this.items), // each safe for removal
37057             i = 0,
37058             len = items.length,
37059             item;
37060
37061         for (; i < len; i++) {
37062             item = items[i];
37063             if (fn.call(scope || item, item, i, len) === false) {
37064                 break;
37065             }
37066         }
37067     },
37068
37069     /**
37070      * Executes the specified function once for every key in the collection, passing each
37071      * key, and its associated item as the first two parameters.
37072      * @param {Function} fn The function to execute for each item.
37073      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37074      */
37075     eachKey : function(fn, scope){
37076         var keys = this.keys,
37077             items = this.items,
37078             i = 0,
37079             len = keys.length;
37080
37081         for (; i < len; i++) {
37082             fn.call(scope || window, keys[i], items[i], i, len);
37083         }
37084     },
37085
37086     /**
37087      * Returns the first item in the collection which elicits a true return value from the
37088      * passed selection function.
37089      * @param {Function} fn The selection function to execute for each item.
37090      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37091      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
37092      */
37093     findBy : function(fn, scope) {
37094         var keys = this.keys,
37095             items = this.items,
37096             i = 0,
37097             len = items.length;
37098
37099         for (; i < len; i++) {
37100             if (fn.call(scope || window, items[i], keys[i])) {
37101                 return items[i];
37102             }
37103         }
37104         return null;
37105     },
37106
37107     find : function() {
37108         if (Ext.isDefined(Ext.global.console)) {
37109             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
37110         }
37111         return this.findBy.apply(this, arguments);
37112     },
37113
37114     /**
37115      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
37116      * @param {Number} index The index to insert the item at.
37117      * @param {String} key The key to associate with the new item, or the item itself.
37118      * @param {Object} o (optional) If the second parameter was a key, the new item.
37119      * @return {Object} The item inserted.
37120      */
37121     insert : function(index, key, obj){
37122         var me = this,
37123             myKey = key,
37124             myObj = obj;
37125
37126         if (arguments.length == 2) {
37127             myObj = myKey;
37128             myKey = me.getKey(myObj);
37129         }
37130         if (me.containsKey(myKey)) {
37131             me.suspendEvents();
37132             me.removeAtKey(myKey);
37133             me.resumeEvents();
37134         }
37135         if (index >= me.length) {
37136             return me.add(myKey, myObj);
37137         }
37138         me.length++;
37139         Ext.Array.splice(me.items, index, 0, myObj);
37140         if (typeof myKey != 'undefined' && myKey !== null) {
37141             me.map[myKey] = myObj;
37142         }
37143         Ext.Array.splice(me.keys, index, 0, myKey);
37144         me.fireEvent('add', index, myObj, myKey);
37145         return myObj;
37146     },
37147
37148     /**
37149      * Remove an item from the collection.
37150      * @param {Object} o The item to remove.
37151      * @return {Object} The item removed or false if no item was removed.
37152      */
37153     remove : function(o){
37154         return this.removeAt(this.indexOf(o));
37155     },
37156
37157     /**
37158      * Remove all items in the passed array from the collection.
37159      * @param {Array} items An array of items to be removed.
37160      * @return {Ext.util.MixedCollection} this object
37161      */
37162     removeAll : function(items){
37163         Ext.each(items || [], function(item) {
37164             this.remove(item);
37165         }, this);
37166
37167         return this;
37168     },
37169
37170     /**
37171      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
37172      * @param {Number} index The index within the collection of the item to remove.
37173      * @return {Object} The item removed or false if no item was removed.
37174      */
37175     removeAt : function(index){
37176         var me = this,
37177             o,
37178             key;
37179
37180         if (index < me.length && index >= 0) {
37181             me.length--;
37182             o = me.items[index];
37183             Ext.Array.erase(me.items, index, 1);
37184             key = me.keys[index];
37185             if (typeof key != 'undefined') {
37186                 delete me.map[key];
37187             }
37188             Ext.Array.erase(me.keys, index, 1);
37189             me.fireEvent('remove', o, key);
37190             return o;
37191         }
37192         return false;
37193     },
37194
37195     /**
37196      * Removed an item associated with the passed key fom the collection.
37197      * @param {String} key The key of the item to remove.
37198      * @return {Object} The item removed or false if no item was removed.
37199      */
37200     removeAtKey : function(key){
37201         return this.removeAt(this.indexOfKey(key));
37202     },
37203
37204     /**
37205      * Returns the number of items in the collection.
37206      * @return {Number} the number of items in the collection.
37207      */
37208     getCount : function(){
37209         return this.length;
37210     },
37211
37212     /**
37213      * Returns index within the collection of the passed Object.
37214      * @param {Object} o The item to find the index of.
37215      * @return {Number} index of the item. Returns -1 if not found.
37216      */
37217     indexOf : function(o){
37218         return Ext.Array.indexOf(this.items, o);
37219     },
37220
37221     /**
37222      * Returns index within the collection of the passed key.
37223      * @param {String} key The key to find the index of.
37224      * @return {Number} index of the key.
37225      */
37226     indexOfKey : function(key){
37227         return Ext.Array.indexOf(this.keys, key);
37228     },
37229
37230     /**
37231      * Returns the item associated with the passed key OR index.
37232      * Key has priority over index.  This is the equivalent
37233      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
37234      * @param {String/Number} key The key or index of the item.
37235      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
37236      * If an item was found, but is a Class, returns <tt>null</tt>.
37237      */
37238     get : function(key) {
37239         var me = this,
37240             mk = me.map[key],
37241             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
37242         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
37243     },
37244
37245     /**
37246      * Returns the item at the specified index.
37247      * @param {Number} index The index of the item.
37248      * @return {Object} The item at the specified index.
37249      */
37250     getAt : function(index) {
37251         return this.items[index];
37252     },
37253
37254     /**
37255      * Returns the item associated with the passed key.
37256      * @param {String/Number} key The key of the item.
37257      * @return {Object} The item associated with the passed key.
37258      */
37259     getByKey : function(key) {
37260         return this.map[key];
37261     },
37262
37263     /**
37264      * Returns true if the collection contains the passed Object as an item.
37265      * @param {Object} o  The Object to look for in the collection.
37266      * @return {Boolean} True if the collection contains the Object as an item.
37267      */
37268     contains : function(o){
37269         return Ext.Array.contains(this.items, o);
37270     },
37271
37272     /**
37273      * Returns true if the collection contains the passed Object as a key.
37274      * @param {String} key The key to look for in the collection.
37275      * @return {Boolean} True if the collection contains the Object as a key.
37276      */
37277     containsKey : function(key){
37278         return typeof this.map[key] != 'undefined';
37279     },
37280
37281     /**
37282      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
37283      */
37284     clear : function(){
37285         var me = this;
37286
37287         me.length = 0;
37288         me.items = [];
37289         me.keys = [];
37290         me.map = {};
37291         me.fireEvent('clear');
37292     },
37293
37294     /**
37295      * Returns the first item in the collection.
37296      * @return {Object} the first item in the collection..
37297      */
37298     first : function() {
37299         return this.items[0];
37300     },
37301
37302     /**
37303      * Returns the last item in the collection.
37304      * @return {Object} the last item in the collection..
37305      */
37306     last : function() {
37307         return this.items[this.length - 1];
37308     },
37309
37310     /**
37311      * Collects all of the values of the given property and returns their sum
37312      * @param {String} property The property to sum by
37313      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
37314      * summing fields in records, where the fields are all stored inside the 'data' object
37315      * @param {Number} [start=0] The record index to start at
37316      * @param {Number} [end=-1] The record index to end at
37317      * @return {Number} The total
37318      */
37319     sum: function(property, root, start, end) {
37320         var values = this.extractValues(property, root),
37321             length = values.length,
37322             sum    = 0,
37323             i;
37324
37325         start = start || 0;
37326         end   = (end || end === 0) ? end : length - 1;
37327
37328         for (i = start; i <= end; i++) {
37329             sum += values[i];
37330         }
37331
37332         return sum;
37333     },
37334
37335     /**
37336      * Collects unique values of a particular property in this MixedCollection
37337      * @param {String} property The property to collect on
37338      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37339      * summing fields in records, where the fields are all stored inside the 'data' object
37340      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
37341      * @return {Array} The unique values
37342      */
37343     collect: function(property, root, allowNull) {
37344         var values = this.extractValues(property, root),
37345             length = values.length,
37346             hits   = {},
37347             unique = [],
37348             value, strValue, i;
37349
37350         for (i = 0; i < length; i++) {
37351             value = values[i];
37352             strValue = String(value);
37353
37354             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
37355                 hits[strValue] = true;
37356                 unique.push(value);
37357             }
37358         }
37359
37360         return unique;
37361     },
37362
37363     /**
37364      * @private
37365      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
37366      * functions like sum and collect.
37367      * @param {String} property The property to extract
37368      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37369      * extracting field data from Model instances, where the fields are stored inside the 'data' object
37370      * @return {Array} The extracted values
37371      */
37372     extractValues: function(property, root) {
37373         var values = this.items;
37374
37375         if (root) {
37376             values = Ext.Array.pluck(values, root);
37377         }
37378
37379         return Ext.Array.pluck(values, property);
37380     },
37381
37382     /**
37383      * Returns a range of items in this collection
37384      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
37385      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
37386      * @return {Array} An array of items
37387      */
37388     getRange : function(start, end){
37389         var me = this,
37390             items = me.items,
37391             range = [],
37392             i;
37393
37394         if (items.length < 1) {
37395             return range;
37396         }
37397
37398         start = start || 0;
37399         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
37400         if (start <= end) {
37401             for (i = start; i <= end; i++) {
37402                 range[range.length] = items[i];
37403             }
37404         } else {
37405             for (i = start; i >= end; i--) {
37406                 range[range.length] = items[i];
37407             }
37408         }
37409         return range;
37410     },
37411
37412     /**
37413      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
37414      * property/value pair with optional parameters for substring matching and case sensitivity. See
37415      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
37416      * MixedCollection can be easily filtered by property like this:</p>
37417 <pre><code>
37418 //create a simple store with a few people defined
37419 var people = new Ext.util.MixedCollection();
37420 people.addAll([
37421     {id: 1, age: 25, name: 'Ed'},
37422     {id: 2, age: 24, name: 'Tommy'},
37423     {id: 3, age: 24, name: 'Arne'},
37424     {id: 4, age: 26, name: 'Aaron'}
37425 ]);
37426
37427 //a new MixedCollection containing only the items where age == 24
37428 var middleAged = people.filter('age', 24);
37429 </code></pre>
37430      *
37431      *
37432      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
37433      * @param {String/RegExp} value Either string that the property values
37434      * should start with or a RegExp to test against the property
37435      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
37436      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37437      * @return {Ext.util.MixedCollection} The new filtered collection
37438      */
37439     filter : function(property, value, anyMatch, caseSensitive) {
37440         var filters = [],
37441             filterFn;
37442
37443         //support for the simple case of filtering by property/value
37444         if (Ext.isString(property)) {
37445             filters.push(Ext.create('Ext.util.Filter', {
37446                 property     : property,
37447                 value        : value,
37448                 anyMatch     : anyMatch,
37449                 caseSensitive: caseSensitive
37450             }));
37451         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
37452             filters = filters.concat(property);
37453         }
37454
37455         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
37456         //so here we construct a function that combines these filters by ANDing them together
37457         filterFn = function(record) {
37458             var isMatch = true,
37459                 length = filters.length,
37460                 i;
37461
37462             for (i = 0; i < length; i++) {
37463                 var filter = filters[i],
37464                     fn     = filter.filterFn,
37465                     scope  = filter.scope;
37466
37467                 isMatch = isMatch && fn.call(scope, record);
37468             }
37469
37470             return isMatch;
37471         };
37472
37473         return this.filterBy(filterFn);
37474     },
37475
37476     /**
37477      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
37478      * The passed function will be called with each object in the collection.
37479      * If the function returns true, the value is included otherwise it is filtered.
37480      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
37481      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37482      * @return {Ext.util.MixedCollection} The new filtered collection
37483      */
37484     filterBy : function(fn, scope) {
37485         var me = this,
37486             newMC  = new this.self(),
37487             keys   = me.keys,
37488             items  = me.items,
37489             length = items.length,
37490             i;
37491
37492         newMC.getKey = me.getKey;
37493
37494         for (i = 0; i < length; i++) {
37495             if (fn.call(scope || me, items[i], keys[i])) {
37496                 newMC.add(keys[i], items[i]);
37497             }
37498         }
37499
37500         return newMC;
37501     },
37502
37503     /**
37504      * Finds the index of the first matching object in this collection by a specific property/value.
37505      * @param {String} property The name of a property on your objects.
37506      * @param {String/RegExp} value A string that the property values
37507      * should start with or a RegExp to test against the property.
37508      * @param {Number} [start=0] The index to start searching at.
37509      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
37510      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37511      * @return {Number} The matched index or -1
37512      */
37513     findIndex : function(property, value, start, anyMatch, caseSensitive){
37514         if(Ext.isEmpty(value, false)){
37515             return -1;
37516         }
37517         value = this.createValueMatcher(value, anyMatch, caseSensitive);
37518         return this.findIndexBy(function(o){
37519             return o && value.test(o[property]);
37520         }, null, start);
37521     },
37522
37523     /**
37524      * Find the index of the first matching object in this collection by a function.
37525      * If the function returns <i>true</i> it is considered a match.
37526      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
37527      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37528      * @param {Number} [start=0] The index to start searching at.
37529      * @return {Number} The matched index or -1
37530      */
37531     findIndexBy : function(fn, scope, start){
37532         var me = this,
37533             keys = me.keys,
37534             items = me.items,
37535             i = start || 0,
37536             len = items.length;
37537
37538         for (; i < len; i++) {
37539             if (fn.call(scope || me, items[i], keys[i])) {
37540                 return i;
37541             }
37542         }
37543         return -1;
37544     },
37545
37546     /**
37547      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
37548      * and by Ext.data.Store#filter
37549      * @private
37550      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
37551      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
37552      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
37553      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
37554      */
37555     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
37556         if (!value.exec) { // not a regex
37557             var er = Ext.String.escapeRegex;
37558             value = String(value);
37559
37560             if (anyMatch === true) {
37561                 value = er(value);
37562             } else {
37563                 value = '^' + er(value);
37564                 if (exactMatch === true) {
37565                     value += '$';
37566                 }
37567             }
37568             value = new RegExp(value, caseSensitive ? '' : 'i');
37569         }
37570         return value;
37571     },
37572
37573     /**
37574      * Creates a shallow copy of this collection
37575      * @return {Ext.util.MixedCollection}
37576      */
37577     clone : function() {
37578         var me = this,
37579             copy = new this.self(),
37580             keys = me.keys,
37581             items = me.items,
37582             i = 0,
37583             len = items.length;
37584
37585         for(; i < len; i++){
37586             copy.add(keys[i], items[i]);
37587         }
37588         copy.getKey = me.getKey;
37589         return copy;
37590     }
37591 });
37592
37593 /**
37594  * @docauthor Tommy Maintz <tommy@sencha.com>
37595  *
37596  * A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
37597  *
37598  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
37599  * is more likely you will want to use one of the component classes that import this mixin, such as
37600  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
37601  */
37602 Ext.define("Ext.util.Sortable", {
37603     /**
37604      * @property {Boolean} isSortable
37605      * Flag denoting that this object is sortable. Always true.
37606      */
37607     isSortable: true,
37608
37609     /**
37610      * @property {String} defaultSortDirection
37611      * The default sort direction to use if one is not specified.
37612      */
37613     defaultSortDirection: "ASC",
37614
37615     requires: [
37616         'Ext.util.Sorter'
37617     ],
37618
37619     /**
37620      * @property {String} sortRoot
37621      * The property in each item that contains the data to sort.
37622      */
37623
37624     /**
37625      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
37626      * own initialization.
37627      */
37628     initSortable: function() {
37629         var me = this,
37630             sorters = me.sorters;
37631
37632         /**
37633          * @property {Ext.util.MixedCollection} sorters
37634          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
37635          */
37636         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
37637             return item.id || item.property;
37638         });
37639
37640         if (sorters) {
37641             me.sorters.addAll(me.decodeSorters(sorters));
37642         }
37643     },
37644
37645     /**
37646      * Sorts the data in the Store by one or more of its properties. Example usage:
37647      *
37648      *     //sort by a single field
37649      *     myStore.sort('myField', 'DESC');
37650      *
37651      *     //sorting by multiple fields
37652      *     myStore.sort([
37653      *         {
37654      *             property : 'age',
37655      *             direction: 'ASC'
37656      *         },
37657      *         {
37658      *             property : 'name',
37659      *             direction: 'DESC'
37660      *         }
37661      *     ]);
37662      *
37663      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
37664      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
37665      *
37666      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
37667      *
37668      *     store.sort('myField');
37669      *     store.sort('myField');
37670      *
37671      * Is equivalent to this code, because Store handles the toggling automatically:
37672      *
37673      *     store.sort('myField', 'ASC');
37674      *     store.sort('myField', 'DESC');
37675      *
37676      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
37677      * {@link Ext.data.Model Model}, or an array of sorter configurations.
37678      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
37679      * @return {Ext.util.Sorter[]}
37680      */
37681     sort: function(sorters, direction, where, doSort) {
37682         var me = this,
37683             sorter, sorterFn,
37684             newSorters;
37685
37686         if (Ext.isArray(sorters)) {
37687             doSort = where;
37688             where = direction;
37689             newSorters = sorters;
37690         }
37691         else if (Ext.isObject(sorters)) {
37692             doSort = where;
37693             where = direction;
37694             newSorters = [sorters];
37695         }
37696         else if (Ext.isString(sorters)) {
37697             sorter = me.sorters.get(sorters);
37698
37699             if (!sorter) {
37700                 sorter = {
37701                     property : sorters,
37702                     direction: direction
37703                 };
37704                 newSorters = [sorter];
37705             }
37706             else if (direction === undefined) {
37707                 sorter.toggle();
37708             }
37709             else {
37710                 sorter.setDirection(direction);
37711             }
37712         }
37713
37714         if (newSorters && newSorters.length) {
37715             newSorters = me.decodeSorters(newSorters);
37716             if (Ext.isString(where)) {
37717                 if (where === 'prepend') {
37718                     sorters = me.sorters.clone().items;
37719
37720                     me.sorters.clear();
37721                     me.sorters.addAll(newSorters);
37722                     me.sorters.addAll(sorters);
37723                 }
37724                 else {
37725                     me.sorters.addAll(newSorters);
37726                 }
37727             }
37728             else {
37729                 me.sorters.clear();
37730                 me.sorters.addAll(newSorters);
37731             }
37732         }
37733
37734         if (doSort !== false) {
37735             me.onBeforeSort(newSorters);
37736             
37737             sorters = me.sorters.items;
37738             if (sorters.length) {
37739                 //construct an amalgamated sorter function which combines all of the Sorters passed
37740                 sorterFn = function(r1, r2) {
37741                     var result = sorters[0].sort(r1, r2),
37742                         length = sorters.length,
37743                         i;
37744
37745                         //if we have more than one sorter, OR any additional sorter functions together
37746                         for (i = 1; i < length; i++) {
37747                             result = result || sorters[i].sort.call(this, r1, r2);
37748                         }
37749
37750                     return result;
37751                 };
37752
37753                 me.doSort(sorterFn);
37754             }
37755         }
37756
37757         return sorters;
37758     },
37759
37760     onBeforeSort: Ext.emptyFn,
37761
37762     /**
37763      * @private
37764      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
37765      * @param {Object[]} sorters The sorters array
37766      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
37767      */
37768     decodeSorters: function(sorters) {
37769         if (!Ext.isArray(sorters)) {
37770             if (sorters === undefined) {
37771                 sorters = [];
37772             } else {
37773                 sorters = [sorters];
37774             }
37775         }
37776
37777         var length = sorters.length,
37778             Sorter = Ext.util.Sorter,
37779             fields = this.model ? this.model.prototype.fields : null,
37780             field,
37781             config, i;
37782
37783         for (i = 0; i < length; i++) {
37784             config = sorters[i];
37785
37786             if (!(config instanceof Sorter)) {
37787                 if (Ext.isString(config)) {
37788                     config = {
37789                         property: config
37790                     };
37791                 }
37792
37793                 Ext.applyIf(config, {
37794                     root     : this.sortRoot,
37795                     direction: "ASC"
37796                 });
37797
37798                 //support for 3.x style sorters where a function can be defined as 'fn'
37799                 if (config.fn) {
37800                     config.sorterFn = config.fn;
37801                 }
37802
37803                 //support a function to be passed as a sorter definition
37804                 if (typeof config == 'function') {
37805                     config = {
37806                         sorterFn: config
37807                     };
37808                 }
37809
37810                 // ensure sortType gets pushed on if necessary
37811                 if (fields && !config.transform) {
37812                     field = fields.get(config.property);
37813                     config.transform = field ? field.sortType : undefined;
37814                 }
37815                 sorters[i] = Ext.create('Ext.util.Sorter', config);
37816             }
37817         }
37818
37819         return sorters;
37820     },
37821
37822     getSorters: function() {
37823         return this.sorters.items;
37824     }
37825 });
37826 /**
37827  * @class Ext.util.MixedCollection
37828  * <p>
37829  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
37830  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
37831  * collection can be accessed by index  or via the key. Newly added items are added to
37832  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
37833  * is heavier and provides more functionality. Sample usage:
37834  * <pre><code>
37835 var coll = new Ext.util.MixedCollection();
37836 coll.add('key1', 'val1');
37837 coll.add('key2', 'val2');
37838 coll.add('key3', 'val3');
37839
37840 console.log(coll.get('key1')); // prints 'val1'
37841 console.log(coll.indexOfKey('key3')); // prints 2
37842  * </code></pre>
37843  *
37844  * <p>
37845  * The MixedCollection also has support for sorting and filtering of the values in the collection.
37846  * <pre><code>
37847 var coll = new Ext.util.MixedCollection();
37848 coll.add('key1', 100);
37849 coll.add('key2', -100);
37850 coll.add('key3', 17);
37851 coll.add('key4', 0);
37852 var biggerThanZero = coll.filterBy(function(value){
37853     return value > 0;
37854 });
37855 console.log(biggerThanZero.getCount()); // prints 2
37856  * </code></pre>
37857  * </p>
37858  */
37859 Ext.define('Ext.util.MixedCollection', {
37860     extend: 'Ext.util.AbstractMixedCollection',
37861     mixins: {
37862         sortable: 'Ext.util.Sortable'
37863     },
37864
37865     /**
37866      * Creates new MixedCollection.
37867      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
37868      * function should add function references to the collection. Defaults to
37869      * <tt>false</tt>.
37870      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
37871      * and return the key value for that item.  This is used when available to look up the key on items that
37872      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
37873      * equivalent to providing an implementation for the {@link #getKey} method.
37874      */
37875     constructor: function() {
37876         var me = this;
37877         me.callParent(arguments);
37878         me.addEvents('sort');
37879         me.mixins.sortable.initSortable.call(me);
37880     },
37881
37882     doSort: function(sorterFn) {
37883         this.sortBy(sorterFn);
37884     },
37885
37886     /**
37887      * @private
37888      * Performs the actual sorting based on a direction and a sorting function. Internally,
37889      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
37890      * the sorted array data back into this.items and this.keys
37891      * @param {String} property Property to sort by ('key', 'value', or 'index')
37892      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
37893      * @param {Function} fn (optional) Comparison function that defines the sort order.
37894      * Defaults to sorting by numeric value.
37895      */
37896     _sort : function(property, dir, fn){
37897         var me = this,
37898             i, len,
37899             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
37900
37901             //this is a temporary array used to apply the sorting function
37902             c     = [],
37903             keys  = me.keys,
37904             items = me.items;
37905
37906         //default to a simple sorter function if one is not provided
37907         fn = fn || function(a, b) {
37908             return a - b;
37909         };
37910
37911         //copy all the items into a temporary array, which we will sort
37912         for(i = 0, len = items.length; i < len; i++){
37913             c[c.length] = {
37914                 key  : keys[i],
37915                 value: items[i],
37916                 index: i
37917             };
37918         }
37919
37920         //sort the temporary array
37921         Ext.Array.sort(c, function(a, b){
37922             var v = fn(a[property], b[property]) * dsc;
37923             if(v === 0){
37924                 v = (a.index < b.index ? -1 : 1);
37925             }
37926             return v;
37927         });
37928
37929         //copy the temporary array back into the main this.items and this.keys objects
37930         for(i = 0, len = c.length; i < len; i++){
37931             items[i] = c[i].value;
37932             keys[i]  = c[i].key;
37933         }
37934
37935         me.fireEvent('sort', me);
37936     },
37937
37938     /**
37939      * Sorts the collection by a single sorter function
37940      * @param {Function} sorterFn The function to sort by
37941      */
37942     sortBy: function(sorterFn) {
37943         var me     = this,
37944             items  = me.items,
37945             keys   = me.keys,
37946             length = items.length,
37947             temp   = [],
37948             i;
37949
37950         //first we create a copy of the items array so that we can sort it
37951         for (i = 0; i < length; i++) {
37952             temp[i] = {
37953                 key  : keys[i],
37954                 value: items[i],
37955                 index: i
37956             };
37957         }
37958
37959         Ext.Array.sort(temp, function(a, b) {
37960             var v = sorterFn(a.value, b.value);
37961             if (v === 0) {
37962                 v = (a.index < b.index ? -1 : 1);
37963             }
37964
37965             return v;
37966         });
37967
37968         //copy the temporary array back into the main this.items and this.keys objects
37969         for (i = 0; i < length; i++) {
37970             items[i] = temp[i].value;
37971             keys[i]  = temp[i].key;
37972         }
37973         
37974         me.fireEvent('sort', me, items, keys);
37975     },
37976
37977     /**
37978      * Reorders each of the items based on a mapping from old index to new index. Internally this
37979      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
37980      * @param {Object} mapping Mapping from old item index to new item index
37981      */
37982     reorder: function(mapping) {
37983         var me = this,
37984             items = me.items,
37985             index = 0,
37986             length = items.length,
37987             order = [],
37988             remaining = [],
37989             oldIndex;
37990
37991         me.suspendEvents();
37992
37993         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
37994         for (oldIndex in mapping) {
37995             order[mapping[oldIndex]] = items[oldIndex];
37996         }
37997
37998         for (index = 0; index < length; index++) {
37999             if (mapping[index] == undefined) {
38000                 remaining.push(items[index]);
38001             }
38002         }
38003
38004         for (index = 0; index < length; index++) {
38005             if (order[index] == undefined) {
38006                 order[index] = remaining.shift();
38007             }
38008         }
38009
38010         me.clear();
38011         me.addAll(order);
38012
38013         me.resumeEvents();
38014         me.fireEvent('sort', me);
38015     },
38016
38017     /**
38018      * Sorts this collection by <b>key</b>s.
38019      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
38020      * @param {Function} fn (optional) Comparison function that defines the sort order.
38021      * Defaults to sorting by case insensitive string.
38022      */
38023     sortByKey : function(dir, fn){
38024         this._sort('key', dir, fn || function(a, b){
38025             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
38026             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
38027         });
38028     }
38029 });
38030
38031 /**
38032  * @author Ed Spencer
38033  * @class Ext.data.Errors
38034  * @extends Ext.util.MixedCollection
38035  *
38036  * <p>Wraps a collection of validation error responses and provides convenient functions for
38037  * accessing and errors for specific fields.</p>
38038  *
38039  * <p>Usually this class does not need to be instantiated directly - instances are instead created
38040  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
38041  *
38042 <pre><code>
38043 //validate some existing model instance - in this case it returned 2 failures messages
38044 var errors = myModel.validate();
38045
38046 errors.isValid(); //false
38047
38048 errors.length; //2
38049 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
38050 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
38051 </code></pre>
38052  */
38053 Ext.define('Ext.data.Errors', {
38054     extend: 'Ext.util.MixedCollection',
38055
38056     /**
38057      * Returns true if there are no errors in the collection
38058      * @return {Boolean}
38059      */
38060     isValid: function() {
38061         return this.length === 0;
38062     },
38063
38064     /**
38065      * Returns all of the errors for the given field
38066      * @param {String} fieldName The field to get errors for
38067      * @return {Object[]} All errors for the given field
38068      */
38069     getByField: function(fieldName) {
38070         var errors = [],
38071             error, field, i;
38072
38073         for (i = 0; i < this.length; i++) {
38074             error = this.items[i];
38075
38076             if (error.field == fieldName) {
38077                 errors.push(error);
38078             }
38079         }
38080
38081         return errors;
38082     }
38083 });
38084
38085 /**
38086  * @author Ed Spencer
38087  *
38088  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
38089  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
38090  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
38091  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
38092  * 
38093  *     Ext.create('Ext.data.Store', {
38094  *         model: 'User',
38095  *         proxy: {
38096  *             type: 'ajax',
38097  *             url : 'users.json',
38098  *             reader: {
38099  *                 type: 'json',
38100  *                 root: 'users'
38101  *             }
38102  *         },
38103  *     });
38104  *     
38105  * The above reader is configured to consume a JSON string that looks something like this:
38106  *  
38107  *     {
38108  *         "success": true,
38109  *         "users": [
38110  *             { "name": "User 1" },
38111  *             { "name": "User 2" }
38112  *         ]
38113  *     }
38114  * 
38115  *
38116  * # Loading Nested Data
38117  *
38118  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
38119  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
38120  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
38121  *
38122  *     Ext.define("User", {
38123  *         extend: 'Ext.data.Model',
38124  *         fields: [
38125  *             'id', 'name'
38126  *         ],
38127  *
38128  *         hasMany: {model: 'Order', name: 'orders'},
38129  *
38130  *         proxy: {
38131  *             type: 'rest',
38132  *             url : 'users.json',
38133  *             reader: {
38134  *                 type: 'json',
38135  *                 root: 'users'
38136  *             }
38137  *         }
38138  *     });
38139  *
38140  *     Ext.define("Order", {
38141  *         extend: 'Ext.data.Model',
38142  *         fields: [
38143  *             'id', 'total'
38144  *         ],
38145  *
38146  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
38147  *         belongsTo: 'User'
38148  *     });
38149  *
38150  *     Ext.define("OrderItem", {
38151  *         extend: 'Ext.data.Model',
38152  *         fields: [
38153  *             'id', 'price', 'quantity', 'order_id', 'product_id'
38154  *         ],
38155  *
38156  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
38157  *     });
38158  *
38159  *     Ext.define("Product", {
38160  *         extend: 'Ext.data.Model',
38161  *         fields: [
38162  *             'id', 'name'
38163  *         ],
38164  *
38165  *         hasMany: 'OrderItem'
38166  *     });
38167  *
38168  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
38169  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
38170  *
38171  *     {
38172  *         "users": [
38173  *             {
38174  *                 "id": 123,
38175  *                 "name": "Ed",
38176  *                 "orders": [
38177  *                     {
38178  *                         "id": 50,
38179  *                         "total": 100,
38180  *                         "order_items": [
38181  *                             {
38182  *                                 "id"      : 20,
38183  *                                 "price"   : 40,
38184  *                                 "quantity": 2,
38185  *                                 "product" : {
38186  *                                     "id": 1000,
38187  *                                     "name": "MacBook Pro"
38188  *                                 }
38189  *                             },
38190  *                             {
38191  *                                 "id"      : 21,
38192  *                                 "price"   : 20,
38193  *                                 "quantity": 3,
38194  *                                 "product" : {
38195  *                                     "id": 1001,
38196  *                                     "name": "iPhone"
38197  *                                 }
38198  *                             }
38199  *                         ]
38200  *                     }
38201  *                 ]
38202  *             }
38203  *         ]
38204  *     }
38205  *
38206  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
38207  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
38208  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
38209  *
38210  *     var store = Ext.create('Ext.data.Store', {
38211  *         model: "User"
38212  *     });
38213  *
38214  *     store.load({
38215  *         callback: function() {
38216  *             //the user that was loaded
38217  *             var user = store.first();
38218  *
38219  *             console.log("Orders for " + user.get('name') + ":")
38220  *
38221  *             //iterate over the Orders for each User
38222  *             user.orders().each(function(order) {
38223  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
38224  *
38225  *                 //iterate over the OrderItems for each Order
38226  *                 order.orderItems().each(function(orderItem) {
38227  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
38228  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
38229  *                     var product = orderItem.getProduct();
38230  *
38231  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
38232  *                 });
38233  *             });
38234  *         }
38235  *     });
38236  *
38237  * Running the code above results in the following:
38238  *
38239  *     Orders for Ed:
38240  *     Order ID: 50, which contains items:
38241  *     2 orders of MacBook Pro
38242  *     3 orders of iPhone
38243  */
38244 Ext.define('Ext.data.reader.Reader', {
38245     requires: ['Ext.data.ResultSet'],
38246     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
38247     
38248     /**
38249      * @cfg {String} idProperty
38250      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
38251      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
38252      */
38253
38254     /**
38255      * @cfg {String} totalProperty
38256      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
38257      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
38258      */
38259     totalProperty: 'total',
38260
38261     /**
38262      * @cfg {String} successProperty
38263      * Name of the property from which to retrieve the success attribute. Defaults to success. See
38264      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
38265      */
38266     successProperty: 'success',
38267
38268     /**
38269      * @cfg {String} root
38270      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
38271      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
38272      * 
38273      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
38274      *
38275      * The data packet value for this property should be an empty array to clear the data or show no data.
38276      */
38277     root: '',
38278     
38279     /**
38280      * @cfg {String} messageProperty
38281      * The name of the property which contains a response message. This property is optional.
38282      */
38283     
38284     /**
38285      * @cfg {Boolean} implicitIncludes
38286      * True to automatically parse models nested within other models in a response object. See the
38287      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
38288      */
38289     implicitIncludes: true,
38290     
38291     isReader: true,
38292     
38293     /**
38294      * Creates new Reader.
38295      * @param {Object} config (optional) Config object.
38296      */
38297     constructor: function(config) {
38298         var me = this;
38299         
38300         Ext.apply(me, config || {});
38301         me.fieldCount = 0;
38302         me.model = Ext.ModelManager.getModel(config.model);
38303         if (me.model) {
38304             me.buildExtractors();
38305         }
38306     },
38307
38308     /**
38309      * Sets a new model for the reader.
38310      * @private
38311      * @param {Object} model The model to set.
38312      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
38313      */
38314     setModel: function(model, setOnProxy) {
38315         var me = this;
38316         
38317         me.model = Ext.ModelManager.getModel(model);
38318         me.buildExtractors(true);
38319         
38320         if (setOnProxy && me.proxy) {
38321             me.proxy.setModel(me.model, true);
38322         }
38323     },
38324
38325     /**
38326      * Reads the given response object. This method normalizes the different types of response object that may be passed
38327      * to it, before handing off the reading of records to the {@link #readRecords} function.
38328      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
38329      * @return {Ext.data.ResultSet} The parsed ResultSet object
38330      */
38331     read: function(response) {
38332         var data = response;
38333         
38334         if (response && response.responseText) {
38335             data = this.getResponseData(response);
38336         }
38337         
38338         if (data) {
38339             return this.readRecords(data);
38340         } else {
38341             return this.nullResultSet;
38342         }
38343     },
38344
38345     /**
38346      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
38347      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
38348      * processing should not be needed.
38349      * @param {Object} data The raw data object
38350      * @return {Ext.data.ResultSet} A ResultSet object
38351      */
38352     readRecords: function(data) {
38353         var me  = this;
38354         
38355         /*
38356          * We check here whether the number of fields has changed since the last read.
38357          * This works around an issue when a Model is used for both a Tree and another
38358          * source, because the tree decorates the model with extra fields and it causes
38359          * issues because the readers aren't notified.
38360          */
38361         if (me.fieldCount !== me.getFields().length) {
38362             me.buildExtractors(true);
38363         }
38364         
38365         /**
38366          * @property {Object} rawData
38367          * The raw data object that was last passed to readRecords. Stored for further processing if needed
38368          */
38369         me.rawData = data;
38370
38371         data = me.getData(data);
38372
38373         // If we pass an array as the data, we dont use getRoot on the data.
38374         // Instead the root equals to the data.
38375         var root    = Ext.isArray(data) ? data : me.getRoot(data),
38376             success = true,
38377             recordCount = 0,
38378             total, value, records, message;
38379             
38380         if (root) {
38381             total = root.length;
38382         }
38383
38384         if (me.totalProperty) {
38385             value = parseInt(me.getTotal(data), 10);
38386             if (!isNaN(value)) {
38387                 total = value;
38388             }
38389         }
38390
38391         if (me.successProperty) {
38392             value = me.getSuccess(data);
38393             if (value === false || value === 'false') {
38394                 success = false;
38395             }
38396         }
38397         
38398         if (me.messageProperty) {
38399             message = me.getMessage(data);
38400         }
38401         
38402         if (root) {
38403             records = me.extractData(root);
38404             recordCount = records.length;
38405         } else {
38406             recordCount = 0;
38407             records = [];
38408         }
38409
38410         return Ext.create('Ext.data.ResultSet', {
38411             total  : total || recordCount,
38412             count  : recordCount,
38413             records: records,
38414             success: success,
38415             message: message
38416         });
38417     },
38418
38419     /**
38420      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
38421      * @param {Object[]/Object} root from server response
38422      * @private
38423      */
38424     extractData : function(root) {
38425         var me = this,
38426             values  = [],
38427             records = [],
38428             Model   = me.model,
38429             i       = 0,
38430             length  = root.length,
38431             idProp  = me.getIdProperty(),
38432             node, id, record;
38433             
38434         if (!root.length && Ext.isObject(root)) {
38435             root = [root];
38436             length = 1;
38437         }
38438
38439         for (; i < length; i++) {
38440             node   = root[i];
38441             values = me.extractValues(node);
38442             id     = me.getId(node);
38443
38444             
38445             record = new Model(values, id, node);
38446             records.push(record);
38447                 
38448             if (me.implicitIncludes) {
38449                 me.readAssociated(record, node);
38450             }
38451         }
38452
38453         return records;
38454     },
38455     
38456     /**
38457      * @private
38458      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
38459      * on the record provided.
38460      * @param {Ext.data.Model} record The record to load associations for
38461      * @param {Object} data The data object
38462      * @return {String} Return value description
38463      */
38464     readAssociated: function(record, data) {
38465         var associations = record.associations.items,
38466             i            = 0,
38467             length       = associations.length,
38468             association, associationData, proxy, reader;
38469         
38470         for (; i < length; i++) {
38471             association     = associations[i];
38472             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
38473             
38474             if (associationData) {
38475                 reader = association.getReader();
38476                 if (!reader) {
38477                     proxy = association.associatedModel.proxy;
38478                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
38479                     if (proxy) {
38480                         reader = proxy.getReader();
38481                     } else {
38482                         reader = new this.constructor({
38483                             model: association.associatedName
38484                         });
38485                     }
38486                 }
38487                 association.read(record, reader, associationData);
38488             }  
38489         }
38490     },
38491     
38492     /**
38493      * @private
38494      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
38495      * record, this should return the relevant part of that data for the given association name. This is only really
38496      * needed to support the XML Reader, which has to do a query to get the associated data object
38497      * @param {Object} data The raw data object
38498      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
38499      * @return {Object} The root
38500      */
38501     getAssociatedDataRoot: function(data, associationName) {
38502         return data[associationName];
38503     },
38504     
38505     getFields: function() {
38506         return this.model.prototype.fields.items;
38507     },
38508
38509     /**
38510      * @private
38511      * Given an object representing a single model instance's data, iterates over the model's fields and
38512      * builds an object with the value for each field.
38513      * @param {Object} data The data object to convert
38514      * @return {Object} Data object suitable for use with a model constructor
38515      */
38516     extractValues: function(data) {
38517         var fields = this.getFields(),
38518             i      = 0,
38519             length = fields.length,
38520             output = {},
38521             field, value;
38522
38523         for (; i < length; i++) {
38524             field = fields[i];
38525             value = this.extractorFunctions[i](data);
38526
38527             output[field.name] = value;
38528         }
38529
38530         return output;
38531     },
38532
38533     /**
38534      * @private
38535      * By default this function just returns what is passed to it. It can be overridden in a subclass
38536      * to return something else. See XmlReader for an example.
38537      * @param {Object} data The data object
38538      * @return {Object} The normalized data object
38539      */
38540     getData: function(data) {
38541         return data;
38542     },
38543
38544     /**
38545      * @private
38546      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
38547      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
38548      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
38549      * @param {Object} data The data object
38550      * @return {Object} The same data object
38551      */
38552     getRoot: function(data) {
38553         return data;
38554     },
38555
38556     /**
38557      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
38558      * implemented by each subclass
38559      * @param {Object} response The responce object
38560      * @return {Object} The useful data from the response
38561      */
38562     getResponseData: function(response) {
38563     },
38564
38565     /**
38566      * @private
38567      * Reconfigures the meta data tied to this Reader
38568      */
38569     onMetaChange : function(meta) {
38570         var fields = meta.fields,
38571             newModel;
38572         
38573         Ext.apply(this, meta);
38574         
38575         if (fields) {
38576             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
38577                 extend: 'Ext.data.Model',
38578                 fields: fields
38579             });
38580             this.setModel(newModel, true);
38581         } else {
38582             this.buildExtractors(true);
38583         }
38584     },
38585     
38586     /**
38587      * Get the idProperty to use for extracting data
38588      * @private
38589      * @return {String} The id property
38590      */
38591     getIdProperty: function(){
38592         var prop = this.idProperty;
38593         if (Ext.isEmpty(prop)) {
38594             prop = this.model.prototype.idProperty;
38595         }
38596         return prop;
38597     },
38598
38599     /**
38600      * @private
38601      * This builds optimized functions for retrieving record data and meta data from an object.
38602      * Subclasses may need to implement their own getRoot function.
38603      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
38604      */
38605     buildExtractors: function(force) {
38606         var me          = this,
38607             idProp      = me.getIdProperty(),
38608             totalProp   = me.totalProperty,
38609             successProp = me.successProperty,
38610             messageProp = me.messageProperty,
38611             accessor;
38612             
38613         if (force === true) {
38614             delete me.extractorFunctions;
38615         }
38616         
38617         if (me.extractorFunctions) {
38618             return;
38619         }   
38620
38621         //build the extractors for all the meta data
38622         if (totalProp) {
38623             me.getTotal = me.createAccessor(totalProp);
38624         }
38625
38626         if (successProp) {
38627             me.getSuccess = me.createAccessor(successProp);
38628         }
38629
38630         if (messageProp) {
38631             me.getMessage = me.createAccessor(messageProp);
38632         }
38633
38634         if (idProp) {
38635             accessor = me.createAccessor(idProp);
38636
38637             me.getId = function(record) {
38638                 var id = accessor.call(me, record);
38639                 return (id === undefined || id === '') ? null : id;
38640             };
38641         } else {
38642             me.getId = function() {
38643                 return null;
38644             };
38645         }
38646         me.buildFieldExtractors();
38647     },
38648
38649     /**
38650      * @private
38651      */
38652     buildFieldExtractors: function() {
38653         //now build the extractors for all the fields
38654         var me = this,
38655             fields = me.getFields(),
38656             ln = fields.length,
38657             i  = 0,
38658             extractorFunctions = [],
38659             field, map;
38660
38661         for (; i < ln; i++) {
38662             field = fields[i];
38663             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
38664
38665             extractorFunctions.push(me.createAccessor(map));
38666         }
38667         me.fieldCount = ln;
38668
38669         me.extractorFunctions = extractorFunctions;
38670     }
38671 }, function() {
38672     Ext.apply(this, {
38673         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
38674         nullResultSet: Ext.create('Ext.data.ResultSet', {
38675             total  : 0,
38676             count  : 0,
38677             records: [],
38678             success: true
38679         })
38680     });
38681 });
38682 /**
38683  * @author Ed Spencer
38684  * @class Ext.data.reader.Json
38685  * @extends Ext.data.reader.Reader
38686  *
38687  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
38688  * happens as a result of loading a Store - for example we might create something like this:</p>
38689  *
38690 <pre><code>
38691 Ext.define('User', {
38692     extend: 'Ext.data.Model',
38693     fields: ['id', 'name', 'email']
38694 });
38695
38696 var store = Ext.create('Ext.data.Store', {
38697     model: 'User',
38698     proxy: {
38699         type: 'ajax',
38700         url : 'users.json',
38701         reader: {
38702             type: 'json'
38703         }
38704     }
38705 });
38706 </code></pre>
38707  *
38708  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
38709  * not already familiar with them.</p>
38710  *
38711  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
38712  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
38713  * Store, so it is as if we passed this instead:
38714  *
38715 <pre><code>
38716 reader: {
38717     type : 'json',
38718     model: 'User'
38719 }
38720 </code></pre>
38721  *
38722  * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
38723  *
38724 <pre><code>
38725 [
38726     {
38727         "id": 1,
38728         "name": "Ed Spencer",
38729         "email": "ed@sencha.com"
38730     },
38731     {
38732         "id": 2,
38733         "name": "Abe Elias",
38734         "email": "abe@sencha.com"
38735     }
38736 ]
38737 </code></pre>
38738  *
38739  * <p><u>Reading other JSON formats</u></p>
38740  *
38741  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
38742  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
38743  * {@link #root} configuration to parse data that comes back like this:</p>
38744  *
38745 <pre><code>
38746 {
38747     "users": [
38748        {
38749            "id": 1,
38750            "name": "Ed Spencer",
38751            "email": "ed@sencha.com"
38752        },
38753        {
38754            "id": 2,
38755            "name": "Abe Elias",
38756            "email": "abe@sencha.com"
38757        }
38758     ]
38759 }
38760 </code></pre>
38761  *
38762  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
38763  *
38764 <pre><code>
38765 reader: {
38766     type: 'json',
38767     root: 'users'
38768 }
38769 </code></pre>
38770  *
38771  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
38772  * around each record inside a nested structure like this:</p>
38773  *
38774 <pre><code>
38775 {
38776     "total": 122,
38777     "offset": 0,
38778     "users": [
38779         {
38780             "id": "ed-spencer-1",
38781             "value": 1,
38782             "user": {
38783                 "id": 1,
38784                 "name": "Ed Spencer",
38785                 "email": "ed@sencha.com"
38786             }
38787         }
38788     ]
38789 }
38790 </code></pre>
38791  *
38792  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
38793  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
38794  * JSON above we need to specify the {@link #record} configuration like this:</p>
38795  *
38796 <pre><code>
38797 reader: {
38798     type  : 'json',
38799     root  : 'users',
38800     record: 'user'
38801 }
38802 </code></pre>
38803  *
38804  * <p><u>Response metadata</u></p>
38805  *
38806  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
38807  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
38808  * like this:</p>
38809  *
38810 <pre><code>
38811 {
38812     "total": 100,
38813     "success": true,
38814     "users": [
38815         {
38816             "id": 1,
38817             "name": "Ed Spencer",
38818             "email": "ed@sencha.com"
38819         }
38820     ]
38821 }
38822 </code></pre>
38823  *
38824  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
38825  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
38826  * options:</p>
38827  *
38828 <pre><code>
38829 reader: {
38830     type : 'json',
38831     root : 'users',
38832     totalProperty  : 'total',
38833     successProperty: 'success'
38834 }
38835 </code></pre>
38836  *
38837  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
38838  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
38839  * returned.</p>
38840  */
38841 Ext.define('Ext.data.reader.Json', {
38842     extend: 'Ext.data.reader.Reader',
38843     alternateClassName: 'Ext.data.JsonReader',
38844     alias : 'reader.json',
38845
38846     root: '',
38847
38848     /**
38849      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
38850      * See the JsonReader intro docs for more details. This is not often needed.
38851      */
38852
38853     /**
38854      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
38855      * reading values. Defalts to <tt>false</tt>.
38856      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
38857      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
38858      * "foo.bar.baz" direct from the root object.
38859      */
38860     useSimpleAccessors: false,
38861
38862     /**
38863      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
38864      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
38865      * @param {Object} data The raw JSON data
38866      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
38867      */
38868     readRecords: function(data) {
38869         //this has to be before the call to super because we use the meta data in the superclass readRecords
38870         if (data.metaData) {
38871             this.onMetaChange(data.metaData);
38872         }
38873
38874         /**
38875          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
38876          * @property {Object} jsonData
38877          */
38878         this.jsonData = data;
38879         return this.callParent([data]);
38880     },
38881
38882     //inherit docs
38883     getResponseData: function(response) {
38884         var data;
38885         try {
38886             data = Ext.decode(response.responseText);
38887         }
38888         catch (ex) {
38889             Ext.Error.raise({
38890                 response: response,
38891                 json: response.responseText,
38892                 parseError: ex,
38893                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
38894             });
38895         }
38896
38897         return data;
38898     },
38899
38900     //inherit docs
38901     buildExtractors : function() {
38902         var me = this;
38903
38904         me.callParent(arguments);
38905
38906         if (me.root) {
38907             me.getRoot = me.createAccessor(me.root);
38908         } else {
38909             me.getRoot = function(root) {
38910                 return root;
38911             };
38912         }
38913     },
38914
38915     /**
38916      * @private
38917      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
38918      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
38919      * @param {Object} root The JSON root node
38920      * @return {Ext.data.Model[]} The records
38921      */
38922     extractData: function(root) {
38923         var recordName = this.record,
38924             data = [],
38925             length, i;
38926
38927         if (recordName) {
38928             length = root.length;
38929             
38930             if (!length && Ext.isObject(root)) {
38931                 length = 1;
38932                 root = [root];
38933             }
38934
38935             for (i = 0; i < length; i++) {
38936                 data[i] = root[i][recordName];
38937             }
38938         } else {
38939             data = root;
38940         }
38941         return this.callParent([data]);
38942     },
38943
38944     /**
38945      * @private
38946      * Returns an accessor function for the given property string. Gives support for properties such as the following:
38947      * 'someProperty'
38948      * 'some.property'
38949      * 'some["property"]'
38950      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
38951      */
38952     createAccessor: function() {
38953         var re = /[\[\.]/;
38954
38955         return function(expr) {
38956             if (Ext.isEmpty(expr)) {
38957                 return Ext.emptyFn;
38958             }
38959             if (Ext.isFunction(expr)) {
38960                 return expr;
38961             }
38962             if (this.useSimpleAccessors !== true) {
38963                 var i = String(expr).search(re);
38964                 if (i >= 0) {
38965                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
38966                 }
38967             }
38968             return function(obj) {
38969                 return obj[expr];
38970             };
38971         };
38972     }()
38973 });
38974 /**
38975  * @class Ext.data.writer.Json
38976  * @extends Ext.data.writer.Writer
38977
38978 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
38979 The {@link #allowSingle} configuration can be set to false to force the records to always be
38980 encoded in an array, even if there is only a single record being sent.
38981
38982  * @markdown
38983  */
38984 Ext.define('Ext.data.writer.Json', {
38985     extend: 'Ext.data.writer.Writer',
38986     alternateClassName: 'Ext.data.JsonWriter',
38987     alias: 'writer.json',
38988     
38989     /**
38990      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
38991      * Example generated request, using root: 'records':
38992 <pre><code>
38993 {'records': [{name: 'my record'}, {name: 'another record'}]}
38994 </code></pre>
38995      */
38996     root: undefined,
38997     
38998     /**
38999      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
39000      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
39001      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
39002      * sent to the server.
39003      */
39004     encode: false,
39005     
39006     /**
39007      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
39008      * one record being sent. When there is more than one record, they will always be encoded into an array.
39009      * Defaults to <tt>true</tt>. Example:
39010      * <pre><code>
39011 // with allowSingle: true
39012 "root": {
39013     "first": "Mark",
39014     "last": "Corrigan"
39015 }
39016
39017 // with allowSingle: false
39018 "root": [{
39019     "first": "Mark",
39020     "last": "Corrigan"
39021 }]
39022      * </code></pre>
39023      */
39024     allowSingle: true,
39025     
39026     //inherit docs
39027     writeRecords: function(request, data) {
39028         var root = this.root;
39029         
39030         if (this.allowSingle && data.length == 1) {
39031             // convert to single object format
39032             data = data[0];
39033         }
39034         
39035         if (this.encode) {
39036             if (root) {
39037                 // sending as a param, need to encode
39038                 request.params[root] = Ext.encode(data);
39039             } else {
39040             }
39041         } else {
39042             // send as jsonData
39043             request.jsonData = request.jsonData || {};
39044             if (root) {
39045                 request.jsonData[root] = data;
39046             } else {
39047                 request.jsonData = data;
39048             }
39049         }
39050         return request;
39051     }
39052 });
39053
39054 /**
39055  * @author Ed Spencer
39056  *
39057  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
39058  * data. Usually developers will not need to create or interact with proxies directly.
39059  *
39060  * # Types of Proxy
39061  *
39062  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
39063  * The Client proxies save their data locally and include the following subclasses:
39064  *
39065  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
39066  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
39067  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
39068  *
39069  * The Server proxies save their data by sending requests to some remote server. These proxies include:
39070  *
39071  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
39072  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
39073  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
39074  *
39075  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
39076  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
39077  * respectively. Each Proxy subclass implements these functions.
39078  *
39079  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
39080  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
39081  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
39082  * method also accepts a callback function to be called asynchronously on completion.
39083  *
39084  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
39085  * method.
39086  */
39087 Ext.define('Ext.data.proxy.Proxy', {
39088     alias: 'proxy.proxy',
39089     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
39090     requires: [
39091         'Ext.data.reader.Json',
39092         'Ext.data.writer.Json'
39093     ],
39094     uses: [
39095         'Ext.data.Batch', 
39096         'Ext.data.Operation', 
39097         'Ext.data.Model'
39098     ],
39099     mixins: {
39100         observable: 'Ext.util.Observable'
39101     },
39102     
39103     /**
39104      * @cfg {String} batchOrder
39105      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
39106      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
39107      */
39108     batchOrder: 'create,update,destroy',
39109     
39110     /**
39111      * @cfg {Boolean} batchActions
39112      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
39113      */
39114     batchActions: true,
39115     
39116     /**
39117      * @cfg {String} defaultReaderType
39118      * The default registered reader type. Defaults to 'json'.
39119      * @private
39120      */
39121     defaultReaderType: 'json',
39122     
39123     /**
39124      * @cfg {String} defaultWriterType
39125      * The default registered writer type. Defaults to 'json'.
39126      * @private
39127      */
39128     defaultWriterType: 'json',
39129     
39130     /**
39131      * @cfg {String/Ext.data.Model} model
39132      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
39133      * Model constructor. Required.
39134      */
39135     
39136     /**
39137      * @cfg {Object/String/Ext.data.reader.Reader} reader
39138      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
39139      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
39140      */
39141     
39142     /**
39143      * @cfg {Object/String/Ext.data.writer.Writer} writer
39144      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
39145      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
39146      */
39147     
39148     isProxy: true,
39149     
39150     /**
39151      * Creates the Proxy
39152      * @param {Object} config (optional) Config object.
39153      */
39154     constructor: function(config) {
39155         config = config || {};
39156         
39157         if (config.model === undefined) {
39158             delete config.model;
39159         }
39160
39161         this.mixins.observable.constructor.call(this, config);
39162         
39163         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
39164             this.setModel(this.model);
39165         }
39166     },
39167     
39168     /**
39169      * Sets the model associated with this proxy. This will only usually be called by a Store
39170      *
39171      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
39172      * or a reference to the model's constructor
39173      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
39174      */
39175     setModel: function(model, setOnStore) {
39176         this.model = Ext.ModelManager.getModel(model);
39177         
39178         var reader = this.reader,
39179             writer = this.writer;
39180         
39181         this.setReader(reader);
39182         this.setWriter(writer);
39183         
39184         if (setOnStore && this.store) {
39185             this.store.setModel(this.model);
39186         }
39187     },
39188     
39189     /**
39190      * Returns the model attached to this Proxy
39191      * @return {Ext.data.Model} The model
39192      */
39193     getModel: function() {
39194         return this.model;
39195     },
39196     
39197     /**
39198      * Sets the Proxy's Reader by string, config object or Reader instance
39199      *
39200      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
39201      * a configuration object or an Ext.data.reader.Reader instance
39202      * @return {Ext.data.reader.Reader} The attached Reader object
39203      */
39204     setReader: function(reader) {
39205         var me = this;
39206         
39207         if (reader === undefined || typeof reader == 'string') {
39208             reader = {
39209                 type: reader
39210             };
39211         }
39212
39213         if (reader.isReader) {
39214             reader.setModel(me.model);
39215         } else {
39216             Ext.applyIf(reader, {
39217                 proxy: me,
39218                 model: me.model,
39219                 type : me.defaultReaderType
39220             });
39221
39222             reader = Ext.createByAlias('reader.' + reader.type, reader);
39223         }
39224         
39225         me.reader = reader;
39226         return me.reader;
39227     },
39228     
39229     /**
39230      * Returns the reader currently attached to this proxy instance
39231      * @return {Ext.data.reader.Reader} The Reader instance
39232      */
39233     getReader: function() {
39234         return this.reader;
39235     },
39236     
39237     /**
39238      * Sets the Proxy's Writer by string, config object or Writer instance
39239      *
39240      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
39241      * a configuration object or an Ext.data.writer.Writer instance
39242      * @return {Ext.data.writer.Writer} The attached Writer object
39243      */
39244     setWriter: function(writer) {
39245         if (writer === undefined || typeof writer == 'string') {
39246             writer = {
39247                 type: writer
39248             };
39249         }
39250
39251         if (!(writer instanceof Ext.data.writer.Writer)) {
39252             Ext.applyIf(writer, {
39253                 model: this.model,
39254                 type : this.defaultWriterType
39255             });
39256
39257             writer = Ext.createByAlias('writer.' + writer.type, writer);
39258         }
39259         
39260         this.writer = writer;
39261         
39262         return this.writer;
39263     },
39264     
39265     /**
39266      * Returns the writer currently attached to this proxy instance
39267      * @return {Ext.data.writer.Writer} The Writer instance
39268      */
39269     getWriter: function() {
39270         return this.writer;
39271     },
39272     
39273     /**
39274      * Performs the given create operation.
39275      * @param {Ext.data.Operation} operation The Operation to perform
39276      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39277      * @param {Object} scope Scope to execute the callback function in
39278      * @method
39279      */
39280     create: Ext.emptyFn,
39281     
39282     /**
39283      * Performs the given read operation.
39284      * @param {Ext.data.Operation} operation The Operation to perform
39285      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39286      * @param {Object} scope Scope to execute the callback function in
39287      * @method
39288      */
39289     read: Ext.emptyFn,
39290     
39291     /**
39292      * Performs the given update operation.
39293      * @param {Ext.data.Operation} operation The Operation to perform
39294      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39295      * @param {Object} scope Scope to execute the callback function in
39296      * @method
39297      */
39298     update: Ext.emptyFn,
39299     
39300     /**
39301      * Performs the given destroy operation.
39302      * @param {Ext.data.Operation} operation The Operation to perform
39303      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39304      * @param {Object} scope Scope to execute the callback function in
39305      * @method
39306      */
39307     destroy: Ext.emptyFn,
39308     
39309     /**
39310      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
39311      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
39312      *
39313      *     myProxy.batch({
39314      *         create : [myModel1, myModel2],
39315      *         update : [myModel3],
39316      *         destroy: [myModel4, myModel5]
39317      *     });
39318      *
39319      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
39320      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
39321      * saved but should now be destroyed.
39322      *
39323      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
39324      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
39325      * see {@link Ext.data.Batch}
39326      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
39327      */
39328     batch: function(operations, listeners) {
39329         var me = this,
39330             batch = Ext.create('Ext.data.Batch', {
39331                 proxy: me,
39332                 listeners: listeners || {}
39333             }),
39334             useBatch = me.batchActions, 
39335             records;
39336         
39337         Ext.each(me.batchOrder.split(','), function(action) {
39338             records = operations[action];
39339             if (records) {
39340                 if (useBatch) {
39341                     batch.add(Ext.create('Ext.data.Operation', {
39342                         action: action,
39343                         records: records
39344                     }));
39345                 } else {
39346                     Ext.each(records, function(record){
39347                         batch.add(Ext.create('Ext.data.Operation', {
39348                             action : action, 
39349                             records: [record]
39350                         }));
39351                     });
39352                 }
39353             }
39354         }, me);
39355         
39356         batch.start();
39357         return batch;
39358     }
39359 }, function() {
39360     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
39361     
39362     //backwards compatibility
39363     Ext.data.DataProxy = this;
39364     // Ext.deprecate('platform', '2.0', function() {
39365     //     Ext.data.DataProxy = this;
39366     // }, this);
39367 });
39368
39369 /**
39370  * @author Ed Spencer
39371  *
39372  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
39373  * would not usually be used directly.
39374  *
39375  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
39376  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
39377  * an alias of AjaxProxy).
39378  * @private
39379  */
39380 Ext.define('Ext.data.proxy.Server', {
39381     extend: 'Ext.data.proxy.Proxy',
39382     alias : 'proxy.server',
39383     alternateClassName: 'Ext.data.ServerProxy',
39384     uses  : ['Ext.data.Request'],
39385
39386     /**
39387      * @cfg {String} url
39388      * The URL from which to request the data object.
39389      */
39390
39391     /**
39392      * @cfg {String} pageParam
39393      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
39394      * want to send a page parameter.
39395      */
39396     pageParam: 'page',
39397
39398     /**
39399      * @cfg {String} startParam
39400      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
39401      * want to send a start parameter.
39402      */
39403     startParam: 'start',
39404
39405     /**
39406      * @cfg {String} limitParam
39407      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
39408      * want to send a limit parameter.
39409      */
39410     limitParam: 'limit',
39411
39412     /**
39413      * @cfg {String} groupParam
39414      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
39415      * want to send a group parameter.
39416      */
39417     groupParam: 'group',
39418
39419     /**
39420      * @cfg {String} sortParam
39421      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
39422      * want to send a sort parameter.
39423      */
39424     sortParam: 'sort',
39425
39426     /**
39427      * @cfg {String} filterParam
39428      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
39429      * want to send a filter parameter.
39430      */
39431     filterParam: 'filter',
39432
39433     /**
39434      * @cfg {String} directionParam
39435      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
39436      * true.** Defaults to 'dir'.
39437      */
39438     directionParam: 'dir',
39439
39440     /**
39441      * @cfg {Boolean} simpleSortMode
39442      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
39443      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
39444      * or 'DESC'.
39445      */
39446     simpleSortMode: false,
39447
39448     /**
39449      * @cfg {Boolean} noCache
39450      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
39451      */
39452     noCache : true,
39453
39454     /**
39455      * @cfg {String} cacheString
39456      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
39457      */
39458     cacheString: "_dc",
39459
39460     /**
39461      * @cfg {Number} timeout
39462      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
39463      */
39464     timeout : 30000,
39465
39466     /**
39467      * @cfg {Object} api
39468      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
39469      *
39470      *     api: {
39471      *         create  : undefined,
39472      *         read    : undefined,
39473      *         update  : undefined,
39474      *         destroy : undefined
39475      *     }
39476      *
39477      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
39478      * {@link #api} property, or if undefined default to the configured
39479      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
39480      *
39481      * For example:
39482      *
39483      *     api: {
39484      *         create  : '/controller/new',
39485      *         read    : '/controller/load',
39486      *         update  : '/controller/update',
39487      *         destroy : '/controller/destroy_action'
39488      *     }
39489      *
39490      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
39491      * configured {@link Ext.data.proxy.Server#url url}.
39492      */
39493
39494     constructor: function(config) {
39495         var me = this;
39496
39497         config = config || {};
39498         this.addEvents(
39499             /**
39500              * @event exception
39501              * Fires when the server returns an exception
39502              * @param {Ext.data.proxy.Proxy} this
39503              * @param {Object} response The response from the AJAX request
39504              * @param {Ext.data.Operation} operation The operation that triggered request
39505              */
39506             'exception'
39507         );
39508         me.callParent([config]);
39509
39510         /**
39511          * @cfg {Object} extraParams
39512          * Extra parameters that will be included on every request. Individual requests with params of the same name
39513          * will override these params when they are in conflict.
39514          */
39515         me.extraParams = config.extraParams || {};
39516
39517         me.api = config.api || {};
39518
39519         //backwards compatibility, will be deprecated in 5.0
39520         me.nocache = me.noCache;
39521     },
39522
39523     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
39524     create: function() {
39525         return this.doRequest.apply(this, arguments);
39526     },
39527
39528     read: function() {
39529         return this.doRequest.apply(this, arguments);
39530     },
39531
39532     update: function() {
39533         return this.doRequest.apply(this, arguments);
39534     },
39535
39536     destroy: function() {
39537         return this.doRequest.apply(this, arguments);
39538     },
39539
39540     /**
39541      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
39542      * that this Proxy is attached to.
39543      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
39544      * @return {Ext.data.Request} The request object
39545      */
39546     buildRequest: function(operation) {
39547         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
39548             request;
39549
39550         //copy any sorters, filters etc into the params so they can be sent over the wire
39551         params = Ext.applyIf(params, this.getParams(operation));
39552
39553         if (operation.id && !params.id) {
39554             params.id = operation.id;
39555         }
39556
39557         request = Ext.create('Ext.data.Request', {
39558             params   : params,
39559             action   : operation.action,
39560             records  : operation.records,
39561             operation: operation,
39562             url      : operation.url
39563         });
39564
39565         request.url = this.buildUrl(request);
39566
39567         /*
39568          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
39569          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
39570          */
39571         operation.request = request;
39572
39573         return request;
39574     },
39575
39576     // Should this be documented as protected method?
39577     processResponse: function(success, operation, request, response, callback, scope){
39578         var me = this,
39579             reader,
39580             result;
39581
39582         if (success === true) {
39583             reader = me.getReader();
39584             result = reader.read(me.extractResponseData(response));
39585
39586             if (result.success !== false) {
39587                 //see comment in buildRequest for why we include the response object here
39588                 Ext.apply(operation, {
39589                     response: response,
39590                     resultSet: result
39591                 });
39592
39593                 operation.commitRecords(result.records);
39594                 operation.setCompleted();
39595                 operation.setSuccessful();
39596             } else {
39597                 operation.setException(result.message);
39598                 me.fireEvent('exception', this, response, operation);
39599             }
39600         } else {
39601             me.setException(operation, response);
39602             me.fireEvent('exception', this, response, operation);
39603         }
39604
39605         //this callback is the one that was passed to the 'read' or 'write' function above
39606         if (typeof callback == 'function') {
39607             callback.call(scope || me, operation);
39608         }
39609
39610         me.afterRequest(request, success);
39611     },
39612
39613     /**
39614      * Sets up an exception on the operation
39615      * @private
39616      * @param {Ext.data.Operation} operation The operation
39617      * @param {Object} response The response
39618      */
39619     setException: function(operation, response){
39620         operation.setException({
39621             status: response.status,
39622             statusText: response.statusText
39623         });
39624     },
39625
39626     /**
39627      * Template method to allow subclasses to specify how to get the response for the reader.
39628      * @template
39629      * @private
39630      * @param {Object} response The server response
39631      * @return {Object} The response data to be used by the reader
39632      */
39633     extractResponseData: function(response){
39634         return response;
39635     },
39636
39637     /**
39638      * Encode any values being sent to the server. Can be overridden in subclasses.
39639      * @private
39640      * @param {Array} An array of sorters/filters.
39641      * @return {Object} The encoded value
39642      */
39643     applyEncoding: function(value){
39644         return Ext.encode(value);
39645     },
39646
39647     /**
39648      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
39649      * this simply JSON-encodes the sorter data
39650      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
39651      * @return {String} The encoded sorters
39652      */
39653     encodeSorters: function(sorters) {
39654         var min = [],
39655             length = sorters.length,
39656             i = 0;
39657
39658         for (; i < length; i++) {
39659             min[i] = {
39660                 property : sorters[i].property,
39661                 direction: sorters[i].direction
39662             };
39663         }
39664         return this.applyEncoding(min);
39665
39666     },
39667
39668     /**
39669      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
39670      * this simply JSON-encodes the filter data
39671      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
39672      * @return {String} The encoded filters
39673      */
39674     encodeFilters: function(filters) {
39675         var min = [],
39676             length = filters.length,
39677             i = 0;
39678
39679         for (; i < length; i++) {
39680             min[i] = {
39681                 property: filters[i].property,
39682                 value   : filters[i].value
39683             };
39684         }
39685         return this.applyEncoding(min);
39686     },
39687
39688     /**
39689      * @private
39690      * Copy any sorters, filters etc into the params so they can be sent over the wire
39691      */
39692     getParams: function(operation) {
39693         var me             = this,
39694             params         = {},
39695             isDef          = Ext.isDefined,
39696             groupers       = operation.groupers,
39697             sorters        = operation.sorters,
39698             filters        = operation.filters,
39699             page           = operation.page,
39700             start          = operation.start,
39701             limit          = operation.limit,
39702
39703             simpleSortMode = me.simpleSortMode,
39704
39705             pageParam      = me.pageParam,
39706             startParam     = me.startParam,
39707             limitParam     = me.limitParam,
39708             groupParam     = me.groupParam,
39709             sortParam      = me.sortParam,
39710             filterParam    = me.filterParam,
39711             directionParam = me.directionParam;
39712
39713         if (pageParam && isDef(page)) {
39714             params[pageParam] = page;
39715         }
39716
39717         if (startParam && isDef(start)) {
39718             params[startParam] = start;
39719         }
39720
39721         if (limitParam && isDef(limit)) {
39722             params[limitParam] = limit;
39723         }
39724
39725         if (groupParam && groupers && groupers.length > 0) {
39726             // Grouper is a subclass of sorter, so we can just use the sorter method
39727             params[groupParam] = me.encodeSorters(groupers);
39728         }
39729
39730         if (sortParam && sorters && sorters.length > 0) {
39731             if (simpleSortMode) {
39732                 params[sortParam] = sorters[0].property;
39733                 params[directionParam] = sorters[0].direction;
39734             } else {
39735                 params[sortParam] = me.encodeSorters(sorters);
39736             }
39737
39738         }
39739
39740         if (filterParam && filters && filters.length > 0) {
39741             params[filterParam] = me.encodeFilters(filters);
39742         }
39743
39744         return params;
39745     },
39746
39747     /**
39748      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
39749      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
39750      * @param {Ext.data.Request} request The request object
39751      * @return {String} The url
39752      */
39753     buildUrl: function(request) {
39754         var me = this,
39755             url = me.getUrl(request);
39756
39757
39758         if (me.noCache) {
39759             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
39760         }
39761
39762         return url;
39763     },
39764
39765     /**
39766      * Get the url for the request taking into account the order of priority,
39767      * - The request
39768      * - The api
39769      * - The url
39770      * @private
39771      * @param {Ext.data.Request} request The request
39772      * @return {String} The url
39773      */
39774     getUrl: function(request){
39775         return request.url || this.api[request.action] || this.url;
39776     },
39777
39778     /**
39779      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
39780      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
39781      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
39782      * each of the methods that delegate to it.
39783      *
39784      * @param {Ext.data.Operation} operation The Ext.data.Operation object
39785      * @param {Function} callback The callback function to call when the Operation has completed
39786      * @param {Object} scope The scope in which to execute the callback
39787      */
39788     doRequest: function(operation, callback, scope) {
39789     },
39790
39791     /**
39792      * Optional callback function which can be used to clean up after a request has been completed.
39793      * @param {Ext.data.Request} request The Request object
39794      * @param {Boolean} success True if the request was successful
39795      * @method
39796      */
39797     afterRequest: Ext.emptyFn,
39798
39799     onDestroy: function() {
39800         Ext.destroy(this.reader, this.writer);
39801     }
39802 });
39803
39804 /**
39805  * @author Ed Spencer
39806  *
39807  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
39808  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
39809  * Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a {@link Ext.data.Model
39810  * Model}:
39811  *
39812  *     Ext.define('User', {
39813  *         extend: 'Ext.data.Model',
39814  *         fields: ['id', 'name', 'email']
39815  *     });
39816  *
39817  *     //The Store contains the AjaxProxy as an inline configuration
39818  *     var store = Ext.create('Ext.data.Store', {
39819  *         model: 'User',
39820  *         proxy: {
39821  *             type: 'ajax',
39822  *             url : 'users.json'
39823  *         }
39824  *     });
39825  *
39826  *     store.load();
39827  *
39828  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
39829  * the fields that we expect the server to return. Next we set up the Store itself, along with a
39830  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
39831  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
39832  * It's as if we'd done this:
39833  *
39834  *     new Ext.data.proxy.Ajax({
39835  *         url: 'users.json',
39836  *         model: 'User',
39837  *         reader: 'json'
39838  *     });
39839  *
39840  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
39841  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
39842  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
39843  *
39844  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
39845  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
39846  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
39847  * will be sent as a POST request).
39848  *
39849  * # Limitations
39850  *
39851  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
39852  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
39853  * talking to each other via AJAX.
39854  *
39855  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
39856  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
39857  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
39858  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
39859  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
39860  *
39861  * # Readers and Writers
39862  *
39863  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
39864  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
39865  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
39866  * Ext.data.reader.Reader Reader} instance:
39867  *
39868  *     var proxy = new Ext.data.proxy.Ajax({
39869  *         model: 'User',
39870  *         reader: {
39871  *             type: 'xml',
39872  *             root: 'users'
39873  *         }
39874  *     });
39875  *
39876  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
39877  *
39878  * # Url generation
39879  *
39880  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
39881  * each request. These are controlled with the following configuration options:
39882  *
39883  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
39884  * - {@link #sortParam} - controls how sort information is sent to the server
39885  * - {@link #groupParam} - controls how grouping information is sent to the server
39886  * - {@link #filterParam} - controls how filter information is sent to the server
39887  *
39888  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
39889  * the generated urls, let's say we're loading the Proxy with the following Operation:
39890  *
39891  *     var operation = new Ext.data.Operation({
39892  *         action: 'read',
39893  *         page  : 2
39894  *     });
39895  *
39896  * Now we'll issue the request for this Operation by calling {@link #read}:
39897  *
39898  *     var proxy = new Ext.data.proxy.Ajax({
39899  *         url: '/users'
39900  *     });
39901  *
39902  *     proxy.read(operation); //GET /users?page=2
39903  *
39904  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
39905  * to the server:
39906  *
39907  *     var proxy = new Ext.data.proxy.Ajax({
39908  *         url: '/users',
39909  *         pagePage: 'pageNumber'
39910  *     });
39911  *
39912  *     proxy.read(operation); //GET /users?pageNumber=2
39913  *
39914  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
39915  *
39916  *     var operation = new Ext.data.Operation({
39917  *         action: 'read',
39918  *         start : 50,
39919  *         limit : 25
39920  *     });
39921  *
39922  *     var proxy = new Ext.data.proxy.Ajax({
39923  *         url: '/users'
39924  *     });
39925  *
39926  *     proxy.read(operation); //GET /users?start=50&limit;=25
39927  *
39928  * Again we can customize this url:
39929  *
39930  *     var proxy = new Ext.data.proxy.Ajax({
39931  *         url: '/users',
39932  *         startParam: 'startIndex',
39933  *         limitParam: 'limitIndex'
39934  *     });
39935  *
39936  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
39937  *
39938  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
39939  * expressive Operation object:
39940  *
39941  *     var operation = new Ext.data.Operation({
39942  *         action: 'read',
39943  *         sorters: [
39944  *             new Ext.util.Sorter({
39945  *                 property : 'name',
39946  *                 direction: 'ASC'
39947  *             }),
39948  *             new Ext.util.Sorter({
39949  *                 property : 'age',
39950  *                 direction: 'DESC'
39951  *             })
39952  *         ],
39953  *         filters: [
39954  *             new Ext.util.Filter({
39955  *                 property: 'eyeColor',
39956  *                 value   : 'brown'
39957  *             })
39958  *         ]
39959  *     });
39960  *
39961  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
39962  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
39963  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
39964  *
39965  *     var proxy = new Ext.data.proxy.Ajax({
39966  *         url: '/users'
39967  *     });
39968  *
39969  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
39970  *
39971  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
39972  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
39973  * that format like this:
39974  *
39975  *      var proxy = new Ext.data.proxy.Ajax({
39976  *          url: '/users',
39977  *          sortParam: 'sortBy',
39978  *          filterParam: 'filterBy',
39979  *
39980  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
39981  *          encodeSorters: function(sorters) {
39982  *              var length   = sorters.length,
39983  *                  sortStrs = [],
39984  *                  sorter, i;
39985  *
39986  *              for (i = 0; i < length; i++) {
39987  *                  sorter = sorters[i];
39988  *
39989  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
39990  *              }
39991  *
39992  *              return sortStrs.join(",");
39993  *          }
39994  *      });
39995  *
39996  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
39997  *
39998  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
39999  *
40000  * @constructor
40001  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
40002  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
40003  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
40004  *
40005  * @param {Object} config (optional) Config object.
40006  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
40007  */
40008 Ext.define('Ext.data.proxy.Ajax', {
40009     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
40010     extend: 'Ext.data.proxy.Server',
40011     alias: 'proxy.ajax',
40012     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
40013     
40014     /**
40015      * @property {Object} actionMethods
40016      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
40017      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
40018      * correct RESTful methods.
40019      */
40020     actionMethods: {
40021         create : 'POST',
40022         read   : 'GET',
40023         update : 'POST',
40024         destroy: 'POST'
40025     },
40026     
40027     /**
40028      * @cfg {Object} headers
40029      * Any headers to add to the Ajax request. Defaults to undefined.
40030      */
40031     
40032     /**
40033      * @ignore
40034      */
40035     doRequest: function(operation, callback, scope) {
40036         var writer  = this.getWriter(),
40037             request = this.buildRequest(operation, callback, scope);
40038             
40039         if (operation.allowWrite()) {
40040             request = writer.write(request);
40041         }
40042         
40043         Ext.apply(request, {
40044             headers       : this.headers,
40045             timeout       : this.timeout,
40046             scope         : this,
40047             callback      : this.createRequestCallback(request, operation, callback, scope),
40048             method        : this.getMethod(request),
40049             disableCaching: false // explicitly set it to false, ServerProxy handles caching
40050         });
40051         
40052         Ext.Ajax.request(request);
40053         
40054         return request;
40055     },
40056     
40057     /**
40058      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
40059      * {@link #actionMethods}.
40060      * @param {Ext.data.Request} request The request object
40061      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
40062      */
40063     getMethod: function(request) {
40064         return this.actionMethods[request.action];
40065     },
40066     
40067     /**
40068      * @private
40069      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
40070      * of code duplication inside the returned function so we need to find a way to DRY this up.
40071      * @param {Ext.data.Request} request The Request object
40072      * @param {Ext.data.Operation} operation The Operation being executed
40073      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
40074      * passed to doRequest
40075      * @param {Object} scope The scope in which to execute the callback function
40076      * @return {Function} The callback function
40077      */
40078     createRequestCallback: function(request, operation, callback, scope) {
40079         var me = this;
40080         
40081         return function(options, success, response) {
40082             me.processResponse(success, operation, request, response, callback, scope);
40083         };
40084     }
40085 }, function() {
40086     //backwards compatibility, remove in Ext JS 5.0
40087     Ext.data.HttpProxy = this;
40088 });
40089
40090 /**
40091  * @author Ed Spencer
40092  *
40093  * A Model represents some object that your application manages. For example, one might define a Model for Users,
40094  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
40095  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
40096  * of the data-bound components in Ext.
40097  *
40098  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
40099  *
40100  *     Ext.define('User', {
40101  *         extend: 'Ext.data.Model',
40102  *         fields: [
40103  *             {name: 'name',  type: 'string'},
40104  *             {name: 'age',   type: 'int'},
40105  *             {name: 'phone', type: 'string'},
40106  *             {name: 'alive', type: 'boolean', defaultValue: true}
40107  *         ],
40108  *
40109  *         changeName: function() {
40110  *             var oldName = this.get('name'),
40111  *                 newName = oldName + " The Barbarian";
40112  *
40113  *             this.set('name', newName);
40114  *         }
40115  *     });
40116  *
40117  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
40118  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
40119  *
40120  * Now we can create instances of our User model and call any model logic we defined:
40121  *
40122  *     var user = Ext.create('User', {
40123  *         name : 'Conan',
40124  *         age  : 24,
40125  *         phone: '555-555-5555'
40126  *     });
40127  *
40128  *     user.changeName();
40129  *     user.get('name'); //returns "Conan The Barbarian"
40130  *
40131  * # Validations
40132  *
40133  * Models have built-in support for validations, which are executed against the validator functions in {@link
40134  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
40135  * models:
40136  *
40137  *     Ext.define('User', {
40138  *         extend: 'Ext.data.Model',
40139  *         fields: [
40140  *             {name: 'name',     type: 'string'},
40141  *             {name: 'age',      type: 'int'},
40142  *             {name: 'phone',    type: 'string'},
40143  *             {name: 'gender',   type: 'string'},
40144  *             {name: 'username', type: 'string'},
40145  *             {name: 'alive',    type: 'boolean', defaultValue: true}
40146  *         ],
40147  *
40148  *         validations: [
40149  *             {type: 'presence',  field: 'age'},
40150  *             {type: 'length',    field: 'name',     min: 2},
40151  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
40152  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
40153  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
40154  *         ]
40155  *     });
40156  *
40157  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
40158  * object:
40159  *
40160  *     var instance = Ext.create('User', {
40161  *         name: 'Ed',
40162  *         gender: 'Male',
40163  *         username: 'edspencer'
40164  *     });
40165  *
40166  *     var errors = instance.validate();
40167  *
40168  * # Associations
40169  *
40170  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
40171  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
40172  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
40173  * this:
40174  *
40175  *     Ext.define('Post', {
40176  *         extend: 'Ext.data.Model',
40177  *         fields: ['id', 'user_id'],
40178  *
40179  *         belongsTo: 'User',
40180  *         hasMany  : {model: 'Comment', name: 'comments'}
40181  *     });
40182  *
40183  *     Ext.define('Comment', {
40184  *         extend: 'Ext.data.Model',
40185  *         fields: ['id', 'user_id', 'post_id'],
40186  *
40187  *         belongsTo: 'Post'
40188  *     });
40189  *
40190  *     Ext.define('User', {
40191  *         extend: 'Ext.data.Model',
40192  *         fields: ['id'],
40193  *
40194  *         hasMany: [
40195  *             'Post',
40196  *             {model: 'Comment', name: 'comments'}
40197  *         ]
40198  *     });
40199  *
40200  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
40201  * usage and configuration of associations. Note that associations can also be specified like this:
40202  *
40203  *     Ext.define('User', {
40204  *         extend: 'Ext.data.Model',
40205  *         fields: ['id'],
40206  *
40207  *         associations: [
40208  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
40209  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
40210  *         ]
40211  *     });
40212  *
40213  * # Using a Proxy
40214  *
40215  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
40216  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
40217  * can be set directly on the Model:
40218  *
40219  *     Ext.define('User', {
40220  *         extend: 'Ext.data.Model',
40221  *         fields: ['id', 'name', 'email'],
40222  *
40223  *         proxy: {
40224  *             type: 'rest',
40225  *             url : '/users'
40226  *         }
40227  *     });
40228  *
40229  * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
40230  * RESTful backend. Let's see how this works:
40231  *
40232  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
40233  *
40234  *     user.save(); //POST /users
40235  *
40236  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
40237  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
40238  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
40239  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
40240  *
40241  * Loading data via the Proxy is equally easy:
40242  *
40243  *     //get a reference to the User model class
40244  *     var User = Ext.ModelManager.getModel('User');
40245  *
40246  *     //Uses the configured RestProxy to make a GET request to /users/123
40247  *     User.load(123, {
40248  *         success: function(user) {
40249  *             console.log(user.getId()); //logs 123
40250  *         }
40251  *     });
40252  *
40253  * Models can also be updated and destroyed easily:
40254  *
40255  *     //the user Model we loaded in the last snippet:
40256  *     user.set('name', 'Edward Spencer');
40257  *
40258  *     //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
40259  *     user.save({
40260  *         success: function() {
40261  *             console.log('The User was updated');
40262  *         }
40263  *     });
40264  *
40265  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
40266  *     user.destroy({
40267  *         success: function() {
40268  *             console.log('The User was destroyed!');
40269  *         }
40270  *     });
40271  *
40272  * # Usage in Stores
40273  *
40274  * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
40275  * creating a {@link Ext.data.Store Store}:
40276  *
40277  *     var store = Ext.create('Ext.data.Store', {
40278  *         model: 'User'
40279  *     });
40280  *
40281  *     //uses the Proxy we set up on Model to load the Store data
40282  *     store.load();
40283  *
40284  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
40285  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
40286  * Ext.data.Store Store docs} for more information on Stores.
40287  *
40288  * @constructor
40289  * Creates new Model instance.
40290  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
40291  * @param {Number} id (optional) Unique ID to assign to this model instance
40292  */
40293 Ext.define('Ext.data.Model', {
40294     alternateClassName: 'Ext.data.Record',
40295
40296     mixins: {
40297         observable: 'Ext.util.Observable'
40298     },
40299
40300     requires: [
40301         'Ext.ModelManager',
40302         'Ext.data.IdGenerator',
40303         'Ext.data.Field',
40304         'Ext.data.Errors',
40305         'Ext.data.Operation',
40306         'Ext.data.validations',
40307         'Ext.data.proxy.Ajax',
40308         'Ext.util.MixedCollection'
40309     ],
40310
40311     onClassExtended: function(cls, data) {
40312         var onBeforeClassCreated = data.onBeforeClassCreated;
40313
40314         data.onBeforeClassCreated = function(cls, data) {
40315             var me = this,
40316                 name = Ext.getClassName(cls),
40317                 prototype = cls.prototype,
40318                 superCls = cls.prototype.superclass,
40319
40320                 validations = data.validations || [],
40321                 fields = data.fields || [],
40322                 associations = data.associations || [],
40323                 belongsTo = data.belongsTo,
40324                 hasMany = data.hasMany,
40325                 idgen = data.idgen,
40326
40327                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
40328                     return field.name;
40329                 }),
40330
40331                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
40332                     return association.name;
40333                 }),
40334
40335                 superValidations = superCls.validations,
40336                 superFields = superCls.fields,
40337                 superAssociations = superCls.associations,
40338
40339                 association, i, ln,
40340                 dependencies = [];
40341
40342             // Save modelName on class and its prototype
40343             cls.modelName = name;
40344             prototype.modelName = name;
40345
40346             // Merge the validations of the superclass and the new subclass
40347             if (superValidations) {
40348                 validations = superValidations.concat(validations);
40349             }
40350
40351             data.validations = validations;
40352
40353             // Merge the fields of the superclass and the new subclass
40354             if (superFields) {
40355                 fields = superFields.items.concat(fields);
40356             }
40357
40358             for (i = 0, ln = fields.length; i < ln; ++i) {
40359                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
40360             }
40361
40362             data.fields = fieldsMixedCollection;
40363
40364             if (idgen) {
40365                 data.idgen = Ext.data.IdGenerator.get(idgen);
40366             }
40367
40368             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
40369             //we support that here
40370             if (belongsTo) {
40371                 belongsTo = Ext.Array.from(belongsTo);
40372
40373                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
40374                     association = belongsTo[i];
40375
40376                     if (!Ext.isObject(association)) {
40377                         association = {model: association};
40378                     }
40379
40380                     association.type = 'belongsTo';
40381                     associations.push(association);
40382                 }
40383
40384                 delete data.belongsTo;
40385             }
40386
40387             if (hasMany) {
40388                 hasMany = Ext.Array.from(hasMany);
40389                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
40390                     association = hasMany[i];
40391
40392                     if (!Ext.isObject(association)) {
40393                         association = {model: association};
40394                     }
40395
40396                     association.type = 'hasMany';
40397                     associations.push(association);
40398                 }
40399
40400                 delete data.hasMany;
40401             }
40402
40403             if (superAssociations) {
40404                 associations = superAssociations.items.concat(associations);
40405             }
40406
40407             for (i = 0, ln = associations.length; i < ln; ++i) {
40408                 dependencies.push('association.' + associations[i].type.toLowerCase());
40409             }
40410
40411             if (data.proxy) {
40412                 if (typeof data.proxy === 'string') {
40413                     dependencies.push('proxy.' + data.proxy);
40414                 }
40415                 else if (typeof data.proxy.type === 'string') {
40416                     dependencies.push('proxy.' + data.proxy.type);
40417                 }
40418             }
40419
40420             Ext.require(dependencies, function() {
40421                 Ext.ModelManager.registerType(name, cls);
40422
40423                 for (i = 0, ln = associations.length; i < ln; ++i) {
40424                     association = associations[i];
40425
40426                     Ext.apply(association, {
40427                         ownerModel: name,
40428                         associatedModel: association.model
40429                     });
40430
40431                     if (Ext.ModelManager.getModel(association.model) === undefined) {
40432                         Ext.ModelManager.registerDeferredAssociation(association);
40433                     } else {
40434                         associationsMixedCollection.add(Ext.data.Association.create(association));
40435                     }
40436                 }
40437
40438                 data.associations = associationsMixedCollection;
40439
40440                 onBeforeClassCreated.call(me, cls, data);
40441
40442                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
40443
40444                 // Fire the onModelDefined template method on ModelManager
40445                 Ext.ModelManager.onModelDefined(cls);
40446             });
40447         };
40448     },
40449
40450     inheritableStatics: {
40451         /**
40452          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
40453          * {@link Ext#createByAlias Ext.createByAlias}.
40454          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
40455          * @return {Ext.data.proxy.Proxy}
40456          * @static
40457          * @inheritable
40458          */
40459         setProxy: function(proxy) {
40460             //make sure we have an Ext.data.proxy.Proxy object
40461             if (!proxy.isProxy) {
40462                 if (typeof proxy == "string") {
40463                     proxy = {
40464                         type: proxy
40465                     };
40466                 }
40467                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
40468             }
40469             proxy.setModel(this);
40470             this.proxy = this.prototype.proxy = proxy;
40471
40472             return proxy;
40473         },
40474
40475         /**
40476          * Returns the configured Proxy for this Model
40477          * @return {Ext.data.proxy.Proxy} The proxy
40478          * @static
40479          * @inheritable
40480          */
40481         getProxy: function() {
40482             return this.proxy;
40483         },
40484
40485         /**
40486          * Asynchronously loads a model instance by id. Sample usage:
40487          *
40488          *     MyApp.User = Ext.define('User', {
40489          *         extend: 'Ext.data.Model',
40490          *         fields: [
40491          *             {name: 'id', type: 'int'},
40492          *             {name: 'name', type: 'string'}
40493          *         ]
40494          *     });
40495          *
40496          *     MyApp.User.load(10, {
40497          *         scope: this,
40498          *         failure: function(record, operation) {
40499          *             //do something if the load failed
40500          *         },
40501          *         success: function(record, operation) {
40502          *             //do something if the load succeeded
40503          *         },
40504          *         callback: function(record, operation) {
40505          *             //do something whether the load succeeded or failed
40506          *         }
40507          *     });
40508          *
40509          * @param {Number} id The id of the model to load
40510          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
40511          * optional scope
40512          * @static
40513          * @inheritable
40514          */
40515         load: function(id, config) {
40516             config = Ext.apply({}, config);
40517             config = Ext.applyIf(config, {
40518                 action: 'read',
40519                 id    : id
40520             });
40521
40522             var operation  = Ext.create('Ext.data.Operation', config),
40523                 scope      = config.scope || this,
40524                 record     = null,
40525                 callback;
40526
40527             callback = function(operation) {
40528                 if (operation.wasSuccessful()) {
40529                     record = operation.getRecords()[0];
40530                     Ext.callback(config.success, scope, [record, operation]);
40531                 } else {
40532                     Ext.callback(config.failure, scope, [record, operation]);
40533                 }
40534                 Ext.callback(config.callback, scope, [record, operation]);
40535             };
40536
40537             this.proxy.read(operation, callback, this);
40538         }
40539     },
40540
40541     statics: {
40542         PREFIX : 'ext-record',
40543         AUTO_ID: 1,
40544         EDIT   : 'edit',
40545         REJECT : 'reject',
40546         COMMIT : 'commit',
40547
40548         /**
40549          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
40550          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
40551          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
40552          *
40553          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
40554          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
40555          *
40556          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
40557          * @return {String} auto-generated string id, `"ext-record-i++"`;
40558          * @static
40559          */
40560         id: function(rec) {
40561             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
40562             rec.phantom = true;
40563             rec.internalId = id;
40564             return id;
40565         }
40566     },
40567
40568     /**
40569      * @cfg {String/Object} idgen
40570      * The id generator to use for this model. The default id generator does not generate
40571      * values for the {@link #idProperty}.
40572      *
40573      * This can be overridden at the model level to provide a custom generator for a model.
40574      * The simplest form of this would be:
40575      *
40576      *      Ext.define('MyApp.data.MyModel', {
40577      *          extend: 'Ext.data.Model',
40578      *          requires: ['Ext.data.SequentialIdGenerator'],
40579      *          idgen: 'sequential',
40580      *          ...
40581      *      });
40582      *
40583      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
40584      * as 1, 2, 3 etc..
40585      *
40586      * Another useful id generator is {@link Ext.data.UuidGenerator}:
40587      *
40588      *      Ext.define('MyApp.data.MyModel', {
40589      *          extend: 'Ext.data.Model',
40590      *          requires: ['Ext.data.UuidGenerator'],
40591      *          idgen: 'uuid',
40592      *          ...
40593      *      });
40594      *
40595      * An id generation can also be further configured:
40596      *
40597      *      Ext.define('MyApp.data.MyModel', {
40598      *          extend: 'Ext.data.Model',
40599      *          idgen: {
40600      *              type: 'sequential',
40601      *              seed: 1000,
40602      *              prefix: 'ID_'
40603      *          }
40604      *      });
40605      *
40606      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
40607      *
40608      * If multiple models share an id space, a single generator can be shared:
40609      *
40610      *      Ext.define('MyApp.data.MyModelX', {
40611      *          extend: 'Ext.data.Model',
40612      *          idgen: {
40613      *              type: 'sequential',
40614      *              id: 'xy'
40615      *          }
40616      *      });
40617      *
40618      *      Ext.define('MyApp.data.MyModelY', {
40619      *          extend: 'Ext.data.Model',
40620      *          idgen: {
40621      *              type: 'sequential',
40622      *              id: 'xy'
40623      *          }
40624      *      });
40625      *
40626      * For more complex, shared id generators, a custom generator is the best approach.
40627      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
40628      *
40629      * @markdown
40630      */
40631     idgen: {
40632         isGenerator: true,
40633         type: 'default',
40634
40635         generate: function () {
40636             return null;
40637         },
40638         getRecId: function (rec) {
40639             return rec.modelName + '-' + rec.internalId;
40640         }
40641     },
40642
40643     /**
40644      * @property {Boolean} editing
40645      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
40646      */
40647     editing : false,
40648
40649     /**
40650      * @property {Boolean} dirty
40651      * True if this Record has been modified. Read-only.
40652      */
40653     dirty : false,
40654
40655     /**
40656      * @cfg {String} persistenceProperty
40657      * The property on this Persistable object that its data is saved to. Defaults to 'data'
40658      * (e.g. all persistable data resides in this.data.)
40659      */
40660     persistenceProperty: 'data',
40661
40662     evented: false,
40663     isModel: true,
40664
40665     /**
40666      * @property {Boolean} phantom
40667      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
40668      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
40669      */
40670     phantom : false,
40671
40672     /**
40673      * @cfg {String} idProperty
40674      * The name of the field treated as this Model's unique id. Defaults to 'id'.
40675      */
40676     idProperty: 'id',
40677
40678     /**
40679      * @cfg {String} defaultProxyType
40680      * The string type of the default Model Proxy. Defaults to 'ajax'.
40681      */
40682     defaultProxyType: 'ajax',
40683
40684     // Fields config and property
40685     /**
40686      * @cfg {Object[]/String[]} fields
40687      * The fields for this model.
40688      */
40689     /**
40690      * @property {Ext.util.MixedCollection} fields
40691      * The fields defined on this model.
40692      */
40693
40694     /**
40695      * @cfg {Object[]} validations
40696      * An array of {@link Ext.data.validations validations} for this model.
40697      */
40698
40699     // Associations configs and properties
40700     /**
40701      * @cfg {Object[]} associations
40702      * An array of {@link Ext.data.Association associations} for this model.
40703      */
40704     /**
40705      * @cfg {String/Object/String[]/Object[]} hasMany
40706      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
40707      */
40708     /**
40709      * @cfg {String/Object/String[]/Object[]} belongsTo
40710      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
40711      */
40712     /**
40713      * @property {Ext.util.MixedCollection} associations
40714      * {@link Ext.data.Association Associations} defined on this model.
40715      */
40716
40717     /**
40718      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
40719      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
40720      */
40721
40722     // raw not documented intentionally, meant to be used internally.
40723     constructor: function(data, id, raw) {
40724         data = data || {};
40725
40726         var me = this,
40727             fields,
40728             length,
40729             field,
40730             name,
40731             i,
40732             newId,
40733             isArray = Ext.isArray(data),
40734             newData = isArray ? {} : null; // to hold mapped array data if needed
40735
40736         /**
40737          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
40738          * @property internalId
40739          * @type String
40740          * @private
40741          */
40742         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
40743
40744         /**
40745          * @property {Object} raw The raw data used to create this model if created via a reader.
40746          */
40747         me.raw = raw;
40748
40749         Ext.applyIf(me, {
40750             data: {}
40751         });
40752
40753         /**
40754          * @property {Object} modified Key: value pairs of all fields whose values have changed
40755          */
40756         me.modified = {};
40757
40758         // Deal with spelling error in previous releases
40759         if (me.persistanceProperty) {
40760             me.persistenceProperty = me.persistanceProperty;
40761         }
40762         me[me.persistenceProperty] = {};
40763
40764         me.mixins.observable.constructor.call(me);
40765
40766         //add default field values if present
40767         fields = me.fields.items;
40768         length = fields.length;
40769
40770         for (i = 0; i < length; i++) {
40771             field = fields[i];
40772             name  = field.name;
40773
40774             if (isArray){
40775                 // Have to map array data so the values get assigned to the named fields
40776                 // rather than getting set as the field names with undefined values.
40777                 newData[name] = data[i];
40778             }
40779             else if (data[name] === undefined) {
40780                 data[name] = field.defaultValue;
40781             }
40782         }
40783
40784         me.set(newData || data);
40785
40786         if (me.getId()) {
40787             me.phantom = false;
40788         } else if (me.phantom) {
40789             newId = me.idgen.generate();
40790             if (newId !== null) {
40791                 me.setId(newId);
40792             }
40793         }
40794
40795         // clear any dirty/modified since we're initializing
40796         me.dirty = false;
40797         me.modified = {};
40798
40799         if (typeof me.init == 'function') {
40800             me.init();
40801         }
40802
40803         me.id = me.idgen.getRecId(me);
40804     },
40805
40806     /**
40807      * Returns the value of the given field
40808      * @param {String} fieldName The field to fetch the value for
40809      * @return {Object} The value
40810      */
40811     get: function(field) {
40812         return this[this.persistenceProperty][field];
40813     },
40814
40815     /**
40816      * Sets the given field to the given value, marks the instance as dirty
40817      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
40818      * @param {Object} value The value to set
40819      */
40820     set: function(fieldName, value) {
40821         var me = this,
40822             fields = me.fields,
40823             modified = me.modified,
40824             convertFields = [],
40825             field, key, i, currentValue, notEditing, count, length;
40826
40827         /*
40828          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
40829          * set those last so that all other possible data is set before the convert function is called
40830          */
40831         if (arguments.length == 1 && Ext.isObject(fieldName)) {
40832             notEditing = !me.editing;
40833             count = 0;
40834             for (key in fieldName) {
40835                 if (fieldName.hasOwnProperty(key)) {
40836
40837                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
40838                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
40839                     field = fields.get(key);
40840                     if (field && field.convert !== field.type.convert) {
40841                         convertFields.push(key);
40842                         continue;
40843                     }
40844
40845                     if (!count && notEditing) {
40846                         me.beginEdit();
40847                     }
40848                     ++count;
40849                     me.set(key, fieldName[key]);
40850                 }
40851             }
40852
40853             length = convertFields.length;
40854             if (length) {
40855                 if (!count && notEditing) {
40856                     me.beginEdit();
40857                 }
40858                 count += length;
40859                 for (i = 0; i < length; i++) {
40860                     field = convertFields[i];
40861                     me.set(field, fieldName[field]);
40862                 }
40863             }
40864
40865             if (notEditing && count) {
40866                 me.endEdit();
40867             }
40868         } else {
40869             if (fields) {
40870                 field = fields.get(fieldName);
40871
40872                 if (field && field.convert) {
40873                     value = field.convert(value, me);
40874                 }
40875             }
40876             currentValue = me.get(fieldName);
40877             me[me.persistenceProperty][fieldName] = value;
40878
40879             if (field && field.persist && !me.isEqual(currentValue, value)) {
40880                 if (me.isModified(fieldName)) {
40881                     if (me.isEqual(modified[fieldName], value)) {
40882                         // the original value in me.modified equals the new value, so the
40883                         // field is no longer modified
40884                         delete modified[fieldName];
40885                         // we might have removed the last modified field, so check to see if
40886                         // there are any modified fields remaining and correct me.dirty:
40887                         me.dirty = false;
40888                         for (key in modified) {
40889                             if (modified.hasOwnProperty(key)){
40890                                 me.dirty = true;
40891                                 break;
40892                             }
40893                         }
40894                     }
40895                 } else {
40896                     me.dirty = true;
40897                     modified[fieldName] = currentValue;
40898                 }
40899             }
40900
40901             if (!me.editing) {
40902                 me.afterEdit();
40903             }
40904         }
40905     },
40906
40907     /**
40908      * Checks if two values are equal, taking into account certain
40909      * special factors, for example dates.
40910      * @private
40911      * @param {Object} a The first value
40912      * @param {Object} b The second value
40913      * @return {Boolean} True if the values are equal
40914      */
40915     isEqual: function(a, b){
40916         if (Ext.isDate(a) && Ext.isDate(b)) {
40917             return a.getTime() === b.getTime();
40918         }
40919         return a === b;
40920     },
40921
40922     /**
40923      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
40924      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
40925      */
40926     beginEdit : function(){
40927         var me = this;
40928         if (!me.editing) {
40929             me.editing = true;
40930             me.dirtySave = me.dirty;
40931             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
40932             me.modifiedSave = Ext.apply({}, me.modified);
40933         }
40934     },
40935
40936     /**
40937      * Cancels all changes made in the current edit operation.
40938      */
40939     cancelEdit : function(){
40940         var me = this;
40941         if (me.editing) {
40942             me.editing = false;
40943             // reset the modified state, nothing changed since the edit began
40944             me.modified = me.modifiedSave;
40945             me[me.persistenceProperty] = me.dataSave;
40946             me.dirty = me.dirtySave;
40947             delete me.modifiedSave;
40948             delete me.dataSave;
40949             delete me.dirtySave;
40950         }
40951     },
40952
40953     /**
40954      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
40955      * fire).
40956      * @param {Boolean} silent True to not notify the store of the change
40957      */
40958     endEdit : function(silent){
40959         var me = this,
40960             didChange;
40961             
40962         if (me.editing) {
40963             me.editing = false;
40964             didChange = me.dirty || me.changedWhileEditing();
40965             delete me.modifiedSave;
40966             delete me.dataSave;
40967             delete me.dirtySave;
40968             if (silent !== true && didChange) {
40969                 me.afterEdit();
40970             }
40971         }
40972     },
40973     
40974     /**
40975      * Checks if the underlying data has changed during an edit. This doesn't necessarily
40976      * mean the record is dirty, however we still need to notify the store since it may need
40977      * to update any views.
40978      * @private
40979      * @return {Boolean} True if the underlying data has changed during an edit.
40980      */
40981     changedWhileEditing: function(){
40982         var me = this,
40983             saved = me.dataSave,
40984             data = me[me.persistenceProperty],
40985             key;
40986             
40987         for (key in data) {
40988             if (data.hasOwnProperty(key)) {
40989                 if (!me.isEqual(data[key], saved[key])) {
40990                     return true;
40991                 }
40992             }
40993         }
40994         return false; 
40995     },
40996
40997     /**
40998      * Gets a hash of only the fields that have been modified since this Model was created or commited.
40999      * @return {Object}
41000      */
41001     getChanges : function(){
41002         var modified = this.modified,
41003             changes  = {},
41004             field;
41005
41006         for (field in modified) {
41007             if (modified.hasOwnProperty(field)){
41008                 changes[field] = this.get(field);
41009             }
41010         }
41011
41012         return changes;
41013     },
41014
41015     /**
41016      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
41017      * @param {String} fieldName {@link Ext.data.Field#name}
41018      * @return {Boolean}
41019      */
41020     isModified : function(fieldName) {
41021         return this.modified.hasOwnProperty(fieldName);
41022     },
41023
41024     /**
41025      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
41026      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
41027      *
41028      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
41029      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
41030      */
41031     setDirty : function() {
41032         var me = this,
41033             name;
41034
41035         me.dirty = true;
41036
41037         me.fields.each(function(field) {
41038             if (field.persist) {
41039                 name = field.name;
41040                 me.modified[name] = me.get(name);
41041             }
41042         }, me);
41043     },
41044
41045
41046     /**
41047      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
41048      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
41049      * reverted to their original values.
41050      *
41051      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
41052      * operations.
41053      *
41054      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41055      * Defaults to false.
41056      */
41057     reject : function(silent) {
41058         var me = this,
41059             modified = me.modified,
41060             field;
41061
41062         for (field in modified) {
41063             if (modified.hasOwnProperty(field)) {
41064                 if (typeof modified[field] != "function") {
41065                     me[me.persistenceProperty][field] = modified[field];
41066                 }
41067             }
41068         }
41069
41070         me.dirty = false;
41071         me.editing = false;
41072         me.modified = {};
41073
41074         if (silent !== true) {
41075             me.afterReject();
41076         }
41077     },
41078
41079     /**
41080      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
41081      * instance since either creation or the last commit operation.
41082      *
41083      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
41084      * operations.
41085      *
41086      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41087      * Defaults to false.
41088      */
41089     commit : function(silent) {
41090         var me = this;
41091
41092         me.phantom = me.dirty = me.editing = false;
41093         me.modified = {};
41094
41095         if (silent !== true) {
41096             me.afterCommit();
41097         }
41098     },
41099
41100     /**
41101      * Creates a copy (clone) of this Model instance.
41102      *
41103      * @param {String} [id] A new id, defaults to the id of the instance being copied.
41104      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
41105      *
41106      *     var rec = record.copy(); // clone the record
41107      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
41108      *
41109      * @return {Ext.data.Model}
41110      */
41111     copy : function(newId) {
41112         var me = this;
41113
41114         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
41115     },
41116
41117     /**
41118      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41119      * {@link Ext#createByAlias Ext.createByAlias}.
41120      *
41121      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41122      * @return {Ext.data.proxy.Proxy}
41123      */
41124     setProxy: function(proxy) {
41125         //make sure we have an Ext.data.proxy.Proxy object
41126         if (!proxy.isProxy) {
41127             if (typeof proxy === "string") {
41128                 proxy = {
41129                     type: proxy
41130                 };
41131             }
41132             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41133         }
41134         proxy.setModel(this.self);
41135         this.proxy = proxy;
41136
41137         return proxy;
41138     },
41139
41140     /**
41141      * Returns the configured Proxy for this Model.
41142      * @return {Ext.data.proxy.Proxy} The proxy
41143      */
41144     getProxy: function() {
41145         return this.proxy;
41146     },
41147
41148     /**
41149      * Validates the current data against all of its configured {@link #validations}.
41150      * @return {Ext.data.Errors} The errors object
41151      */
41152     validate: function() {
41153         var errors      = Ext.create('Ext.data.Errors'),
41154             validations = this.validations,
41155             validators  = Ext.data.validations,
41156             length, validation, field, valid, type, i;
41157
41158         if (validations) {
41159             length = validations.length;
41160
41161             for (i = 0; i < length; i++) {
41162                 validation = validations[i];
41163                 field = validation.field || validation.name;
41164                 type  = validation.type;
41165                 valid = validators[type](validation, this.get(field));
41166
41167                 if (!valid) {
41168                     errors.add({
41169                         field  : field,
41170                         message: validation.message || validators[type + 'Message']
41171                     });
41172                 }
41173             }
41174         }
41175
41176         return errors;
41177     },
41178
41179     /**
41180      * Checks if the model is valid. See {@link #validate}.
41181      * @return {Boolean} True if the model is valid.
41182      */
41183     isValid: function(){
41184         return this.validate().isValid();
41185     },
41186
41187     /**
41188      * Saves the model instance using the configured proxy.
41189      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41190      * @return {Ext.data.Model} The Model instance
41191      */
41192     save: function(options) {
41193         options = Ext.apply({}, options);
41194
41195         var me     = this,
41196             action = me.phantom ? 'create' : 'update',
41197             record = null,
41198             scope  = options.scope || me,
41199             operation,
41200             callback;
41201
41202         Ext.apply(options, {
41203             records: [me],
41204             action : action
41205         });
41206
41207         operation = Ext.create('Ext.data.Operation', options);
41208
41209         callback = function(operation) {
41210             if (operation.wasSuccessful()) {
41211                 record = operation.getRecords()[0];
41212                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
41213                 //ModelCache is in place
41214                 me.set(record.data);
41215                 record.dirty = false;
41216
41217                 Ext.callback(options.success, scope, [record, operation]);
41218             } else {
41219                 Ext.callback(options.failure, scope, [record, operation]);
41220             }
41221
41222             Ext.callback(options.callback, scope, [record, operation]);
41223         };
41224
41225         me.getProxy()[action](operation, callback, me);
41226
41227         return me;
41228     },
41229
41230     /**
41231      * Destroys the model using the configured proxy.
41232      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41233      * @return {Ext.data.Model} The Model instance
41234      */
41235     destroy: function(options){
41236         options = Ext.apply({}, options);
41237
41238         var me     = this,
41239             record = null,
41240             scope  = options.scope || me,
41241             operation,
41242             callback;
41243
41244         Ext.apply(options, {
41245             records: [me],
41246             action : 'destroy'
41247         });
41248
41249         operation = Ext.create('Ext.data.Operation', options);
41250         callback = function(operation) {
41251             if (operation.wasSuccessful()) {
41252                 Ext.callback(options.success, scope, [record, operation]);
41253             } else {
41254                 Ext.callback(options.failure, scope, [record, operation]);
41255             }
41256             Ext.callback(options.callback, scope, [record, operation]);
41257         };
41258
41259         me.getProxy().destroy(operation, callback, me);
41260         return me;
41261     },
41262
41263     /**
41264      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
41265      * @return {Number} The id
41266      */
41267     getId: function() {
41268         return this.get(this.idProperty);
41269     },
41270
41271     /**
41272      * Sets the model instance's id field to the given id.
41273      * @param {Number} id The new id
41274      */
41275     setId: function(id) {
41276         this.set(this.idProperty, id);
41277     },
41278
41279     /**
41280      * Tells this model instance that it has been added to a store.
41281      * @param {Ext.data.Store} store The store to which this model has been added.
41282      */
41283     join : function(store) {
41284         /**
41285          * @property {Ext.data.Store} store
41286          * The {@link Ext.data.Store Store} to which this Record belongs.
41287          */
41288         this.store = store;
41289     },
41290
41291     /**
41292      * Tells this model instance that it has been removed from the store.
41293      * @param {Ext.data.Store} store The store from which this model has been removed.
41294      */
41295     unjoin: function(store) {
41296         delete this.store;
41297     },
41298
41299     /**
41300      * @private
41301      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41302      * afterEdit method is called
41303      */
41304     afterEdit : function() {
41305         this.callStore('afterEdit');
41306     },
41307
41308     /**
41309      * @private
41310      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41311      * afterReject method is called
41312      */
41313     afterReject : function() {
41314         this.callStore("afterReject");
41315     },
41316
41317     /**
41318      * @private
41319      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41320      * afterCommit method is called
41321      */
41322     afterCommit: function() {
41323         this.callStore('afterCommit');
41324     },
41325
41326     /**
41327      * @private
41328      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
41329      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
41330      * will always be called with the model instance as its single argument.
41331      * @param {String} fn The function to call on the store
41332      */
41333     callStore: function(fn) {
41334         var store = this.store;
41335
41336         if (store !== undefined && typeof store[fn] == "function") {
41337             store[fn](this);
41338         }
41339     },
41340
41341     /**
41342      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
41343      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
41344      *
41345      *     {
41346      *         orders: [
41347      *             {
41348      *                 id: 123,
41349      *                 status: 'shipped',
41350      *                 orderItems: [
41351      *                     ...
41352      *                 ]
41353      *             }
41354      *         ]
41355      *     }
41356      *
41357      * @return {Object} The nested data set for the Model's loaded associations
41358      */
41359     getAssociatedData: function(){
41360         return this.prepareAssociatedData(this, [], null);
41361     },
41362
41363     /**
41364      * @private
41365      * This complex-looking method takes a given Model instance and returns an object containing all data from
41366      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
41367      * @param {Ext.data.Model} record The Model instance
41368      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
41369      * @param {String} associationType (optional) The name of the type of association to limit to.
41370      * @return {Object} The nested data set for the Model's loaded associations
41371      */
41372     prepareAssociatedData: function(record, ids, associationType) {
41373         //we keep track of all of the internalIds of the models that we have loaded so far in here
41374         var associations     = record.associations.items,
41375             associationCount = associations.length,
41376             associationData  = {},
41377             associatedStore, associatedName, associatedRecords, associatedRecord,
41378             associatedRecordCount, association, id, i, j, type, allow;
41379
41380         for (i = 0; i < associationCount; i++) {
41381             association = associations[i];
41382             type = association.type;
41383             allow = true;
41384             if (associationType) {
41385                 allow = type == associationType;
41386             }
41387             if (allow && type == 'hasMany') {
41388
41389                 //this is the hasMany store filled with the associated data
41390                 associatedStore = record[association.storeName];
41391
41392                 //we will use this to contain each associated record's data
41393                 associationData[association.name] = [];
41394
41395                 //if it's loaded, put it into the association data
41396                 if (associatedStore && associatedStore.data.length > 0) {
41397                     associatedRecords = associatedStore.data.items;
41398                     associatedRecordCount = associatedRecords.length;
41399
41400                     //now we're finally iterating over the records in the association. We do this recursively
41401                     for (j = 0; j < associatedRecordCount; j++) {
41402                         associatedRecord = associatedRecords[j];
41403                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
41404                         id = associatedRecord.id;
41405
41406                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
41407                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
41408                         if (Ext.Array.indexOf(ids, id) == -1) {
41409                             ids.push(id);
41410
41411                             associationData[association.name][j] = associatedRecord.data;
41412                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
41413                         }
41414                     }
41415                 }
41416             } else if (allow && type == 'belongsTo') {
41417                 associatedRecord = record[association.instanceName];
41418                 if (associatedRecord !== undefined) {
41419                     id = associatedRecord.id;
41420                     if (Ext.Array.indexOf(ids, id) == -1) {
41421                         ids.push(id);
41422                         associationData[association.name] = associatedRecord.data;
41423                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
41424                     }
41425                 }
41426             }
41427         }
41428
41429         return associationData;
41430     }
41431 });
41432
41433 /**
41434  * @docauthor Evan Trimboli <evan@sencha.com>
41435  *
41436  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
41437  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
41438  * referred to via it's identifier:
41439  *
41440  *     Ext.create('Ext.data.Store', {
41441  *         model: 'SomeModel',
41442  *         storeId: 'myStore'
41443  *     });
41444  *
41445  *     var store = Ext.data.StoreManager.lookup('myStore');
41446  *
41447  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
41448  *
41449  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
41450  * it with any Component that consumes data from a store:
41451  *
41452  *     Ext.create('Ext.data.Store', {
41453  *         model: 'SomeModel',
41454  *         storeId: 'myStore'
41455  *     });
41456  *
41457  *     Ext.create('Ext.view.View', {
41458  *         store: 'myStore',
41459  *         // other configuration here
41460  *     });
41461  *
41462  */
41463 Ext.define('Ext.data.StoreManager', {
41464     extend: 'Ext.util.MixedCollection',
41465     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
41466     singleton: true,
41467     uses: ['Ext.data.ArrayStore'],
41468     
41469     /**
41470      * @cfg {Object} listeners @hide
41471      */
41472
41473     /**
41474      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
41475      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
41476      * @param {Ext.data.Store...} stores Any number of Store instances
41477      */
41478     register : function() {
41479         for (var i = 0, s; (s = arguments[i]); i++) {
41480             this.add(s);
41481         }
41482     },
41483
41484     /**
41485      * Unregisters one or more Stores with the StoreManager
41486      * @param {String/Object...} stores Any number of Store instances or ID-s
41487      */
41488     unregister : function() {
41489         for (var i = 0, s; (s = arguments[i]); i++) {
41490             this.remove(this.lookup(s));
41491         }
41492     },
41493
41494     /**
41495      * Gets a registered Store by id
41496      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
41497      * @return {Ext.data.Store}
41498      */
41499     lookup : function(store) {
41500         // handle the case when we are given an array or an array of arrays.
41501         if (Ext.isArray(store)) {
41502             var fields = ['field1'], 
41503                 expand = !Ext.isArray(store[0]),
41504                 data = store,
41505                 i,
41506                 len;
41507                 
41508             if(expand){
41509                 data = [];
41510                 for (i = 0, len = store.length; i < len; ++i) {
41511                     data.push([store[i]]);
41512                 }
41513             } else {
41514                 for(i = 2, len = store[0].length; i <= len; ++i){
41515                     fields.push('field' + i);
41516                 }
41517             }
41518             return Ext.create('Ext.data.ArrayStore', {
41519                 data  : data,
41520                 fields: fields,
41521                 autoDestroy: true,
41522                 autoCreated: true,
41523                 expanded: expand
41524             });
41525         }
41526         
41527         if (Ext.isString(store)) {
41528             // store id
41529             return this.get(store);
41530         } else {
41531             // store instance or store config
41532             return Ext.data.AbstractStore.create(store);
41533         }
41534     },
41535
41536     // getKey implementation for MixedCollection
41537     getKey : function(o) {
41538          return o.storeId;
41539     }
41540 }, function() {    
41541     /**
41542      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
41543      * Sample usage:
41544      *
41545      *     Ext.regStore('AllUsers', {
41546      *         model: 'User'
41547      *     });
41548      *
41549      *     // the store can now easily be used throughout the application
41550      *     new Ext.List({
41551      *         store: 'AllUsers',
41552      *         ... other config
41553      *     });
41554      *
41555      * @param {String} id The id to set on the new store
41556      * @param {Object} config The store config
41557      * @member Ext
41558      * @method regStore
41559      */
41560     Ext.regStore = function(name, config) {
41561         var store;
41562
41563         if (Ext.isObject(name)) {
41564             config = name;
41565         } else {
41566             config.storeId = name;
41567         }
41568
41569         if (config instanceof Ext.data.Store) {
41570             store = config;
41571         } else {
41572             store = Ext.create('Ext.data.Store', config);
41573         }
41574
41575         return Ext.data.StoreManager.register(store);
41576     };
41577
41578     /**
41579      * Shortcut to {@link Ext.data.StoreManager#lookup}.
41580      * @member Ext
41581      * @method getStore
41582      * @alias Ext.data.StoreManager#lookup
41583      */
41584     Ext.getStore = function(name) {
41585         return Ext.data.StoreManager.lookup(name);
41586     };
41587 });
41588
41589 /**
41590  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
41591  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
41592  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
41593  * at the time the Container is created, or they may be added dynamically via the
41594  * {@link Ext.container.Container#add add} method.
41595  *
41596  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
41597  *
41598  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
41599  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
41600  *
41601  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
41602  * subclass Component.
41603  *
41604  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
41605  * to create custom Components.
41606  *
41607  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
41608  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
41609  * Component hierarchy.
41610  *
41611  * This is the list of all valid xtypes:
41612  *
41613  *     xtype            Class
41614  *     -------------    ------------------
41615  *     button           {@link Ext.button.Button}
41616  *     buttongroup      {@link Ext.container.ButtonGroup}
41617  *     colorpalette     {@link Ext.picker.Color}
41618  *     component        {@link Ext.Component}
41619  *     container        {@link Ext.container.Container}
41620  *     cycle            {@link Ext.button.Cycle}
41621  *     dataview         {@link Ext.view.View}
41622  *     datepicker       {@link Ext.picker.Date}
41623  *     editor           {@link Ext.Editor}
41624  *     editorgrid       {@link Ext.grid.plugin.Editing}
41625  *     grid             {@link Ext.grid.Panel}
41626  *     multislider      {@link Ext.slider.Multi}
41627  *     panel            {@link Ext.panel.Panel}
41628  *     progressbar      {@link Ext.ProgressBar}
41629  *     slider           {@link Ext.slider.Single}
41630  *     splitbutton      {@link Ext.button.Split}
41631  *     tabpanel         {@link Ext.tab.Panel}
41632  *     treepanel        {@link Ext.tree.Panel}
41633  *     viewport         {@link Ext.container.Viewport}
41634  *     window           {@link Ext.window.Window}
41635  *
41636  *     Toolbar components
41637  *     ---------------------------------------
41638  *     pagingtoolbar    {@link Ext.toolbar.Paging}
41639  *     toolbar          {@link Ext.toolbar.Toolbar}
41640  *     tbfill           {@link Ext.toolbar.Fill}
41641  *     tbitem           {@link Ext.toolbar.Item}
41642  *     tbseparator      {@link Ext.toolbar.Separator}
41643  *     tbspacer         {@link Ext.toolbar.Spacer}
41644  *     tbtext           {@link Ext.toolbar.TextItem}
41645  *
41646  *     Menu components
41647  *     ---------------------------------------
41648  *     menu             {@link Ext.menu.Menu}
41649  *     menucheckitem    {@link Ext.menu.CheckItem}
41650  *     menuitem         {@link Ext.menu.Item}
41651  *     menuseparator    {@link Ext.menu.Separator}
41652  *     menutextitem     {@link Ext.menu.Item}
41653  *
41654  *     Form components
41655  *     ---------------------------------------
41656  *     form             {@link Ext.form.Panel}
41657  *     checkbox         {@link Ext.form.field.Checkbox}
41658  *     combo            {@link Ext.form.field.ComboBox}
41659  *     datefield        {@link Ext.form.field.Date}
41660  *     displayfield     {@link Ext.form.field.Display}
41661  *     field            {@link Ext.form.field.Base}
41662  *     fieldset         {@link Ext.form.FieldSet}
41663  *     hidden           {@link Ext.form.field.Hidden}
41664  *     htmleditor       {@link Ext.form.field.HtmlEditor}
41665  *     label            {@link Ext.form.Label}
41666  *     numberfield      {@link Ext.form.field.Number}
41667  *     radio            {@link Ext.form.field.Radio}
41668  *     radiogroup       {@link Ext.form.RadioGroup}
41669  *     textarea         {@link Ext.form.field.TextArea}
41670  *     textfield        {@link Ext.form.field.Text}
41671  *     timefield        {@link Ext.form.field.Time}
41672  *     trigger          {@link Ext.form.field.Trigger}
41673  *
41674  *     Chart components
41675  *     ---------------------------------------
41676  *     chart            {@link Ext.chart.Chart}
41677  *     barchart         {@link Ext.chart.series.Bar}
41678  *     columnchart      {@link Ext.chart.series.Column}
41679  *     linechart        {@link Ext.chart.series.Line}
41680  *     piechart         {@link Ext.chart.series.Pie}
41681  *
41682  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
41683  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
41684  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
41685  *
41686  *     @example
41687  *     Ext.create('Ext.Component', {
41688  *         html: 'Hello world!',
41689  *         width: 300,
41690  *         height: 200,
41691  *         padding: 20,
41692  *         style: {
41693  *             color: '#FFFFFF',
41694  *             backgroundColor:'#000000'
41695  *         },
41696  *         renderTo: Ext.getBody()
41697  *     });
41698  *
41699  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
41700  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
41701  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
41702  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
41703  *
41704  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
41705  */
41706 Ext.define('Ext.Component', {
41707
41708     /* Begin Definitions */
41709
41710     alias: ['widget.component', 'widget.box'],
41711
41712     extend: 'Ext.AbstractComponent',
41713
41714     requires: [
41715         'Ext.util.DelayedTask'
41716     ],
41717
41718     uses: [
41719         'Ext.Layer',
41720         'Ext.resizer.Resizer',
41721         'Ext.util.ComponentDragger'
41722     ],
41723
41724     mixins: {
41725         floating: 'Ext.util.Floating'
41726     },
41727
41728     statics: {
41729         // Collapse/expand directions
41730         DIRECTION_TOP: 'top',
41731         DIRECTION_RIGHT: 'right',
41732         DIRECTION_BOTTOM: 'bottom',
41733         DIRECTION_LEFT: 'left',
41734
41735         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
41736
41737         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
41738         // This includes dot, comma and whitespace
41739         INVALID_ID_CHARS_Re: /[\.,\s]/g
41740     },
41741
41742     /* End Definitions */
41743
41744     /**
41745      * @cfg {Boolean/Object} resizable
41746      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
41747      *
41748      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
41749      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
41750      * `{@link Ext.resizer.Resizer#dynamic}: false`
41751      */
41752
41753     /**
41754      * @cfg {String} resizeHandles
41755      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
41756      */
41757     resizeHandles: 'all',
41758
41759     /**
41760      * @cfg {Boolean} [autoScroll=false]
41761      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
41762      * `false` to clip any overflowing content.
41763      */
41764
41765     /**
41766      * @cfg {Boolean} floating
41767      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
41768      *
41769      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
41770      *
41771      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
41772      * the global {@link Ext.WindowManager ZIndexManager}
41773      *
41774      * ### Floating Components as child items of a Container
41775      *
41776      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
41777      * a ZIndexManager by examining the ownerCt chain.
41778      *
41779      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
41780      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
41781      * of its stack when the Component's {@link #toFront} method is called.
41782      *
41783      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
41784      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
41785      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
41786      * ancestor Container.
41787      *
41788      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
41789      * ZIndexManager}.
41790      *
41791      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
41792      * you explicitly {@link #show} them.
41793      *
41794      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
41795      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
41796      * not be set.
41797      */
41798     floating: false,
41799
41800     /**
41801      * @cfg {Boolean} toFrontOnShow
41802      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
41803      * floating component.
41804      */
41805     toFrontOnShow: true,
41806
41807     /**
41808      * @property {Ext.ZIndexManager} zIndexManager
41809      * Only present for {@link #floating} Components after they have been rendered.
41810      *
41811      * A reference to the ZIndexManager which is managing this Component's z-index.
41812      *
41813      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
41814      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
41815      *
41816      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
41817      * z-index stack.
41818      *
41819      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
41820      * programatically {@link Ext.Component#render rendered}.
41821      *
41822      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
41823      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
41824      * used.
41825      *
41826      * See {@link #floating} and {@link #floatParent}
41827      */
41828
41829     /**
41830      * @property {Ext.Container} floatParent
41831      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
41832      *
41833      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
41834      * property.
41835      *
41836      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
41837      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
41838      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
41839      * Components.
41840      *
41841      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
41842      * Window as its `floatParent`
41843      *
41844      * See {@link #floating} and {@link #zIndexManager}
41845      */
41846
41847     /**
41848      * @cfg {Boolean/Object} [draggable=false]
41849      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
41850      * the drag handle.
41851      *
41852      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
41853      * instantiated to perform dragging.
41854      *
41855      * For example to create a Component which may only be dragged around using a certain internal element as the drag
41856      * handle, use the delegate option:
41857      *
41858      *     new Ext.Component({
41859      *         constrain: true,
41860      *         floating: true,
41861      *         style: {
41862      *             backgroundColor: '#fff',
41863      *             border: '1px solid black'
41864      *         },
41865      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
41866      *         draggable: {
41867      *             delegate: 'h1'
41868      *         }
41869      *     }).show();
41870      */
41871
41872     /**
41873      * @cfg {Boolean} [maintainFlex=false]
41874      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
41875      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
41876      *
41877      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
41878      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
41879      */
41880
41881     hideMode: 'display',
41882     // Deprecate 5.0
41883     hideParent: false,
41884
41885     ariaRole: 'presentation',
41886
41887     bubbleEvents: [],
41888
41889     actionMode: 'el',
41890     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
41891
41892     //renderTpl: new Ext.XTemplate(
41893     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
41894     //        compiled: true,
41895     //        disableFormats: true
41896     //    }
41897     //),
41898
41899     /**
41900      * Creates new Component.
41901      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
41902      *
41903      * - **an element** : it is set as the internal element and its id used as the component id
41904      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
41905      * - **anything else** : it is assumed to be a standard config object and is applied to the component
41906      */
41907     constructor: function(config) {
41908         var me = this;
41909
41910         config = config || {};
41911         if (config.initialConfig) {
41912
41913             // Being initialized from an Ext.Action instance...
41914             if (config.isAction) {
41915                 me.baseAction = config;
41916             }
41917             config = config.initialConfig;
41918             // component cloning / action set up
41919         }
41920         else if (config.tagName || config.dom || Ext.isString(config)) {
41921             // element object
41922             config = {
41923                 applyTo: config,
41924                 id: config.id || config
41925             };
41926         }
41927
41928         me.callParent([config]);
41929
41930         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
41931         // register this Component as one of its items
41932         if (me.baseAction){
41933             me.baseAction.addComponent(me);
41934         }
41935     },
41936
41937     /**
41938      * The initComponent template method is an important initialization step for a Component. It is intended to be
41939      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
41940      * initComponent method of the class being created is called first, with each initComponent method
41941      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
41942      * if needed, override the constructor logic of the Component at any step in the hierarchy.
41943      *
41944      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
41945      * to ensure that the parent class' initComponent method is also called.
41946      *
41947      * The following example demonstrates using a dynamic string for the text of a button at the time of
41948      * instantiation of the class.
41949      *
41950      *     Ext.define('DynamicButtonText', {
41951      *         extend: 'Ext.button.Button',
41952      *
41953      *         initComponent: function() {
41954      *             this.text = new Date();
41955      *             this.renderTo = Ext.getBody();
41956      *             this.callParent();
41957      *         }
41958      *     });
41959      *
41960      *     Ext.onReady(function() {
41961      *         Ext.create('DynamicButtonText');
41962      *     });
41963      *
41964      * @template
41965      */
41966     initComponent: function() {
41967         var me = this;
41968
41969         me.callParent();
41970
41971         if (me.listeners) {
41972             me.on(me.listeners);
41973             delete me.listeners;
41974         }
41975         me.enableBubble(me.bubbleEvents);
41976         me.mons = [];
41977     },
41978
41979     // private
41980     afterRender: function() {
41981         var me = this,
41982             resizable = me.resizable;
41983
41984         if (me.floating) {
41985             me.makeFloating(me.floating);
41986         } else {
41987             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
41988         }
41989
41990         if (Ext.isDefined(me.autoScroll)) {
41991             me.setAutoScroll(me.autoScroll);
41992         }
41993         me.callParent();
41994
41995         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
41996             me.setPagePosition(me.pageX, me.pageY);
41997         }
41998
41999         if (resizable) {
42000             me.initResizable(resizable);
42001         }
42002
42003         if (me.draggable) {
42004             me.initDraggable();
42005         }
42006
42007         me.initAria();
42008     },
42009
42010     initAria: function() {
42011         var actionEl = this.getActionEl(),
42012             role = this.ariaRole;
42013         if (role) {
42014             actionEl.dom.setAttribute('role', role);
42015         }
42016     },
42017
42018     /**
42019      * Sets the overflow on the content element of the component.
42020      * @param {Boolean} scroll True to allow the Component to auto scroll.
42021      * @return {Ext.Component} this
42022      */
42023     setAutoScroll : function(scroll){
42024         var me = this,
42025             targetEl;
42026         scroll = !!scroll;
42027         if (me.rendered) {
42028             targetEl = me.getTargetEl();
42029             targetEl.setStyle('overflow', scroll ? 'auto' : '');
42030             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
42031                 // The scrollable container element must be non-statically positioned or IE6/7 will make
42032                 // positioned children stay in place rather than scrolling with the rest of the content
42033                 targetEl.position();
42034             }
42035         }
42036         me.autoScroll = scroll;
42037         return me;
42038     },
42039
42040     // private
42041     makeFloating : function(cfg){
42042         this.mixins.floating.constructor.call(this, cfg);
42043     },
42044
42045     initResizable: function(resizable) {
42046         var me = this;
42047
42048         resizable = Ext.apply({
42049             target: me,
42050             dynamic: false,
42051             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
42052             handles: me.resizeHandles
42053         }, resizable);
42054         resizable.target = me;
42055         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
42056     },
42057
42058     getDragEl: function() {
42059         return this.el;
42060     },
42061
42062     initDraggable: function() {
42063         var me = this,
42064             ddConfig = Ext.applyIf({
42065                 el: me.getDragEl(),
42066                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
42067             }, me.draggable);
42068
42069         // Add extra configs if Component is specified to be constrained
42070         if (me.constrain || me.constrainDelegate) {
42071             ddConfig.constrain = me.constrain;
42072             ddConfig.constrainDelegate = me.constrainDelegate;
42073         }
42074
42075         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
42076     },
42077
42078     /**
42079      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
42080      * method fires the {@link #move} event.
42081      * @param {Number} left The new left
42082      * @param {Number} top The new top
42083      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
42084      * animation configuration.
42085      * @return {Ext.Component} this
42086      */
42087     setPosition: function(x, y, animate) {
42088         var me = this,
42089             el = me.el,
42090             to = {},
42091             adj, adjX, adjY, xIsNumber, yIsNumber;
42092
42093         if (Ext.isArray(x)) {
42094             animate = y;
42095             y = x[1];
42096             x = x[0];
42097         }
42098         me.x = x;
42099         me.y = y;
42100
42101         if (!me.rendered) {
42102             return me;
42103         }
42104
42105         adj = me.adjustPosition(x, y);
42106         adjX = adj.x;
42107         adjY = adj.y;
42108         xIsNumber = Ext.isNumber(adjX);
42109         yIsNumber = Ext.isNumber(adjY);
42110
42111         if (xIsNumber || yIsNumber) {
42112             if (animate) {
42113                 if (xIsNumber) {
42114                     to.left = adjX;
42115                 }
42116                 if (yIsNumber) {
42117                     to.top = adjY;
42118                 }
42119
42120                 me.stopAnimation();
42121                 me.animate(Ext.apply({
42122                     duration: 1000,
42123                     listeners: {
42124                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
42125                     },
42126                     to: to
42127                 }, animate));
42128             }
42129             else {
42130                 if (!xIsNumber) {
42131                     el.setTop(adjY);
42132                 }
42133                 else if (!yIsNumber) {
42134                     el.setLeft(adjX);
42135                 }
42136                 else {
42137                     el.setLeftTop(adjX, adjY);
42138                 }
42139                 me.afterSetPosition(adjX, adjY);
42140             }
42141         }
42142         return me;
42143     },
42144
42145     /**
42146      * @private
42147      * @template
42148      * Template method called after a Component has been positioned.
42149      */
42150     afterSetPosition: function(ax, ay) {
42151         this.onPosition(ax, ay);
42152         this.fireEvent('move', this, ax, ay);
42153     },
42154
42155     /**
42156      * Displays component at specific xy position.
42157      * A floating component (like a menu) is positioned relative to its ownerCt if any.
42158      * Useful for popping up a context menu:
42159      *
42160      *     listeners: {
42161      *         itemcontextmenu: function(view, record, item, index, event, options) {
42162      *             Ext.create('Ext.menu.Menu', {
42163      *                 width: 100,
42164      *                 height: 100,
42165      *                 margin: '0 0 10 0',
42166      *                 items: [{
42167      *                     text: 'regular item 1'
42168      *                 },{
42169      *                     text: 'regular item 2'
42170      *                 },{
42171      *                     text: 'regular item 3'
42172      *                 }]
42173      *             }).showAt(event.getXY());
42174      *         }
42175      *     }
42176      *
42177      * @param {Number} x The new x position
42178      * @param {Number} y The new y position
42179      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42180      * animation configuration.
42181      */
42182     showAt: function(x, y, animate) {
42183         var me = this;
42184
42185         if (me.floating) {
42186             me.setPosition(x, y, animate);
42187         } else {
42188             me.setPagePosition(x, y, animate);
42189         }
42190         me.show();
42191     },
42192
42193     /**
42194      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
42195      * This method fires the {@link #move} event.
42196      * @param {Number} x The new x position
42197      * @param {Number} y The new y position
42198      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42199      * animation configuration.
42200      * @return {Ext.Component} this
42201      */
42202     setPagePosition: function(x, y, animate) {
42203         var me = this,
42204             p;
42205
42206         if (Ext.isArray(x)) {
42207             y = x[1];
42208             x = x[0];
42209         }
42210         me.pageX = x;
42211         me.pageY = y;
42212         if (me.floating && me.floatParent) {
42213             // Floating Components being positioned in their ownerCt have to be made absolute
42214             p = me.floatParent.getTargetEl().getViewRegion();
42215             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
42216                 x -= p.left;
42217             }
42218             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
42219                 y -= p.top;
42220             }
42221             me.setPosition(x, y, animate);
42222         }
42223         else {
42224             p = me.el.translatePoints(x, y);
42225             me.setPosition(p.left, p.top, animate);
42226         }
42227         return me;
42228     },
42229
42230     /**
42231      * Gets the current box measurements of the component's underlying element.
42232      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42233      * @return {Object} box An object in the format {x, y, width, height}
42234      */
42235     getBox : function(local){
42236         var pos = this.getPosition(local),
42237             size = this.getSize();
42238
42239         size.x = pos[0];
42240         size.y = pos[1];
42241         return size;
42242     },
42243
42244     /**
42245      * Sets the current box measurements of the component's underlying element.
42246      * @param {Object} box An object in the format {x, y, width, height}
42247      * @return {Ext.Component} this
42248      */
42249     updateBox : function(box){
42250         this.setSize(box.width, box.height);
42251         this.setPagePosition(box.x, box.y);
42252         return this;
42253     },
42254
42255     // Include margins
42256     getOuterSize: function() {
42257         var el = this.el;
42258         return {
42259             width: el.getWidth() + el.getMargin('lr'),
42260             height: el.getHeight() + el.getMargin('tb')
42261         };
42262     },
42263
42264     // private
42265     adjustPosition: function(x, y) {
42266
42267         // Floating Components being positioned in their ownerCt have to be made absolute
42268         if (this.floating && this.floatParent) {
42269             var o = this.floatParent.getTargetEl().getViewRegion();
42270             x += o.left;
42271             y += o.top;
42272         }
42273
42274         return {
42275             x: x,
42276             y: y
42277         };
42278     },
42279
42280     /**
42281      * Gets the current XY position of the component's underlying element.
42282      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42283      * @return {Number[]} The XY position of the element (e.g., [100, 200])
42284      */
42285     getPosition: function(local) {
42286         var me = this,
42287             el = me.el,
42288             xy,
42289             o;
42290
42291         // Floating Components which were just rendered with no ownerCt return local position.
42292         if ((local === true) || (me.floating && !me.floatParent)) {
42293             return [el.getLeft(true), el.getTop(true)];
42294         }
42295         xy = me.xy || el.getXY();
42296
42297         // Floating Components in an ownerCt have to have their positions made relative
42298         if (me.floating) {
42299             o = me.floatParent.getTargetEl().getViewRegion();
42300             xy[0] -= o.left;
42301             xy[1] -= o.top;
42302         }
42303         return xy;
42304     },
42305
42306     getId: function() {
42307         var me = this,
42308             xtype;
42309
42310         if (!me.id) {
42311             xtype = me.getXType();
42312             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
42313             me.id = xtype + '-' + me.getAutoId();
42314         }
42315         return me.id;
42316     },
42317
42318     onEnable: function() {
42319         var actionEl = this.getActionEl();
42320         actionEl.dom.removeAttribute('aria-disabled');
42321         actionEl.dom.disabled = false;
42322         this.callParent();
42323     },
42324
42325     onDisable: function() {
42326         var actionEl = this.getActionEl();
42327         actionEl.dom.setAttribute('aria-disabled', true);
42328         actionEl.dom.disabled = true;
42329         this.callParent();
42330     },
42331
42332     /**
42333      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
42334      *
42335      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
42336      * brought to the front of its {@link #zIndexManager z-index stack}.
42337      *
42338      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
42339      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
42340      * with `floating: true`.** The target from which the Component should animate from while opening.
42341      * @param {Function} [callback] A callback function to call after the Component is displayed.
42342      * Only necessary if animation was specified.
42343      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42344      * Defaults to this Component.
42345      * @return {Ext.Component} this
42346      */
42347     show: function(animateTarget, cb, scope) {
42348         var me = this;
42349
42350         if (me.rendered && me.isVisible()) {
42351             if (me.toFrontOnShow && me.floating) {
42352                 me.toFront();
42353             }
42354         } else if (me.fireEvent('beforeshow', me) !== false) {
42355             me.hidden = false;
42356
42357             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
42358             if (!me.rendered && (me.autoRender || me.floating)) {
42359                 me.doAutoRender();
42360             }
42361             if (me.rendered) {
42362                 me.beforeShow();
42363                 me.onShow.apply(me, arguments);
42364
42365                 // Notify any owning Container unless it's suspended.
42366                 // Floating Components do not participate in layouts.
42367                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42368                     me.ownerCt.doLayout();
42369                 }
42370                 me.afterShow.apply(me, arguments);
42371             }
42372         }
42373         return me;
42374     },
42375
42376     beforeShow: Ext.emptyFn,
42377
42378     // Private. Override in subclasses where more complex behaviour is needed.
42379     onShow: function() {
42380         var me = this;
42381
42382         me.el.show();
42383         me.callParent(arguments);
42384         if (me.floating && me.constrain) {
42385             me.doConstrain();
42386         }
42387     },
42388
42389     afterShow: function(animateTarget, cb, scope) {
42390         var me = this,
42391             fromBox,
42392             toBox,
42393             ghostPanel;
42394
42395         // Default to configured animate target if none passed
42396         animateTarget = animateTarget || me.animateTarget;
42397
42398         // Need to be able to ghost the Component
42399         if (!me.ghost) {
42400             animateTarget = null;
42401         }
42402         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
42403         if (animateTarget) {
42404             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42405             toBox = me.el.getBox();
42406             fromBox = animateTarget.getBox();
42407             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
42408             ghostPanel = me.ghost();
42409             ghostPanel.el.stopAnimation();
42410
42411             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
42412             ghostPanel.el.setX(-10000);
42413
42414             ghostPanel.el.animate({
42415                 from: fromBox,
42416                 to: toBox,
42417                 listeners: {
42418                     afteranimate: function() {
42419                         delete ghostPanel.componentLayout.lastComponentSize;
42420                         me.unghost();
42421                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
42422                         me.onShowComplete(cb, scope);
42423                     }
42424                 }
42425             });
42426         }
42427         else {
42428             me.onShowComplete(cb, scope);
42429         }
42430     },
42431
42432     onShowComplete: function(cb, scope) {
42433         var me = this;
42434         if (me.floating) {
42435             me.toFront();
42436         }
42437         Ext.callback(cb, scope || me);
42438         me.fireEvent('show', me);
42439     },
42440
42441     /**
42442      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
42443      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
42444      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
42445      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
42446      * @param {Function} [callback] A callback function to call after the Component is hidden.
42447      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42448      * Defaults to this Component.
42449      * @return {Ext.Component} this
42450      */
42451     hide: function() {
42452         var me = this;
42453
42454         // Clear the flag which is set if a floatParent was hidden while this is visible.
42455         // If a hide operation was subsequently called, that pending show must be hidden.
42456         me.showOnParentShow = false;
42457
42458         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
42459             me.hidden = true;
42460             if (me.rendered) {
42461                 me.onHide.apply(me, arguments);
42462
42463                 // Notify any owning Container unless it's suspended.
42464                 // Floating Components do not participate in layouts.
42465                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42466                     me.ownerCt.doLayout();
42467                 }
42468             }
42469         }
42470         return me;
42471     },
42472
42473     // Possibly animate down to a target element.
42474     onHide: function(animateTarget, cb, scope) {
42475         var me = this,
42476             ghostPanel,
42477             toBox;
42478
42479         // Default to configured animate target if none passed
42480         animateTarget = animateTarget || me.animateTarget;
42481
42482         // Need to be able to ghost the Component
42483         if (!me.ghost) {
42484             animateTarget = null;
42485         }
42486         // If we're animating, kick off an animation of the ghost down to the target
42487         if (animateTarget) {
42488             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42489             ghostPanel = me.ghost();
42490             ghostPanel.el.stopAnimation();
42491             toBox = animateTarget.getBox();
42492             toBox.width += 'px';
42493             toBox.height += 'px';
42494             ghostPanel.el.animate({
42495                 to: toBox,
42496                 listeners: {
42497                     afteranimate: function() {
42498                         delete ghostPanel.componentLayout.lastComponentSize;
42499                         ghostPanel.el.hide();
42500                         me.afterHide(cb, scope);
42501                     }
42502                 }
42503             });
42504         }
42505         me.el.hide();
42506         if (!animateTarget) {
42507             me.afterHide(cb, scope);
42508         }
42509     },
42510
42511     afterHide: function(cb, scope) {
42512         Ext.callback(cb, scope || this);
42513         this.fireEvent('hide', this);
42514     },
42515
42516     /**
42517      * @private
42518      * @template
42519      * Template method to contribute functionality at destroy time.
42520      */
42521     onDestroy: function() {
42522         var me = this;
42523
42524         // Ensure that any ancillary components are destroyed.
42525         if (me.rendered) {
42526             Ext.destroy(
42527                 me.proxy,
42528                 me.proxyWrap,
42529                 me.resizer
42530             );
42531             // Different from AbstractComponent
42532             if (me.actionMode == 'container' || me.removeMode == 'container') {
42533                 me.container.remove();
42534             }
42535         }
42536         delete me.focusTask;
42537         me.callParent();
42538     },
42539
42540     deleteMembers: function() {
42541         var args = arguments,
42542             len = args.length,
42543             i = 0;
42544         for (; i < len; ++i) {
42545             delete this[args[i]];
42546         }
42547     },
42548
42549     /**
42550      * Try to focus this component.
42551      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
42552      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
42553      * @return {Ext.Component} this
42554      */
42555     focus: function(selectText, delay) {
42556         var me = this,
42557                 focusEl;
42558
42559         if (delay) {
42560             if (!me.focusTask) {
42561                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
42562             }
42563             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
42564             return me;
42565         }
42566
42567         if (me.rendered && !me.isDestroyed) {
42568             // getFocusEl could return a Component.
42569             focusEl = me.getFocusEl();
42570             focusEl.focus();
42571             if (focusEl.dom && selectText === true) {
42572                 focusEl.dom.select();
42573             }
42574
42575             // Focusing a floating Component brings it to the front of its stack.
42576             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
42577             if (me.floating) {
42578                 me.toFront(true);
42579             }
42580         }
42581         return me;
42582     },
42583
42584     /**
42585      * @private
42586      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
42587      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
42588      * by the {@link #focus} method.
42589      * @returns {Ext.Element} the focus holing element.
42590      */
42591     getFocusEl: function() {
42592         return this.el;
42593     },
42594
42595     // private
42596     blur: function() {
42597         if (this.rendered) {
42598             this.getFocusEl().blur();
42599         }
42600         return this;
42601     },
42602
42603     getEl: function() {
42604         return this.el;
42605     },
42606
42607     // Deprecate 5.0
42608     getResizeEl: function() {
42609         return this.el;
42610     },
42611
42612     // Deprecate 5.0
42613     getPositionEl: function() {
42614         return this.el;
42615     },
42616
42617     // Deprecate 5.0
42618     getActionEl: function() {
42619         return this.el;
42620     },
42621
42622     // Deprecate 5.0
42623     getVisibilityEl: function() {
42624         return this.el;
42625     },
42626
42627     // Deprecate 5.0
42628     onResize: Ext.emptyFn,
42629
42630     // private
42631     getBubbleTarget: function() {
42632         return this.ownerCt;
42633     },
42634
42635     // private
42636     getContentTarget: function() {
42637         return this.el;
42638     },
42639
42640     /**
42641      * Clone the current component using the original config values passed into this instance by default.
42642      * @param {Object} overrides A new config containing any properties to override in the cloned version.
42643      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
42644      * @return {Ext.Component} clone The cloned copy of this component
42645      */
42646     cloneConfig: function(overrides) {
42647         overrides = overrides || {};
42648         var id = overrides.id || Ext.id(),
42649             cfg = Ext.applyIf(overrides, this.initialConfig),
42650             self;
42651
42652         cfg.id = id;
42653
42654         self = Ext.getClass(this);
42655
42656         // prevent dup id
42657         return new self(cfg);
42658     },
42659
42660     /**
42661      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
42662      * xtypes, see the {@link Ext.Component} header. Example usage:
42663      *
42664      *     var t = new Ext.form.field.Text();
42665      *     alert(t.getXType());  // alerts 'textfield'
42666      *
42667      * @return {String} The xtype
42668      */
42669     getXType: function() {
42670         return this.self.xtype;
42671     },
42672
42673     /**
42674      * Find a container above this component at any level by a custom function. If the passed function returns true, the
42675      * container will be returned.
42676      * @param {Function} fn The custom function to call with the arguments (container, this component).
42677      * @return {Ext.container.Container} The first Container for which the custom function returns true
42678      */
42679     findParentBy: function(fn) {
42680         var p;
42681
42682         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
42683         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
42684         return p || null;
42685     },
42686
42687     /**
42688      * Find a container above this component at any level by xtype or class
42689      *
42690      * See also the {@link Ext.Component#up up} method.
42691      *
42692      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
42693      * @return {Ext.container.Container} The first Container which matches the given xtype or class
42694      */
42695     findParentByType: function(xtype) {
42696         return Ext.isFunction(xtype) ?
42697             this.findParentBy(function(p) {
42698                 return p.constructor === xtype;
42699             })
42700         :
42701             this.up(xtype);
42702     },
42703
42704     /**
42705      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
42706      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
42707      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
42708      *
42709      * @param {Function} fn The function to call
42710      * @param {Object} [scope] The scope of the function. Defaults to current node.
42711      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
42712      * @return {Ext.Component} this
42713      */
42714     bubble: function(fn, scope, args) {
42715         var p = this;
42716         while (p) {
42717             if (fn.apply(scope || p, args || [p]) === false) {
42718                 break;
42719             }
42720             p = p.ownerCt;
42721         }
42722         return this;
42723     },
42724
42725     getProxy: function() {
42726         var me = this,
42727             target;
42728
42729         if (!me.proxy) {
42730             target = Ext.getBody();
42731             if (Ext.scopeResetCSS) {
42732                 me.proxyWrap = target = Ext.getBody().createChild({
42733                     cls: Ext.baseCSSPrefix + 'reset'
42734                 });
42735             }
42736             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
42737         }
42738         return me.proxy;
42739     }
42740
42741 });
42742
42743 /**
42744  * @class Ext.layout.container.AbstractContainer
42745  * @extends Ext.layout.Layout
42746  * Please refer to sub classes documentation
42747  * @private
42748  */
42749 Ext.define('Ext.layout.container.AbstractContainer', {
42750
42751     /* Begin Definitions */
42752
42753     extend: 'Ext.layout.Layout',
42754
42755     /* End Definitions */
42756
42757     type: 'container',
42758
42759     /**
42760      * @cfg {Boolean} bindToOwnerCtComponent
42761      * Flag to notify the ownerCt Component on afterLayout of a change
42762      */
42763     bindToOwnerCtComponent: false,
42764
42765     /**
42766      * @cfg {Boolean} bindToOwnerCtContainer
42767      * Flag to notify the ownerCt Container on afterLayout of a change
42768      */
42769     bindToOwnerCtContainer: false,
42770
42771     /**
42772      * @cfg {String} itemCls
42773      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
42774      * customized styles to the container or any of its children using standard CSS rules. See
42775      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
42776      * </p>
42777      */
42778
42779     /**
42780     * Set the size of an item within the Container.  We should always use setCalculatedSize.
42781     * @private
42782     */
42783     setItemSize: function(item, width, height) {
42784         if (Ext.isObject(width)) {
42785             height = width.height;
42786             width = width.width;
42787         }
42788         item.setCalculatedSize(width, height, this.owner);
42789     },
42790
42791     /**
42792      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
42793      * base class), or the layout phase (onLayout).</p>
42794      * @return {Ext.Component[]} of child components
42795      */
42796     getLayoutItems: function() {
42797         return this.owner && this.owner.items && this.owner.items.items || [];
42798     },
42799
42800     /**
42801      * Containers should not lay out child components when collapsed.
42802      */
42803     beforeLayout: function() {
42804         return !this.owner.collapsed && this.callParent(arguments);
42805     },
42806
42807     afterLayout: function() {
42808         this.owner.afterLayout(this);
42809     },
42810     /**
42811      * Returns the owner component's resize element.
42812      * @return {Ext.Element}
42813      */
42814      getTarget: function() {
42815          return this.owner.getTargetEl();
42816      },
42817     /**
42818      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
42819      * May be overridden in layout managers which implement an inner element.
42820      * @return {Ext.Element}
42821      */
42822      getRenderTarget: function() {
42823          return this.owner.getTargetEl();
42824      }
42825 });
42826
42827 /**
42828 * @class Ext.layout.container.Container
42829 * @extends Ext.layout.container.AbstractContainer
42830 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
42831 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
42832 */
42833 Ext.define('Ext.layout.container.Container', {
42834
42835     /* Begin Definitions */
42836
42837     extend: 'Ext.layout.container.AbstractContainer',
42838     alternateClassName: 'Ext.layout.ContainerLayout',
42839
42840     /* End Definitions */
42841
42842     layoutItem: function(item, box) {
42843         if (box) {
42844             item.doComponentLayout(box.width, box.height);
42845         } else {
42846             item.doComponentLayout();
42847         }
42848     },
42849
42850     getLayoutTargetSize : function() {
42851         var target = this.getTarget(),
42852             ret;
42853
42854         if (target) {
42855             ret = target.getViewSize();
42856
42857             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
42858             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
42859             // with getViewSize
42860             if (Ext.isIE && ret.width == 0){
42861                 ret = target.getStyleSize();
42862             }
42863
42864             ret.width -= target.getPadding('lr');
42865             ret.height -= target.getPadding('tb');
42866         }
42867         return ret;
42868     },
42869
42870     beforeLayout: function() {
42871         if (this.owner.beforeLayout(arguments) !== false) {
42872             return this.callParent(arguments);
42873         }
42874         else {
42875             return false;
42876         }
42877     },
42878
42879     /**
42880      * @protected
42881      * Returns all items that are rendered
42882      * @return {Array} All matching items
42883      */
42884     getRenderedItems: function() {
42885         var me = this,
42886             target = me.getTarget(),
42887             items = me.getLayoutItems(),
42888             ln = items.length,
42889             renderedItems = [],
42890             i, item;
42891
42892         for (i = 0; i < ln; i++) {
42893             item = items[i];
42894             if (item.rendered && me.isValidParent(item, target, i)) {
42895                 renderedItems.push(item);
42896             }
42897         }
42898
42899         return renderedItems;
42900     },
42901
42902     /**
42903      * @protected
42904      * Returns all items that are both rendered and visible
42905      * @return {Array} All matching items
42906      */
42907     getVisibleItems: function() {
42908         var target   = this.getTarget(),
42909             items = this.getLayoutItems(),
42910             ln = items.length,
42911             visibleItems = [],
42912             i, item;
42913
42914         for (i = 0; i < ln; i++) {
42915             item = items[i];
42916             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
42917                 visibleItems.push(item);
42918             }
42919         }
42920
42921         return visibleItems;
42922     }
42923 });
42924 /**
42925  * @class Ext.layout.container.Auto
42926  * @extends Ext.layout.container.Container
42927  *
42928  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
42929  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
42930  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
42931  * to any child containers.
42932  *
42933  *     @example
42934  *     Ext.create('Ext.Panel', {
42935  *         width: 500,
42936  *         height: 280,
42937  *         title: "AutoLayout Panel",
42938  *         layout: 'auto',
42939  *         renderTo: document.body,
42940  *         items: [{
42941  *             xtype: 'panel',
42942  *             title: 'Top Inner Panel',
42943  *             width: '75%',
42944  *             height: 90
42945  *         },
42946  *         {
42947  *             xtype: 'panel',
42948  *             title: 'Bottom Inner Panel',
42949  *             width: '75%',
42950  *             height: 90
42951  *         }]
42952  *     });
42953  */
42954 Ext.define('Ext.layout.container.Auto', {
42955
42956     /* Begin Definitions */
42957
42958     alias: ['layout.auto', 'layout.autocontainer'],
42959
42960     extend: 'Ext.layout.container.Container',
42961
42962     /* End Definitions */
42963
42964     type: 'autocontainer',
42965
42966     bindToOwnerCtComponent: true,
42967
42968     // @private
42969     onLayout : function(owner, target) {
42970         var me = this,
42971             items = me.getLayoutItems(),
42972             ln = items.length,
42973             i;
42974
42975         // Ensure the Container is only primed with the clear element if there are child items.
42976         if (ln) {
42977             // Auto layout uses natural HTML flow to arrange the child items.
42978             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
42979             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
42980             if (!me.clearEl) {
42981                 me.clearEl = me.getRenderTarget().createChild({
42982                     cls: Ext.baseCSSPrefix + 'clear',
42983                     role: 'presentation'
42984                 });
42985             }
42986
42987             // Auto layout allows CSS to size its child items.
42988             for (i = 0; i < ln; i++) {
42989                 me.setItemSize(items[i]);
42990             }
42991         }
42992     },
42993
42994     configureItem: function(item) {
42995         this.callParent(arguments);
42996
42997         // Auto layout does not manage any dimensions.
42998         item.layoutManagedHeight = 2;
42999         item.layoutManagedWidth = 2;
43000     }
43001 });
43002 /**
43003  * @class Ext.container.AbstractContainer
43004  * @extends Ext.Component
43005  * An abstract base class which provides shared methods for Containers across the Sencha product line.
43006  * @private
43007  */
43008 Ext.define('Ext.container.AbstractContainer', {
43009
43010     /* Begin Definitions */
43011
43012     extend: 'Ext.Component',
43013
43014     requires: [
43015         'Ext.util.MixedCollection',
43016         'Ext.layout.container.Auto',
43017         'Ext.ZIndexManager'
43018     ],
43019
43020     /* End Definitions */
43021     /**
43022      * @cfg {String/Object} layout
43023      * <p><b>Important</b>: In order for child items to be correctly sized and
43024      * positioned, typically a layout manager <b>must</b> be specified through
43025      * the <code>layout</code> configuration option.</p>
43026      * <p>The sizing and positioning of child {@link #items} is the responsibility of
43027      * the Container's layout manager which creates and manages the type of layout
43028      * you have in mind.  For example:</p>
43029      * <p>If the {@link #layout} configuration is not explicitly specified for
43030      * a general purpose container (e.g. Container or Panel) the
43031      * {@link Ext.layout.container.Auto default layout manager} will be used
43032      * which does nothing but render child components sequentially into the
43033      * Container (no sizing or positioning will be performed in this situation).</p>
43034      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
43035      * <div><ul class="mdetail-params">
43036      * <li><u>Specify as an Object</u></li>
43037      * <div><ul class="mdetail-params">
43038      * <li>Example usage:</li>
43039      * <pre><code>
43040 layout: {
43041     type: 'vbox',
43042     align: 'left'
43043 }
43044        </code></pre>
43045      *
43046      * <li><code><b>type</b></code></li>
43047      * <br/><p>The layout type to be used for this container.  If not specified,
43048      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
43049      * <p>Valid layout <code>type</code> values are:</p>
43050      * <div class="sub-desc"><ul class="mdetail-params">
43051      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
43052      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
43053      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
43054      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
43055      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
43056      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
43057      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
43058      * </ul></div>
43059      *
43060      * <li>Layout specific configuration properties</li>
43061      * <p>Additional layout specific configuration properties may also be
43062      * specified. For complete details regarding the valid config options for
43063      * each layout type, see the layout class corresponding to the <code>type</code>
43064      * specified.</p>
43065      *
43066      * </ul></div>
43067      *
43068      * <li><u>Specify as a String</u></li>
43069      * <div><ul class="mdetail-params">
43070      * <li>Example usage:</li>
43071      * <pre><code>
43072 layout: 'vbox'
43073        </code></pre>
43074      * <li><code><b>layout</b></code></li>
43075      * <p>The layout <code>type</code> to be used for this container (see list
43076      * of valid layout type values above).</p>
43077      * <p>Additional layout specific configuration properties. For complete
43078      * details regarding the valid config options for each layout type, see the
43079      * layout class corresponding to the <code>layout</code> specified.</p>
43080      * </ul></div></ul></div>
43081      */
43082
43083     /**
43084      * @cfg {String/Number} activeItem
43085      * A string component id or the numeric index of the component that should be initially activated within the
43086      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
43087      * item in the container's collection).  activeItem only applies to layout styles that can display
43088      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
43089      */
43090     /**
43091      * @cfg {Object/Object[]} items
43092      * <p>A single item, or an array of child Components to be added to this container</p>
43093      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
43094      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
43095      * <p>Example:</p>
43096      * <pre><code>
43097 // specifying a single item
43098 items: {...},
43099 layout: 'fit',    // The single items is sized to fit
43100
43101 // specifying multiple items
43102 items: [{...}, {...}],
43103 layout: 'hbox', // The items are arranged horizontally
43104        </code></pre>
43105      * <p>Each item may be:</p>
43106      * <ul>
43107      * <li>A {@link Ext.Component Component}</li>
43108      * <li>A Component configuration object</li>
43109      * </ul>
43110      * <p>If a configuration object is specified, the actual type of Component to be
43111      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
43112      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
43113      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
43114      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
43115      * <p><b>Notes</b>:</p>
43116      * <p>Ext uses lazy rendering. Child Components will only be rendered
43117      * should it become necessary. Items are automatically laid out when they are first
43118      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
43119      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
43120      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
43121      */
43122     /**
43123      * @cfg {Object/Function} defaults
43124      * This option is a means of applying default settings to all added items whether added through the {@link #items}
43125      * config or via the {@link #add} or {@link #insert} methods.
43126      *
43127      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
43128      * existing properties in the item (see {@link Ext#applyIf}).
43129      *
43130      * If the defaults option is specified as a function, then the function will be called using this Container as the
43131      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
43132      * from that call is then applied to the item as default properties.
43133      *
43134      * For example, to automatically apply padding to the body of each of a set of
43135      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
43136      *
43137      * Usage:
43138      *
43139      *     defaults: { // defaults are applied to items, not the container
43140      *         autoScroll: true
43141      *     },
43142      *     items: [
43143      *         // default will not be applied here, panel1 will be autoScroll: false
43144      *         {
43145      *             xtype: 'panel',
43146      *             id: 'panel1',
43147      *             autoScroll: false
43148      *         },
43149      *         // this component will have autoScroll: true
43150      *         new Ext.panel.Panel({
43151      *             id: 'panel2'
43152      *         })
43153      *     ]
43154      */
43155
43156     /** @cfg {Boolean} suspendLayout
43157      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
43158      * as multiple arguments or an array.
43159      */
43160     suspendLayout : false,
43161
43162     /** @cfg {Boolean} autoDestroy
43163      * If true the container will automatically destroy any contained component that is removed from it, else
43164      * destruction must be handled manually.
43165      * Defaults to true.
43166      */
43167     autoDestroy : true,
43168
43169      /** @cfg {String} defaultType
43170       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
43171       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
43172       * <p>Defaults to <code>'panel'</code>.</p>
43173       */
43174     defaultType: 'panel',
43175
43176     isContainer : true,
43177
43178     /**
43179      * The number of container layout calls made on this object.
43180      * @property layoutCounter
43181      * @type {Number}
43182      * @private
43183      */
43184     layoutCounter : 0,
43185
43186     baseCls: Ext.baseCSSPrefix + 'container',
43187
43188     /**
43189      * @cfg {String[]} bubbleEvents
43190      * <p>An array of events that, when fired, should be bubbled to any parent container.
43191      * See {@link Ext.util.Observable#enableBubble}.
43192      * Defaults to <code>['add', 'remove']</code>.
43193      */
43194     bubbleEvents: ['add', 'remove'],
43195
43196     // @private
43197     initComponent : function(){
43198         var me = this;
43199         me.addEvents(
43200             /**
43201              * @event afterlayout
43202              * Fires when the components in this container are arranged by the associated layout manager.
43203              * @param {Ext.container.Container} this
43204              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
43205              */
43206             'afterlayout',
43207             /**
43208              * @event beforeadd
43209              * Fires before any {@link Ext.Component} is added or inserted into the container.
43210              * A handler can return false to cancel the add.
43211              * @param {Ext.container.Container} this
43212              * @param {Ext.Component} component The component being added
43213              * @param {Number} index The index at which the component will be added to the container's items collection
43214              */
43215             'beforeadd',
43216             /**
43217              * @event beforeremove
43218              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
43219              * false to cancel the remove.
43220              * @param {Ext.container.Container} this
43221              * @param {Ext.Component} component The component being removed
43222              */
43223             'beforeremove',
43224             /**
43225              * @event add
43226              * @bubbles
43227              * Fires after any {@link Ext.Component} is added or inserted into the container.
43228              * @param {Ext.container.Container} this
43229              * @param {Ext.Component} component The component that was added
43230              * @param {Number} index The index at which the component was added to the container's items collection
43231              */
43232             'add',
43233             /**
43234              * @event remove
43235              * @bubbles
43236              * Fires after any {@link Ext.Component} is removed from the container.
43237              * @param {Ext.container.Container} this
43238              * @param {Ext.Component} component The component that was removed
43239              */
43240             'remove'
43241         );
43242
43243         // layoutOnShow stack
43244         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
43245         me.callParent();
43246         me.initItems();
43247     },
43248
43249     // @private
43250     initItems : function() {
43251         var me = this,
43252             items = me.items;
43253
43254         /**
43255          * The MixedCollection containing all the child items of this container.
43256          * @property items
43257          * @type Ext.util.MixedCollection
43258          */
43259         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
43260
43261         if (items) {
43262             if (!Ext.isArray(items)) {
43263                 items = [items];
43264             }
43265
43266             me.add(items);
43267         }
43268     },
43269
43270     // @private
43271     afterRender : function() {
43272         this.getLayout();
43273         this.callParent();
43274     },
43275
43276     renderChildren: function () {
43277         var me = this,
43278             layout = me.getLayout();
43279
43280         me.callParent();
43281         // this component's elements exist, so now create the child components' elements
43282
43283         if (layout) {
43284             me.suspendLayout = true;
43285             layout.renderChildren();
43286             delete me.suspendLayout;
43287         }
43288     },
43289
43290     // @private
43291     setLayout : function(layout) {
43292         var currentLayout = this.layout;
43293
43294         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
43295             currentLayout.setOwner(null);
43296         }
43297
43298         this.layout = layout;
43299         layout.setOwner(this);
43300     },
43301
43302     /**
43303      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
43304      * If a layout has not been instantiated yet, that is done first
43305      * @return {Ext.layout.container.AbstractContainer} The layout
43306      */
43307     getLayout : function() {
43308         var me = this;
43309         if (!me.layout || !me.layout.isLayout) {
43310             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
43311         }
43312
43313         return me.layout;
43314     },
43315
43316     /**
43317      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
43318      * form most cases.
43319      * @return {Ext.container.Container} this
43320      */
43321     doLayout : function() {
43322         var me = this,
43323             layout = me.getLayout();
43324
43325         if (me.rendered && layout && !me.suspendLayout) {
43326             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
43327             if (!me.isFixedWidth() || !me.isFixedHeight()) {
43328                 // Only run the ComponentLayout if it is not already in progress
43329                 if (me.componentLayout.layoutBusy !== true) {
43330                     me.doComponentLayout();
43331                     if (me.componentLayout.layoutCancelled === true) {
43332                         layout.layout();
43333                     }
43334                 }
43335             }
43336             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
43337             else {
43338                 // Only run the ContainerLayout if it is not already in progress
43339                 if (layout.layoutBusy !== true) {
43340                     layout.layout();
43341                 }
43342             }
43343         }
43344
43345         return me;
43346     },
43347
43348     // @private
43349     afterLayout : function(layout) {
43350         ++this.layoutCounter;
43351         this.fireEvent('afterlayout', this, layout);
43352     },
43353
43354     // @private
43355     prepareItems : function(items, applyDefaults) {
43356         if (!Ext.isArray(items)) {
43357             items = [items];
43358         }
43359
43360         // Make sure defaults are applied and item is initialized
43361         var i = 0,
43362             len = items.length,
43363             item;
43364
43365         for (; i < len; i++) {
43366             item = items[i];
43367             if (applyDefaults) {
43368                 item = this.applyDefaults(item);
43369             }
43370             items[i] = this.lookupComponent(item);
43371         }
43372         return items;
43373     },
43374
43375     // @private
43376     applyDefaults : function(config) {
43377         var defaults = this.defaults;
43378
43379         if (defaults) {
43380             if (Ext.isFunction(defaults)) {
43381                 defaults = defaults.call(this, config);
43382             }
43383
43384             if (Ext.isString(config)) {
43385                 config = Ext.ComponentManager.get(config);
43386             }
43387             Ext.applyIf(config, defaults);
43388         }
43389
43390         return config;
43391     },
43392
43393     // @private
43394     lookupComponent : function(comp) {
43395         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
43396     },
43397
43398     // @private
43399     createComponent : function(config, defaultType) {
43400         // // add in ownerCt at creation time but then immediately
43401         // // remove so that onBeforeAdd can handle it
43402         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
43403         //
43404         // delete component.initialConfig.ownerCt;
43405         // delete component.ownerCt;
43406
43407         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
43408     },
43409
43410     // @private - used as the key lookup function for the items collection
43411     getComponentId : function(comp) {
43412         return comp.getItemId();
43413     },
43414
43415     /**
43416
43417 Adds {@link Ext.Component Component}(s) to this Container.
43418
43419 ##Description:##
43420
43421 - Fires the {@link #beforeadd} event before adding.
43422 - The Container's {@link #defaults default config values} will be applied
43423   accordingly (see `{@link #defaults}` for details).
43424 - Fires the `{@link #add}` event after the component has been added.
43425
43426 ##Notes:##
43427
43428 If the Container is __already rendered__ when `add`
43429 is called, it will render the newly added Component into its content area.
43430
43431 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
43432 will recalculate its internal layout at this time too.
43433
43434 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
43435
43436 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
43437
43438     tb = new {@link Ext.toolbar.Toolbar}({
43439         renderTo: document.body
43440     });  // toolbar is rendered
43441     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
43442
43443 ##Warning:##
43444
43445 Components directly managed by the BorderLayout layout manager
43446 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
43447 for more details.
43448
43449      * @param {Ext.Component[]/Ext.Component...} component
43450      * Either one or more Components to add or an Array of Components to add.
43451      * See `{@link #items}` for additional information.
43452      *
43453      * @return {Ext.Component[]/Ext.Component} The Components that were added.
43454      * @markdown
43455      */
43456     add : function() {
43457         var me = this,
43458             args = Array.prototype.slice.call(arguments),
43459             hasMultipleArgs,
43460             items,
43461             results = [],
43462             i,
43463             ln,
43464             item,
43465             index = -1,
43466             cmp;
43467
43468         if (typeof args[0] == 'number') {
43469             index = args.shift();
43470         }
43471
43472         hasMultipleArgs = args.length > 1;
43473         if (hasMultipleArgs || Ext.isArray(args[0])) {
43474
43475             items = hasMultipleArgs ? args : args[0];
43476             // Suspend Layouts while we add multiple items to the container
43477             me.suspendLayout = true;
43478             for (i = 0, ln = items.length; i < ln; i++) {
43479                 item = items[i];
43480
43481
43482                 if (index != -1) {
43483                     item = me.add(index + i, item);
43484                 } else {
43485                     item = me.add(item);
43486                 }
43487                 results.push(item);
43488             }
43489             // Resume Layouts now that all items have been added and do a single layout for all the items just added
43490             me.suspendLayout = false;
43491             me.doLayout();
43492             return results;
43493         }
43494
43495         cmp = me.prepareItems(args[0], true)[0];
43496
43497         // Floating Components are not added into the items collection
43498         // But they do get an upward ownerCt link so that they can traverse
43499         // up to their z-index parent.
43500         if (cmp.floating) {
43501             cmp.onAdded(me, index);
43502         } else {
43503             index = (index !== -1) ? index : me.items.length;
43504             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
43505                 me.items.insert(index, cmp);
43506                 cmp.onAdded(me, index);
43507                 me.onAdd(cmp, index);
43508                 me.fireEvent('add', me, cmp, index);
43509             }
43510             me.doLayout();
43511         }
43512         return cmp;
43513     },
43514
43515     onAdd : Ext.emptyFn,
43516     onRemove : Ext.emptyFn,
43517
43518     /**
43519      * Inserts a Component into this Container at a specified index. Fires the
43520      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
43521      * Component has been inserted.
43522      * @param {Number} index The index at which the Component will be inserted
43523      * into the Container's items collection
43524      * @param {Ext.Component} component The child Component to insert.<br><br>
43525      * Ext uses lazy rendering, and will only render the inserted Component should
43526      * it become necessary.<br><br>
43527      * A Component config object may be passed in order to avoid the overhead of
43528      * constructing a real Component object if lazy rendering might mean that the
43529      * inserted Component will not be rendered immediately. To take advantage of
43530      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
43531      * property to the registered type of the Component wanted.<br><br>
43532      * For a list of all available xtypes, see {@link Ext.Component}.
43533      * @return {Ext.Component} component The Component (or config object) that was
43534      * inserted with the Container's default config values applied.
43535      */
43536     insert : function(index, comp) {
43537         return this.add(index, comp);
43538     },
43539
43540     /**
43541      * Moves a Component within the Container
43542      * @param {Number} fromIdx The index the Component you wish to move is currently at.
43543      * @param {Number} toIdx The new index for the Component.
43544      * @return {Ext.Component} component The Component (or config object) that was moved.
43545      */
43546     move : function(fromIdx, toIdx) {
43547         var items = this.items,
43548             item;
43549         item = items.removeAt(fromIdx);
43550         if (item === false) {
43551             return false;
43552         }
43553         items.insert(toIdx, item);
43554         this.doLayout();
43555         return item;
43556     },
43557
43558     // @private
43559     onBeforeAdd : function(item) {
43560         var me = this;
43561
43562         if (item.ownerCt) {
43563             item.ownerCt.remove(item, false);
43564         }
43565
43566         if (me.border === false || me.border === 0) {
43567             item.border = (item.border === true);
43568         }
43569     },
43570
43571     /**
43572      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
43573      * the {@link #remove} event after the component has been removed.
43574      * @param {Ext.Component/String} component The component reference or id to remove.
43575      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43576      * Defaults to the value of this Container's {@link #autoDestroy} config.
43577      * @return {Ext.Component} component The Component that was removed.
43578      */
43579     remove : function(comp, autoDestroy) {
43580         var me = this,
43581             c = me.getComponent(comp);
43582
43583         if (c && me.fireEvent('beforeremove', me, c) !== false) {
43584             me.doRemove(c, autoDestroy);
43585             me.fireEvent('remove', me, c);
43586         }
43587
43588         return c;
43589     },
43590
43591     // @private
43592     doRemove : function(component, autoDestroy) {
43593         var me = this,
43594             layout = me.layout,
43595             hasLayout = layout && me.rendered;
43596
43597         me.items.remove(component);
43598         component.onRemoved();
43599
43600         if (hasLayout) {
43601             layout.onRemove(component);
43602         }
43603
43604         me.onRemove(component, autoDestroy);
43605
43606         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
43607             component.destroy();
43608         }
43609
43610         if (hasLayout && !autoDestroy) {
43611             layout.afterRemove(component);
43612         }
43613
43614         if (!me.destroying) {
43615             me.doLayout();
43616         }
43617     },
43618
43619     /**
43620      * Removes all components from this container.
43621      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43622      * Defaults to the value of this Container's {@link #autoDestroy} config.
43623      * @return {Ext.Component[]} Array of the destroyed components
43624      */
43625     removeAll : function(autoDestroy) {
43626         var me = this,
43627             removeItems = me.items.items.slice(),
43628             items = [],
43629             i = 0,
43630             len = removeItems.length,
43631             item;
43632
43633         // Suspend Layouts while we remove multiple items from the container
43634         me.suspendLayout = true;
43635         for (; i < len; i++) {
43636             item = removeItems[i];
43637             me.remove(item, autoDestroy);
43638
43639             if (item.ownerCt !== me) {
43640                 items.push(item);
43641             }
43642         }
43643
43644         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
43645         me.suspendLayout = false;
43646         if (len) {
43647             me.doLayout();
43648         }
43649         return items;
43650     },
43651
43652     // Used by ComponentQuery to retrieve all of the items
43653     // which can potentially be considered a child of this Container.
43654     // This should be overriden by components which have child items
43655     // that are not contained in items. For example dockedItems, menu, etc
43656     // IMPORTANT note for maintainers:
43657     //  Items are returned in tree traversal order. Each item is appended to the result array
43658     //  followed by the results of that child's getRefItems call.
43659     //  Floating child items are appended after internal child items.
43660     getRefItems : function(deep) {
43661         var me = this,
43662             items = me.items.items,
43663             len = items.length,
43664             i = 0,
43665             item,
43666             result = [];
43667
43668         for (; i < len; i++) {
43669             item = items[i];
43670             result.push(item);
43671             if (deep && item.getRefItems) {
43672                 result.push.apply(result, item.getRefItems(true));
43673             }
43674         }
43675
43676         // Append floating items to the list.
43677         // These will only be present after they are rendered.
43678         if (me.floatingItems && me.floatingItems.accessList) {
43679             result.push.apply(result, me.floatingItems.accessList);
43680         }
43681
43682         return result;
43683     },
43684
43685     /**
43686      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
43687      * each component. The scope (<code>this</code> reference) of the
43688      * function call will be the scope provided or the current component. The arguments to the function
43689      * will be the args provided or the current component. If the function returns false at any point,
43690      * the cascade is stopped on that branch.
43691      * @param {Function} fn The function to call
43692      * @param {Object} [scope] The scope of the function (defaults to current component)
43693      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
43694      * @return {Ext.Container} this
43695      */
43696     cascade : function(fn, scope, origArgs){
43697         var me = this,
43698             cs = me.items ? me.items.items : [],
43699             len = cs.length,
43700             i = 0,
43701             c,
43702             args = origArgs ? origArgs.concat(me) : [me],
43703             componentIndex = args.length - 1;
43704
43705         if (fn.apply(scope || me, args) !== false) {
43706             for(; i < len; i++){
43707                 c = cs[i];
43708                 if (c.cascade) {
43709                     c.cascade(fn, scope, origArgs);
43710                 } else {
43711                     args[componentIndex] = c;
43712                     fn.apply(scope || cs, args);
43713                 }
43714             }
43715         }
43716         return this;
43717     },
43718
43719     /**
43720      * Examines this container's <code>{@link #items}</code> <b>property</b>
43721      * and gets a direct child component of this container.
43722      * @param {String/Number} comp This parameter may be any of the following:
43723      * <div><ul class="mdetail-params">
43724      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
43725      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
43726      * <li>a <b><code>Number</code></b> : representing the position of the child component
43727      * within the <code>{@link #items}</code> <b>property</b></li>
43728      * </ul></div>
43729      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
43730      * @return Ext.Component The component (if found).
43731      */
43732     getComponent : function(comp) {
43733         if (Ext.isObject(comp)) {
43734             comp = comp.getItemId();
43735         }
43736
43737         return this.items.get(comp);
43738     },
43739
43740     /**
43741      * Retrieves all descendant components which match the passed selector.
43742      * Executes an Ext.ComponentQuery.query using this container as its root.
43743      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
43744      * If no selector is specified all items will be returned.
43745      * @return {Ext.Component[]} Components which matched the selector
43746      */
43747     query : function(selector) {
43748         selector = selector || '*';
43749         return Ext.ComponentQuery.query(selector, this);
43750     },
43751
43752     /**
43753      * Retrieves the first direct child of this container which matches the passed selector.
43754      * The passed in selector must comply with an Ext.ComponentQuery selector.
43755      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43756      * specified, the first child will be returned.
43757      * @return Ext.Component
43758      */
43759     child : function(selector) {
43760         selector = selector || '';
43761         return this.query('> ' + selector)[0] || null;
43762     },
43763
43764     /**
43765      * Retrieves the first descendant of this container which matches the passed selector.
43766      * The passed in selector must comply with an Ext.ComponentQuery selector.
43767      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43768      * specified, the first child will be returned.
43769      * @return Ext.Component
43770      */
43771     down : function(selector) {
43772         return this.query(selector)[0] || null;
43773     },
43774
43775     // inherit docs
43776     show : function() {
43777         this.callParent(arguments);
43778         this.performDeferredLayouts();
43779         return this;
43780     },
43781
43782     // Lay out any descendant containers who queued a layout operation during the time this was hidden
43783     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
43784     performDeferredLayouts: function() {
43785         var layoutCollection = this.layoutOnShow,
43786             ln = layoutCollection.getCount(),
43787             i = 0,
43788             needsLayout,
43789             item;
43790
43791         for (; i < ln; i++) {
43792             item = layoutCollection.get(i);
43793             needsLayout = item.needsLayout;
43794
43795             if (Ext.isObject(needsLayout)) {
43796                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
43797             }
43798         }
43799         layoutCollection.clear();
43800     },
43801
43802     //@private
43803     // Enable all immediate children that was previously disabled
43804     onEnable: function() {
43805         Ext.Array.each(this.query('[isFormField]'), function(item) {
43806             if (item.resetDisable) {
43807                 item.enable();
43808                 delete item.resetDisable;
43809             }
43810         });
43811         this.callParent();
43812     },
43813
43814     // @private
43815     // Disable all immediate children that was previously disabled
43816     onDisable: function() {
43817         Ext.Array.each(this.query('[isFormField]'), function(item) {
43818             if (item.resetDisable !== false && !item.disabled) {
43819                 item.disable();
43820                 item.resetDisable = true;
43821             }
43822         });
43823         this.callParent();
43824     },
43825
43826     /**
43827      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
43828      * from being executed.
43829      */
43830     beforeLayout: function() {
43831         return true;
43832     },
43833
43834     // @private
43835     beforeDestroy : function() {
43836         var me = this,
43837             items = me.items,
43838             c;
43839
43840         if (items) {
43841             while ((c = items.first())) {
43842                 me.doRemove(c, true);
43843             }
43844         }
43845
43846         Ext.destroy(
43847             me.layout
43848         );
43849         me.callParent();
43850     }
43851 });
43852
43853 /**
43854  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
43855  * containing items, namely adding, inserting and removing items.
43856  *
43857  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
43858  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
43859  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
43860  * {@link Ext.Component#autoEl autoEl} config option.
43861  *
43862  * The code below illustrates how to explicitly create a Container:
43863  *
43864  *     @example
43865  *     // Explicitly create a Container
43866  *     Ext.create('Ext.container.Container', {
43867  *         layout: {
43868  *             type: 'hbox'
43869  *         },
43870  *         width: 400,
43871  *         renderTo: Ext.getBody(),
43872  *         border: 1,
43873  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
43874  *         defaults: {
43875  *             labelWidth: 80,
43876  *             // implicitly create Container by specifying xtype
43877  *             xtype: 'datefield',
43878  *             flex: 1,
43879  *             style: {
43880  *                 padding: '10px'
43881  *             }
43882  *         },
43883  *         items: [{
43884  *             xtype: 'datefield',
43885  *             name: 'startDate',
43886  *             fieldLabel: 'Start date'
43887  *         },{
43888  *             xtype: 'datefield',
43889  *             name: 'endDate',
43890  *             fieldLabel: 'End date'
43891  *         }]
43892  *     });
43893  *
43894  * ## Layout
43895  *
43896  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
43897  * the Container using the `{@link #layout}` configuration property.
43898  *
43899  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
43900  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
43901  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
43902  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
43903  * inside the Container, and **does not apply any sizing** at all.
43904  *
43905  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
43906  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
43907  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
43908  * any way when the Container is resized.
43909  *
43910  * Certain layout managers allow dynamic addition of child components. Those that do include
43911  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
43912  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
43913  *
43914  *     //  Create the GridPanel.
43915  *     var myNewGrid = new Ext.grid.Panel({
43916  *         store: myStore,
43917  *         headers: myHeaders,
43918  *         title: 'Results', // the title becomes the title of the tab
43919  *     });
43920  *
43921  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
43922  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
43923  *
43924  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
43925  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
43926  * Ext.layout.container.Fit fit} exactly into its client area.
43927  *
43928  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
43929  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
43930  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
43931  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
43932  * GridPanel will not be sized as expected.
43933  *
43934  * ## Adding via remote configuration
43935  *
43936  * A server side script can be used to add Components which are generated dynamically on the server. An example of
43937  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
43938  *
43939  *     // execute an Ajax request to invoke server side script:
43940  *     Ext.Ajax.request({
43941  *         url: 'gen-invoice-grid.php',
43942  *         // send additional parameters to instruct server script
43943  *         params: {
43944  *             startDate: Ext.getCmp('start-date').getValue(),
43945  *             endDate: Ext.getCmp('end-date').getValue()
43946  *         },
43947  *         // process the response object to add it to the TabPanel:
43948  *         success: function(xhr) {
43949  *             var newComponent = eval(xhr.responseText); // see discussion below
43950  *             myTabPanel.add(newComponent); // add the component to the TabPanel
43951  *             myTabPanel.setActiveTab(newComponent);
43952  *         },
43953  *         failure: function() {
43954  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
43955  *         }
43956  *     });
43957  *
43958  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
43959  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
43960  *
43961  *     {
43962  *         "xtype": 'grid',
43963  *         "title": 'Invoice Report',
43964  *         "store": {
43965  *             "model": 'Invoice',
43966  *             "proxy": {
43967  *                 "type": 'ajax',
43968  *                 "url": 'get-invoice-data.php',
43969  *                 "reader": {
43970  *                     "type": 'json'
43971  *                     "record": 'transaction',
43972  *                     "idProperty": 'id',
43973  *                     "totalRecords": 'total'
43974  *                 })
43975  *             },
43976  *             "autoLoad": {
43977  *                 "params": {
43978  *                     "startDate": '01/01/2008',
43979  *                     "endDate": '01/31/2008'
43980  *                 }
43981  *             }
43982  *         },
43983  *         "headers": [
43984  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
43985  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
43986  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
43987  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
43988  *         ]
43989  *     }
43990  *
43991  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
43992  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
43993  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
43994  *
43995  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
43996  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
43997  * into the code since these are all known on the server.
43998  */
43999 Ext.define('Ext.container.Container', {
44000     extend: 'Ext.container.AbstractContainer',
44001     alias: 'widget.container',
44002     alternateClassName: 'Ext.Container',
44003
44004     /**
44005      * Return the immediate child Component in which the passed element is located.
44006      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
44007      * @return {Ext.Component} The child item which contains the passed element.
44008      */
44009     getChildByElement: function(el) {
44010         var item,
44011             itemEl,
44012             i = 0,
44013             it = this.items.items,
44014             ln = it.length;
44015
44016         el = Ext.getDom(el);
44017         for (; i < ln; i++) {
44018             item = it[i];
44019             itemEl = item.getEl();
44020             if ((itemEl.dom === el) || itemEl.contains(el)) {
44021                 return item;
44022             }
44023         }
44024         return null;
44025     }
44026 });
44027
44028 /**
44029  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
44030  * the right-justified button container.
44031  *
44032  *     @example
44033  *     Ext.create('Ext.panel.Panel', {
44034  *          title: 'Toolbar Fill Example',
44035  *          width: 300,
44036  *          height: 200,
44037  *          tbar : [
44038  *              'Item 1',
44039  *              { xtype: 'tbfill' },
44040  *              'Item 2'
44041  *          ],
44042  *          renderTo: Ext.getBody()
44043  *      });
44044  */
44045 Ext.define('Ext.toolbar.Fill', {
44046     extend: 'Ext.Component',
44047     alias: 'widget.tbfill',
44048     alternateClassName: 'Ext.Toolbar.Fill',
44049     isFill : true,
44050     flex: 1
44051 });
44052 /**
44053  * @class Ext.toolbar.Item
44054  * @extends Ext.Component
44055  * The base class that other non-interacting Toolbar Item classes should extend in order to
44056  * get some basic common toolbar item functionality.
44057  */
44058 Ext.define('Ext.toolbar.Item', {
44059     extend: 'Ext.Component',
44060     alias: 'widget.tbitem',
44061     alternateClassName: 'Ext.Toolbar.Item',
44062     enable:Ext.emptyFn,
44063     disable:Ext.emptyFn,
44064     focus:Ext.emptyFn
44065     /**
44066      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
44067      */
44068 });
44069 /**
44070  * @class Ext.toolbar.Separator
44071  * @extends Ext.toolbar.Item
44072  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
44073  *
44074  *     @example
44075  *     Ext.create('Ext.panel.Panel', {
44076  *         title: 'Toolbar Seperator Example',
44077  *         width: 300,
44078  *         height: 200,
44079  *         tbar : [
44080  *             'Item 1',
44081  *             { xtype: 'tbseparator' },
44082  *             'Item 2'
44083  *         ],
44084  *         renderTo: Ext.getBody()
44085  *     });
44086  */
44087 Ext.define('Ext.toolbar.Separator', {
44088     extend: 'Ext.toolbar.Item',
44089     alias: 'widget.tbseparator',
44090     alternateClassName: 'Ext.Toolbar.Separator',
44091     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
44092     focusable: false
44093 });
44094 /**
44095  * @class Ext.menu.Manager
44096  * Provides a common registry of all menus on a page.
44097  * @singleton
44098  */
44099 Ext.define('Ext.menu.Manager', {
44100     singleton: true,
44101     requires: [
44102         'Ext.util.MixedCollection',
44103         'Ext.util.KeyMap'
44104     ],
44105     alternateClassName: 'Ext.menu.MenuMgr',
44106
44107     uses: ['Ext.menu.Menu'],
44108
44109     menus: {},
44110     groups: {},
44111     attached: false,
44112     lastShow: new Date(),
44113
44114     init: function() {
44115         var me = this;
44116         
44117         me.active = Ext.create('Ext.util.MixedCollection');
44118         Ext.getDoc().addKeyListener(27, function() {
44119             if (me.active.length > 0) {
44120                 me.hideAll();
44121             }
44122         }, me);
44123     },
44124
44125     /**
44126      * Hides all menus that are currently visible
44127      * @return {Boolean} success True if any active menus were hidden.
44128      */
44129     hideAll: function() {
44130         var active = this.active,
44131             c;
44132         if (active && active.length > 0) {
44133             c = active.clone();
44134             c.each(function(m) {
44135                 m.hide();
44136             });
44137             return true;
44138         }
44139         return false;
44140     },
44141
44142     onHide: function(m) {
44143         var me = this,
44144             active = me.active;
44145         active.remove(m);
44146         if (active.length < 1) {
44147             Ext.getDoc().un('mousedown', me.onMouseDown, me);
44148             me.attached = false;
44149         }
44150     },
44151
44152     onShow: function(m) {
44153         var me = this,
44154             active   = me.active,
44155             last     = active.last(),
44156             attached = me.attached,
44157             menuEl   = m.getEl(),
44158             zIndex;
44159
44160         me.lastShow = new Date();
44161         active.add(m);
44162         if (!attached) {
44163             Ext.getDoc().on('mousedown', me.onMouseDown, me);
44164             me.attached = true;
44165         }
44166         m.toFront();
44167     },
44168
44169     onBeforeHide: function(m) {
44170         if (m.activeChild) {
44171             m.activeChild.hide();
44172         }
44173         if (m.autoHideTimer) {
44174             clearTimeout(m.autoHideTimer);
44175             delete m.autoHideTimer;
44176         }
44177     },
44178
44179     onBeforeShow: function(m) {
44180         var active = this.active,
44181             parentMenu = m.parentMenu;
44182             
44183         active.remove(m);
44184         if (!parentMenu && !m.allowOtherMenus) {
44185             this.hideAll();
44186         }
44187         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
44188             parentMenu.activeChild.hide();
44189         }
44190     },
44191
44192     // private
44193     onMouseDown: function(e) {
44194         var me = this,
44195             active = me.active,
44196             lastShow = me.lastShow,
44197             target = e.target;
44198
44199         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
44200             me.hideAll();
44201             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
44202             // fired on the element, so we'll focus it here
44203             if (Ext.isIE && Ext.fly(target).focusable()) {
44204                 target.focus();
44205             }
44206         }
44207     },
44208
44209     // private
44210     register: function(menu) {
44211         var me = this;
44212
44213         if (!me.active) {
44214             me.init();
44215         }
44216
44217         if (menu.floating) {
44218             me.menus[menu.id] = menu;
44219             menu.on({
44220                 beforehide: me.onBeforeHide,
44221                 hide: me.onHide,
44222                 beforeshow: me.onBeforeShow,
44223                 show: me.onShow,
44224                 scope: me
44225             });
44226         }
44227     },
44228
44229     /**
44230      * Returns a {@link Ext.menu.Menu} object
44231      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
44232      * be used to generate and return a new Menu this.
44233      * @return {Ext.menu.Menu} The specified menu, or null if none are found
44234      */
44235     get: function(menu) {
44236         var menus = this.menus;
44237         
44238         if (typeof menu == 'string') { // menu id
44239             if (!menus) {  // not initialized, no menus to return
44240                 return null;
44241             }
44242             return menus[menu];
44243         } else if (menu.isMenu) {  // menu instance
44244             return menu;
44245         } else if (Ext.isArray(menu)) { // array of menu items
44246             return Ext.create('Ext.menu.Menu', {items:menu});
44247         } else { // otherwise, must be a config
44248             return Ext.ComponentManager.create(menu, 'menu');
44249         }
44250     },
44251
44252     // private
44253     unregister: function(menu) {
44254         var me = this,
44255             menus = me.menus,
44256             active = me.active;
44257
44258         delete menus[menu.id];
44259         active.remove(menu);
44260         menu.un({
44261             beforehide: me.onBeforeHide,
44262             hide: me.onHide,
44263             beforeshow: me.onBeforeShow,
44264             show: me.onShow,
44265             scope: me
44266         });
44267     },
44268
44269     // private
44270     registerCheckable: function(menuItem) {
44271         var groups  = this.groups,
44272             groupId = menuItem.group;
44273
44274         if (groupId) {
44275             if (!groups[groupId]) {
44276                 groups[groupId] = [];
44277             }
44278
44279             groups[groupId].push(menuItem);
44280         }
44281     },
44282
44283     // private
44284     unregisterCheckable: function(menuItem) {
44285         var groups  = this.groups,
44286             groupId = menuItem.group;
44287
44288         if (groupId) {
44289             Ext.Array.remove(groups[groupId], menuItem);
44290         }
44291     },
44292
44293     onCheckChange: function(menuItem, state) {
44294         var groups  = this.groups,
44295             groupId = menuItem.group,
44296             i       = 0,
44297             group, ln, curr;
44298
44299         if (groupId && state) {
44300             group = groups[groupId];
44301             ln = group.length;
44302             for (; i < ln; i++) {
44303                 curr = group[i];
44304                 if (curr != menuItem) {
44305                     curr.setChecked(false);
44306                 }
44307             }
44308         }
44309     }
44310 });
44311 /**
44312  * Component layout for buttons
44313  * @class Ext.layout.component.Button
44314  * @extends Ext.layout.component.Component
44315  * @private
44316  */
44317 Ext.define('Ext.layout.component.Button', {
44318
44319     /* Begin Definitions */
44320
44321     alias: ['layout.button'],
44322
44323     extend: 'Ext.layout.component.Component',
44324
44325     /* End Definitions */
44326
44327     type: 'button',
44328
44329     cellClsRE: /-btn-(tl|br)\b/,
44330     htmlRE: /<.*>/,
44331
44332     beforeLayout: function() {
44333         return this.callParent(arguments) || this.lastText !== this.owner.text;
44334     },
44335
44336     /**
44337      * Set the dimensions of the inner &lt;button&gt; element to match the
44338      * component dimensions.
44339      */
44340     onLayout: function(width, height) {
44341         var me = this,
44342             isNum = Ext.isNumber,
44343             owner = me.owner,
44344             ownerEl = owner.el,
44345             btnEl = owner.btnEl,
44346             btnInnerEl = owner.btnInnerEl,
44347             btnIconEl = owner.btnIconEl,
44348             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
44349             minWidth = owner.minWidth,
44350             maxWidth = owner.maxWidth,
44351             ownerWidth, btnFrameWidth, metrics;
44352
44353         me.getTargetInfo();
44354         me.callParent(arguments);
44355
44356         btnInnerEl.unclip();
44357         me.setTargetSize(width, height);
44358
44359         if (!isNum(width)) {
44360             // In IE7 strict mode button elements with width:auto get strange extra side margins within
44361             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
44362             // the size of the text and set the width to match.
44363             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
44364                 btnFrameWidth = me.btnFrameWidth;
44365                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
44366                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
44367                 btnEl.setWidth(metrics.width + btnFrameWidth);
44368                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
44369
44370                 if (sizeIconEl) {
44371                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
44372                 }
44373             } else {
44374                 // Remove any previous fixed widths
44375                 ownerEl.setWidth(null);
44376                 btnEl.setWidth(null);
44377                 btnInnerEl.setWidth(null);
44378                 btnIconEl.setWidth(null);
44379             }
44380
44381             // Handle maxWidth/minWidth config
44382             if (minWidth || maxWidth) {
44383                 ownerWidth = ownerEl.getWidth();
44384                 if (minWidth && (ownerWidth < minWidth)) {
44385                     me.setTargetSize(minWidth, height);
44386                 }
44387                 else if (maxWidth && (ownerWidth > maxWidth)) {
44388                     btnInnerEl.clip();
44389                     me.setTargetSize(maxWidth, height);
44390                 }
44391             }
44392         }
44393
44394         this.lastText = owner.text;
44395     },
44396
44397     setTargetSize: function(width, height) {
44398         var me = this,
44399             owner = me.owner,
44400             isNum = Ext.isNumber,
44401             btnInnerEl = owner.btnInnerEl,
44402             btnWidth = (isNum(width) ? width - me.adjWidth : width),
44403             btnHeight = (isNum(height) ? height - me.adjHeight : height),
44404             btnFrameHeight = me.btnFrameHeight,
44405             text = owner.getText(),
44406             textHeight;
44407
44408         me.callParent(arguments);
44409         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
44410         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44411         if (btnHeight >= 0) {
44412             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
44413         }
44414
44415         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
44416         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
44417         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
44418         // vertically within the button's height. This is more expensive than the basic line-height approach so
44419         // we only do it if the text contains markup.
44420         if (text && this.htmlRE.test(text)) {
44421             btnInnerEl.setStyle('line-height', 'normal');
44422             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
44423             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
44424             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44425         }
44426     },
44427
44428     getTargetInfo: function() {
44429         var me = this,
44430             owner = me.owner,
44431             ownerEl = owner.el,
44432             frameSize = me.frameSize,
44433             frameBody = owner.frameBody,
44434             btnWrap = owner.btnWrap,
44435             innerEl = owner.btnInnerEl;
44436
44437         if (!('adjWidth' in me)) {
44438             Ext.apply(me, {
44439                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
44440                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
44441                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
44442                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
44443                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
44444                 btnFrameWidth: innerEl.getFrameWidth('lr'),
44445                 btnFrameHeight: innerEl.getFrameWidth('tb'),
44446                 btnFrameTop: innerEl.getFrameWidth('t')
44447             });
44448         }
44449
44450         return me.callParent();
44451     }
44452 });
44453 /**
44454  * @docauthor Robert Dougan <rob@sencha.com>
44455  *
44456  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
44457  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
44458  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
44459  * a user clicks the button, or use {@link #listeners listeners} for other events such as
44460  * {@link #mouseover mouseover}. Example usage:
44461  *
44462  *     @example
44463  *     Ext.create('Ext.Button', {
44464  *         text: 'Click me',
44465  *         renderTo: Ext.getBody(),
44466  *         handler: function() {
44467  *             alert('You clicked the button!')
44468  *         }
44469  *     });
44470  *
44471  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
44472  * method.  Example usage:
44473  *
44474  *     @example
44475  *     Ext.create('Ext.Button', {
44476  *         text    : 'Dynamic Handler Button',
44477  *         renderTo: Ext.getBody(),
44478  *         handler : function() {
44479  *             // this button will spit out a different number every time you click it.
44480  *             // so firstly we must check if that number is already set:
44481  *             if (this.clickCount) {
44482  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
44483  *                 this.clickCount++;
44484  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
44485  *             } else {
44486  *                 // if the clickCount property is not set, we will set it and alert the user
44487  *                 this.clickCount = 1;
44488  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
44489  *             }
44490  *         }
44491  *     });
44492  *
44493  * A button within a container:
44494  *
44495  *     @example
44496  *     Ext.create('Ext.Container', {
44497  *         renderTo: Ext.getBody(),
44498  *         items   : [
44499  *             {
44500  *                 xtype: 'button',
44501  *                 text : 'My Button'
44502  *             }
44503  *         ]
44504  *     });
44505  *
44506  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
44507  *
44508  * - `'small'`
44509  * - `'medium'`
44510  * - `'large'`
44511  *
44512  * Example usage:
44513  *
44514  *     @example
44515  *     Ext.create('Ext.Button', {
44516  *         renderTo: document.body,
44517  *         text    : 'Click me',
44518  *         scale   : 'large'
44519  *     });
44520  *
44521  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
44522  * Example usage:
44523  *
44524  *     @example
44525  *     Ext.create('Ext.Button', {
44526  *         renderTo: Ext.getBody(),
44527  *         text: 'Click Me',
44528  *         enableToggle: true
44529  *     });
44530  *
44531  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
44532  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
44533  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
44534  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
44535  * on button.  Example usage:
44536  *
44537  *     @example
44538  *     Ext.create('Ext.Button', {
44539  *         text      : 'Menu button',
44540  *         renderTo  : Ext.getBody(),
44541  *         arrowAlign: 'bottom',
44542  *         menu      : [
44543  *             {text: 'Item 1'},
44544  *             {text: 'Item 2'},
44545  *             {text: 'Item 3'},
44546  *             {text: 'Item 4'}
44547  *         ]
44548  *     });
44549  *
44550  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
44551  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
44552  *
44553  * - `click`
44554  * - `toggle`
44555  * - `mouseover`
44556  * - `mouseout`
44557  * - `mouseshow`
44558  * - `menuhide`
44559  * - `menutriggerover`
44560  * - `menutriggerout`
44561  *
44562  * Example usage:
44563  *
44564  *     @example
44565  *     Ext.create('Ext.Button', {
44566  *         text     : 'Button',
44567  *         renderTo : Ext.getBody(),
44568  *         listeners: {
44569  *             click: function() {
44570  *                 // this == the button, as we are in the local scope
44571  *                 this.setText('I was clicked!');
44572  *             },
44573  *             mouseover: function() {
44574  *                 // set a new config which says we moused over, if not already set
44575  *                 if (!this.mousedOver) {
44576  *                     this.mousedOver = true;
44577  *                     alert('You moused over a button!\n\nI wont do this again.');
44578  *                 }
44579  *             }
44580  *         }
44581  *     });
44582  */
44583 Ext.define('Ext.button.Button', {
44584
44585     /* Begin Definitions */
44586     alias: 'widget.button',
44587     extend: 'Ext.Component',
44588
44589     requires: [
44590         'Ext.menu.Manager',
44591         'Ext.util.ClickRepeater',
44592         'Ext.layout.component.Button',
44593         'Ext.util.TextMetrics',
44594         'Ext.util.KeyMap'
44595     ],
44596
44597     alternateClassName: 'Ext.Button',
44598     /* End Definitions */
44599
44600     isButton: true,
44601     componentLayout: 'button',
44602
44603     /**
44604      * @property {Boolean} hidden
44605      * True if this button is hidden. Read-only.
44606      */
44607     hidden: false,
44608
44609     /**
44610      * @property {Boolean} disabled
44611      * True if this button is disabled. Read-only.
44612      */
44613     disabled: false,
44614
44615     /**
44616      * @property {Boolean} pressed
44617      * True if this button is pressed (only if enableToggle = true). Read-only.
44618      */
44619     pressed: false,
44620
44621     /**
44622      * @cfg {String} text
44623      * The button text to be used as innerHTML (html tags are accepted).
44624      */
44625
44626     /**
44627      * @cfg {String} icon
44628      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
44629      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
44630      */
44631
44632     /**
44633      * @cfg {Function} handler
44634      * A function called when the button is clicked (can be used instead of click event).
44635      * @cfg {Ext.button.Button} handler.button This button.
44636      * @cfg {Ext.EventObject} handler.e The click event.
44637      */
44638
44639     /**
44640      * @cfg {Number} minWidth
44641      * The minimum width for this button (used to give a set of buttons a common width).
44642      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
44643      */
44644
44645     /**
44646      * @cfg {String/Object} tooltip
44647      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
44648      * QuickTips config object.
44649      */
44650
44651     /**
44652      * @cfg {Boolean} [hidden=false]
44653      * True to start hidden.
44654      */
44655
44656     /**
44657      * @cfg {Boolean} [disabled=true]
44658      * True to start disabled.
44659      */
44660
44661     /**
44662      * @cfg {Boolean} [pressed=false]
44663      * True to start pressed (only if enableToggle = true)
44664      */
44665
44666     /**
44667      * @cfg {String} toggleGroup
44668      * The group this toggle button is a member of (only 1 per group can be pressed)
44669      */
44670
44671     /**
44672      * @cfg {Boolean/Object} [repeat=false]
44673      * True to repeat fire the click event while the mouse is down. This can also be a
44674      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
44675      */
44676
44677     /**
44678      * @cfg {Number} tabIndex
44679      * Set a DOM tabIndex for this button.
44680      */
44681
44682     /**
44683      * @cfg {Boolean} [allowDepress=true]
44684      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
44685      */
44686
44687     /**
44688      * @cfg {Boolean} [enableToggle=false]
44689      * True to enable pressed/not pressed toggling.
44690      */
44691     enableToggle: false,
44692
44693     /**
44694      * @cfg {Function} toggleHandler
44695      * Function called when a Button with {@link #enableToggle} set to true is clicked.
44696      * @cfg {Ext.button.Button} toggleHandler.button This button.
44697      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
44698      */
44699
44700     /**
44701      * @cfg {Ext.menu.Menu/String/Object} menu
44702      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
44703      */
44704
44705     /**
44706      * @cfg {String} menuAlign
44707      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
44708      */
44709     menuAlign: 'tl-bl?',
44710
44711     /**
44712      * @cfg {String} textAlign
44713      * The text alignment for this button (center, left, right).
44714      */
44715     textAlign: 'center',
44716
44717     /**
44718      * @cfg {String} overflowText
44719      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
44720      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
44721      */
44722
44723     /**
44724      * @cfg {String} iconCls
44725      * A css class which sets a background image to be used as the icon for this button.
44726      */
44727
44728     /**
44729      * @cfg {String} type
44730      * The type of `<input>` to create: submit, reset or button.
44731      */
44732     type: 'button',
44733
44734     /**
44735      * @cfg {String} clickEvent
44736      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
44737      */
44738     clickEvent: 'click',
44739
44740     /**
44741      * @cfg {Boolean} preventDefault
44742      * True to prevent the default action when the {@link #clickEvent} is processed.
44743      */
44744     preventDefault: true,
44745
44746     /**
44747      * @cfg {Boolean} handleMouseEvents
44748      * False to disable visual cues on mouseover, mouseout and mousedown.
44749      */
44750     handleMouseEvents: true,
44751
44752     /**
44753      * @cfg {String} tooltipType
44754      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
44755      */
44756     tooltipType: 'qtip',
44757
44758     /**
44759      * @cfg {String} [baseCls='x-btn']
44760      * The base CSS class to add to all buttons.
44761      */
44762     baseCls: Ext.baseCSSPrefix + 'btn',
44763
44764     /**
44765      * @cfg {String} pressedCls
44766      * The CSS class to add to a button when it is in the pressed state.
44767      */
44768     pressedCls: 'pressed',
44769
44770     /**
44771      * @cfg {String} overCls
44772      * The CSS class to add to a button when it is in the over (hovered) state.
44773      */
44774     overCls: 'over',
44775
44776     /**
44777      * @cfg {String} focusCls
44778      * The CSS class to add to a button when it is in the focussed state.
44779      */
44780     focusCls: 'focus',
44781
44782     /**
44783      * @cfg {String} menuActiveCls
44784      * The CSS class to add to a button when it's menu is active.
44785      */
44786     menuActiveCls: 'menu-active',
44787
44788     /**
44789      * @cfg {String} href
44790      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
44791      *
44792      *     handler: function() { window.location = "http://www.sencha.com" }
44793      */
44794
44795     /**
44796      * @cfg {Object} baseParams
44797      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
44798      */
44799
44800     /**
44801      * @cfg {Object} params
44802      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
44803      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
44804      */
44805
44806     ariaRole: 'button',
44807
44808     // inherited
44809     renderTpl:
44810         '<em id="{id}-btnWrap" class="{splitCls}">' +
44811             '<tpl if="href">' +
44812                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
44813                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
44814                         '{text}' +
44815                     '</span>' +
44816                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
44817                 '</a>' +
44818             '</tpl>' +
44819             '<tpl if="!href">' +
44820                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
44821                     // the autocomplete="off" is required to prevent Firefox from remembering
44822                     // the button's disabled state between page reloads.
44823                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
44824                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
44825                         '{text}' +
44826                     '</span>' +
44827                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
44828                 '</button>' +
44829             '</tpl>' +
44830         '</em>' ,
44831
44832     /**
44833      * @cfg {String} scale
44834      * The size of the Button. Three values are allowed:
44835      *
44836      * - 'small' - Results in the button element being 16px high.
44837      * - 'medium' - Results in the button element being 24px high.
44838      * - 'large' - Results in the button element being 32px high.
44839      */
44840     scale: 'small',
44841
44842     /**
44843      * @private
44844      * An array of allowed scales.
44845      */
44846     allowedScales: ['small', 'medium', 'large'],
44847
44848     /**
44849      * @cfg {Object} scope
44850      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
44851      * Defaults to this Button.
44852      */
44853
44854     /**
44855      * @cfg {String} iconAlign
44856      * The side of the Button box to render the icon. Four values are allowed:
44857      *
44858      * - 'top'
44859      * - 'right'
44860      * - 'bottom'
44861      * - 'left'
44862      */
44863     iconAlign: 'left',
44864
44865     /**
44866      * @cfg {String} arrowAlign
44867      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
44868      * values are allowed:
44869      *
44870      * - 'right'
44871      * - 'bottom'
44872      */
44873     arrowAlign: 'right',
44874
44875     /**
44876      * @cfg {String} arrowCls
44877      * The className used for the inner arrow element if the button has a menu.
44878      */
44879     arrowCls: 'arrow',
44880
44881     /**
44882      * @property {Ext.Template} template
44883      * A {@link Ext.Template Template} used to create the Button's DOM structure.
44884      *
44885      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
44886      * conjunction with an implementation of {@link #getTemplateArgs}.
44887      */
44888
44889     /**
44890      * @cfg {String} cls
44891      * A CSS class string to apply to the button's main element.
44892      */
44893
44894     /**
44895      * @property {Ext.menu.Menu} menu
44896      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
44897      * option.
44898      */
44899
44900     /**
44901      * @cfg {Boolean} autoWidth
44902      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
44903      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
44904      * from doing this automatic sizing.
44905      */
44906
44907     maskOnDisable: false,
44908
44909     // inherit docs
44910     initComponent: function() {
44911         var me = this;
44912         me.callParent(arguments);
44913
44914         me.addEvents(
44915             /**
44916              * @event click
44917              * Fires when this button is clicked
44918              * @param {Ext.button.Button} this
44919              * @param {Event} e The click event
44920              */
44921             'click',
44922
44923             /**
44924              * @event toggle
44925              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
44926              * @param {Ext.button.Button} this
44927              * @param {Boolean} pressed
44928              */
44929             'toggle',
44930
44931             /**
44932              * @event mouseover
44933              * Fires when the mouse hovers over the button
44934              * @param {Ext.button.Button} this
44935              * @param {Event} e The event object
44936              */
44937             'mouseover',
44938
44939             /**
44940              * @event mouseout
44941              * Fires when the mouse exits the button
44942              * @param {Ext.button.Button} this
44943              * @param {Event} e The event object
44944              */
44945             'mouseout',
44946
44947             /**
44948              * @event menushow
44949              * If this button has a menu, this event fires when it is shown
44950              * @param {Ext.button.Button} this
44951              * @param {Ext.menu.Menu} menu
44952              */
44953             'menushow',
44954
44955             /**
44956              * @event menuhide
44957              * If this button has a menu, this event fires when it is hidden
44958              * @param {Ext.button.Button} this
44959              * @param {Ext.menu.Menu} menu
44960              */
44961             'menuhide',
44962
44963             /**
44964              * @event menutriggerover
44965              * If this button has a menu, this event fires when the mouse enters the menu triggering element
44966              * @param {Ext.button.Button} this
44967              * @param {Ext.menu.Menu} menu
44968              * @param {Event} e
44969              */
44970             'menutriggerover',
44971
44972             /**
44973              * @event menutriggerout
44974              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
44975              * @param {Ext.button.Button} this
44976              * @param {Ext.menu.Menu} menu
44977              * @param {Event} e
44978              */
44979             'menutriggerout'
44980         );
44981
44982         if (me.menu) {
44983             // Flag that we'll have a splitCls
44984             me.split = true;
44985
44986             // retrieve menu by id or instantiate instance if needed
44987             me.menu = Ext.menu.Manager.get(me.menu);
44988             me.menu.ownerCt = me;
44989         }
44990
44991         // Accept url as a synonym for href
44992         if (me.url) {
44993             me.href = me.url;
44994         }
44995
44996         // preventDefault defaults to false for links
44997         if (me.href && !me.hasOwnProperty('preventDefault')) {
44998             me.preventDefault = false;
44999         }
45000
45001         if (Ext.isString(me.toggleGroup)) {
45002             me.enableToggle = true;
45003         }
45004
45005     },
45006
45007     // private
45008     initAria: function() {
45009         this.callParent();
45010         var actionEl = this.getActionEl();
45011         if (this.menu) {
45012             actionEl.dom.setAttribute('aria-haspopup', true);
45013         }
45014     },
45015
45016     // inherit docs
45017     getActionEl: function() {
45018         return this.btnEl;
45019     },
45020
45021     // inherit docs
45022     getFocusEl: function() {
45023         return this.btnEl;
45024     },
45025
45026     // private
45027     setButtonCls: function() {
45028         var me = this,
45029             cls = [],
45030             btnIconEl = me.btnIconEl,
45031             hide = 'x-hide-display';
45032
45033         if (me.useSetClass) {
45034             if (!Ext.isEmpty(me.oldCls)) {
45035                 me.removeClsWithUI(me.oldCls);
45036                 me.removeClsWithUI(me.pressedCls);
45037             }
45038
45039             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
45040             if (me.iconCls || me.icon) {
45041                 if (me.text) {
45042                     cls.push('icon-text-' + me.iconAlign);
45043                 } else {
45044                     cls.push('icon');
45045                 }
45046                 if (btnIconEl) {
45047                     btnIconEl.removeCls(hide);
45048                 }
45049             } else {
45050                 if (me.text) {
45051                     cls.push('noicon');
45052                 }
45053                 if (btnIconEl) {
45054                     btnIconEl.addCls(hide);
45055                 }
45056             }
45057
45058             me.oldCls = cls;
45059             me.addClsWithUI(cls);
45060             me.addClsWithUI(me.pressed ? me.pressedCls : null);
45061         }
45062     },
45063
45064     // private
45065     onRender: function(ct, position) {
45066         // classNames for the button
45067         var me = this,
45068             repeater, btn;
45069
45070         // Apply the renderData to the template args
45071         Ext.applyIf(me.renderData, me.getTemplateArgs());
45072
45073         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
45074
45075         if (me.scale) {
45076             me.ui = me.ui + '-' + me.scale;
45077         }
45078
45079         // Render internal structure
45080         me.callParent(arguments);
45081
45082         // If it is a split button + has a toolip for the arrow
45083         if (me.split && me.arrowTooltip) {
45084             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
45085         }
45086
45087         // Add listeners to the focus and blur events on the element
45088         me.mon(me.btnEl, {
45089             scope: me,
45090             focus: me.onFocus,
45091             blur : me.onBlur
45092         });
45093
45094         // Set btn as a local variable for easy access
45095         btn = me.el;
45096
45097         if (me.icon) {
45098             me.setIcon(me.icon);
45099         }
45100
45101         if (me.iconCls) {
45102             me.setIconCls(me.iconCls);
45103         }
45104
45105         if (me.tooltip) {
45106             me.setTooltip(me.tooltip, true);
45107         }
45108
45109         if (me.textAlign) {
45110             me.setTextAlign(me.textAlign);
45111         }
45112
45113         // Add the mouse events to the button
45114         if (me.handleMouseEvents) {
45115             me.mon(btn, {
45116                 scope: me,
45117                 mouseover: me.onMouseOver,
45118                 mouseout: me.onMouseOut,
45119                 mousedown: me.onMouseDown
45120             });
45121
45122             if (me.split) {
45123                 me.mon(btn, {
45124                     mousemove: me.onMouseMove,
45125                     scope: me
45126                 });
45127             }
45128         }
45129
45130         // Check if the button has a menu
45131         if (me.menu) {
45132             me.mon(me.menu, {
45133                 scope: me,
45134                 show: me.onMenuShow,
45135                 hide: me.onMenuHide
45136             });
45137
45138             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
45139                 key: Ext.EventObject.DOWN,
45140                 handler: me.onDownKey,
45141                 scope: me
45142             });
45143         }
45144
45145         // Check if it is a repeat button
45146         if (me.repeat) {
45147             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
45148             me.mon(repeater, 'click', me.onRepeatClick, me);
45149         } else {
45150             me.mon(btn, me.clickEvent, me.onClick, me);
45151         }
45152
45153         // Register the button in the toggle manager
45154         Ext.ButtonToggleManager.register(me);
45155     },
45156
45157     /**
45158      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
45159      * create this Button's DOM structure.
45160      *
45161      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
45162      * their own implementation of this method.
45163      *
45164      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
45165      * {@link #template} returns an Object containing the following properties:
45166      * @return {String} return.type The `<button>`'s {@link #type}
45167      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
45168      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
45169      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
45170      * button's scale and icon alignment.
45171      * @return {String} return.text The {@link #text} to display ion the Button.
45172      * @return {Number} return.tabIndex The tab index within the input flow.
45173      */
45174     getTemplateArgs: function() {
45175         var me = this,
45176             persistentPadding = me.getPersistentBtnPadding(),
45177             innerSpanStyle = '';
45178
45179         // Create negative margin offsets to counteract persistent button padding if needed
45180         if (Math.max.apply(Math, persistentPadding) > 0) {
45181             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
45182                 return -pad + 'px';
45183             }).join(' ');
45184         }
45185
45186         return {
45187             href     : me.getHref(),
45188             target   : me.target || '_blank',
45189             type     : me.type,
45190             splitCls : me.getSplitCls(),
45191             cls      : me.cls,
45192             iconCls  : me.iconCls || '',
45193             text     : me.text || '&#160;',
45194             tabIndex : me.tabIndex,
45195             innerSpanStyle: innerSpanStyle
45196         };
45197     },
45198
45199     /**
45200      * @private
45201      * If there is a configured href for this Button, returns the href with parameters appended.
45202      * @returns The href string with parameters appended.
45203      */
45204     getHref: function() {
45205         var me = this,
45206             params = Ext.apply({}, me.baseParams);
45207
45208         // write baseParams first, then write any params
45209         params = Ext.apply(params, me.params);
45210         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
45211     },
45212
45213     /**
45214      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
45215      *
45216      * **Only valid if the Button was originally configured with a {@link #href}**
45217      *
45218      * @param {Object} params Parameters to use in the href URL.
45219      */
45220     setParams: function(params) {
45221         this.params = params;
45222         this.btnEl.dom.href = this.getHref();
45223     },
45224
45225     getSplitCls: function() {
45226         var me = this;
45227         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
45228     },
45229
45230     // private
45231     afterRender: function() {
45232         var me = this;
45233         me.useSetClass = true;
45234         me.setButtonCls();
45235         me.doc = Ext.getDoc();
45236         this.callParent(arguments);
45237     },
45238
45239     /**
45240      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
45241      * value of the {@link #iconCls} config internally.
45242      * @param {String} cls The CSS class providing the icon image
45243      * @return {Ext.button.Button} this
45244      */
45245     setIconCls: function(cls) {
45246         var me = this,
45247             btnIconEl = me.btnIconEl,
45248             oldCls = me.iconCls;
45249             
45250         me.iconCls = cls;
45251         if (btnIconEl) {
45252             // Remove the previous iconCls from the button
45253             btnIconEl.removeCls(oldCls);
45254             btnIconEl.addCls(cls || '');
45255             me.setButtonCls();
45256         }
45257         return me;
45258     },
45259
45260     /**
45261      * Sets the tooltip for this Button.
45262      *
45263      * @param {String/Object} tooltip This may be:
45264      *
45265      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
45266      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
45267      *
45268      * @return {Ext.button.Button} this
45269      */
45270     setTooltip: function(tooltip, initial) {
45271         var me = this;
45272
45273         if (me.rendered) {
45274             if (!initial) {
45275                 me.clearTip();
45276             }
45277             if (Ext.isObject(tooltip)) {
45278                 Ext.tip.QuickTipManager.register(Ext.apply({
45279                     target: me.btnEl.id
45280                 },
45281                 tooltip));
45282                 me.tooltip = tooltip;
45283             } else {
45284                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
45285             }
45286         } else {
45287             me.tooltip = tooltip;
45288         }
45289         return me;
45290     },
45291
45292     /**
45293      * Sets the text alignment for this button.
45294      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
45295      */
45296     setTextAlign: function(align) {
45297         var me = this,
45298             btnEl = me.btnEl;
45299
45300         if (btnEl) {
45301             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
45302             btnEl.addCls(me.baseCls + '-' + align);
45303         }
45304         me.textAlign = align;
45305         return me;
45306     },
45307
45308     getTipAttr: function(){
45309         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
45310     },
45311
45312     // private
45313     getRefItems: function(deep){
45314         var menu = this.menu,
45315             items;
45316         
45317         if (menu) {
45318             items = menu.getRefItems(deep);
45319             items.unshift(menu);
45320         }
45321         return items || [];
45322     },
45323
45324     // private
45325     clearTip: function() {
45326         if (Ext.isObject(this.tooltip)) {
45327             Ext.tip.QuickTipManager.unregister(this.btnEl);
45328         }
45329     },
45330
45331     // private
45332     beforeDestroy: function() {
45333         var me = this;
45334         if (me.rendered) {
45335             me.clearTip();
45336         }
45337         if (me.menu && me.destroyMenu !== false) {
45338             Ext.destroy(me.menu);
45339         }
45340         Ext.destroy(me.btnInnerEl, me.repeater);
45341         me.callParent();
45342     },
45343
45344     // private
45345     onDestroy: function() {
45346         var me = this;
45347         if (me.rendered) {
45348             me.doc.un('mouseover', me.monitorMouseOver, me);
45349             me.doc.un('mouseup', me.onMouseUp, me);
45350             delete me.doc;
45351             Ext.ButtonToggleManager.unregister(me);
45352
45353             Ext.destroy(me.keyMap);
45354             delete me.keyMap;
45355         }
45356         me.callParent();
45357     },
45358
45359     /**
45360      * Assigns this Button's click handler
45361      * @param {Function} handler The function to call when the button is clicked
45362      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
45363      * Defaults to this Button.
45364      * @return {Ext.button.Button} this
45365      */
45366     setHandler: function(handler, scope) {
45367         this.handler = handler;
45368         this.scope = scope;
45369         return this;
45370     },
45371
45372     /**
45373      * Sets this Button's text
45374      * @param {String} text The button text
45375      * @return {Ext.button.Button} this
45376      */
45377     setText: function(text) {
45378         var me = this;
45379         me.text = text;
45380         if (me.el) {
45381             me.btnInnerEl.update(text || '&#160;');
45382             me.setButtonCls();
45383         }
45384         me.doComponentLayout();
45385         return me;
45386     },
45387
45388     /**
45389      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
45390      * config internally.
45391      * @param {String} icon The path to an image to display in the button
45392      * @return {Ext.button.Button} this
45393      */
45394     setIcon: function(icon) {
45395         var me = this,
45396             iconEl = me.btnIconEl;
45397             
45398         me.icon = icon;
45399         if (iconEl) {
45400             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
45401             me.setButtonCls();
45402         }
45403         return me;
45404     },
45405
45406     /**
45407      * Gets the text for this Button
45408      * @return {String} The button text
45409      */
45410     getText: function() {
45411         return this.text;
45412     },
45413
45414     /**
45415      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
45416      * @param {Boolean} [state] Force a particular state
45417      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
45418      * @return {Ext.button.Button} this
45419      */
45420     toggle: function(state, suppressEvent) {
45421         var me = this;
45422         state = state === undefined ? !me.pressed : !!state;
45423         if (state !== me.pressed) {
45424             if (me.rendered) {
45425                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
45426             }
45427             me.btnEl.dom.setAttribute('aria-pressed', state);
45428             me.pressed = state;
45429             if (!suppressEvent) {
45430                 me.fireEvent('toggle', me, state);
45431                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
45432             }
45433         }
45434         return me;
45435     },
45436     
45437     maybeShowMenu: function(){
45438         var me = this;
45439         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
45440             me.showMenu();
45441         }
45442     },
45443
45444     /**
45445      * Shows this button's menu (if it has one)
45446      */
45447     showMenu: function() {
45448         var me = this;
45449         if (me.rendered && me.menu) {
45450             if (me.tooltip && me.getTipAttr() != 'title') {
45451                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
45452             }
45453             if (me.menu.isVisible()) {
45454                 me.menu.hide();
45455             }
45456
45457             me.menu.showBy(me.el, me.menuAlign);
45458         }
45459         return me;
45460     },
45461
45462     /**
45463      * Hides this button's menu (if it has one)
45464      */
45465     hideMenu: function() {
45466         if (this.hasVisibleMenu()) {
45467             this.menu.hide();
45468         }
45469         return this;
45470     },
45471
45472     /**
45473      * Returns true if the button has a menu and it is visible
45474      * @return {Boolean}
45475      */
45476     hasVisibleMenu: function() {
45477         var menu = this.menu;
45478         return menu && menu.rendered && menu.isVisible();
45479     },
45480
45481     // private
45482     onRepeatClick: function(repeat, e) {
45483         this.onClick(e);
45484     },
45485
45486     // private
45487     onClick: function(e) {
45488         var me = this;
45489         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
45490             e.preventDefault();
45491         }
45492         if (e.button !== 0) {
45493             return;
45494         }
45495         if (!me.disabled) {
45496             me.doToggle();
45497             me.maybeShowMenu();
45498             me.fireHandler(e);
45499         }
45500     },
45501     
45502     fireHandler: function(e){
45503         var me = this,
45504             handler = me.handler;
45505             
45506         me.fireEvent('click', me, e);
45507         if (handler) {
45508             handler.call(me.scope || me, me, e);
45509         }
45510         me.onBlur();
45511     },
45512     
45513     doToggle: function(){
45514         var me = this;
45515         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
45516             me.toggle();
45517         }
45518     },
45519
45520     /**
45521      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
45522      * The targets are interrogated to see what is being entered from where.
45523      * @param e
45524      */
45525     onMouseOver: function(e) {
45526         var me = this;
45527         if (!me.disabled && !e.within(me.el, true, true)) {
45528             me.onMouseEnter(e);
45529         }
45530     },
45531
45532     /**
45533      * @private
45534      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
45535      * or the mouse leaves the encapsulating element.
45536      * The targets are interrogated to see what is being exited to where.
45537      * @param e
45538      */
45539     onMouseOut: function(e) {
45540         var me = this;
45541         if (!e.within(me.el, true, true)) {
45542             if (me.overMenuTrigger) {
45543                 me.onMenuTriggerOut(e);
45544             }
45545             me.onMouseLeave(e);
45546         }
45547     },
45548
45549     /**
45550      * @private
45551      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
45552      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
45553      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
45554      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
45555      * events when needed. In the future we should consider making the trigger a separate element that
45556      * is absolutely positioned and sized over the trigger area.
45557      */
45558     onMouseMove: function(e) {
45559         var me = this,
45560             el = me.el,
45561             over = me.overMenuTrigger,
45562             overlap, btnSize;
45563
45564         if (me.split) {
45565             if (me.arrowAlign === 'right') {
45566                 overlap = e.getX() - el.getX();
45567                 btnSize = el.getWidth();
45568             } else {
45569                 overlap = e.getY() - el.getY();
45570                 btnSize = el.getHeight();
45571             }
45572
45573             if (overlap > (btnSize - me.getTriggerSize())) {
45574                 if (!over) {
45575                     me.onMenuTriggerOver(e);
45576                 }
45577             } else {
45578                 if (over) {
45579                     me.onMenuTriggerOut(e);
45580                 }
45581             }
45582         }
45583     },
45584
45585     /**
45586      * @private
45587      * Measures the size of the trigger area for menu and split buttons. Will be a width for
45588      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
45589      */
45590     getTriggerSize: function() {
45591         var me = this,
45592             size = me.triggerSize,
45593             side, sideFirstLetter, undef;
45594
45595         if (size === undef) {
45596             side = me.arrowAlign;
45597             sideFirstLetter = side.charAt(0);
45598             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
45599         }
45600         return size;
45601     },
45602
45603     /**
45604      * @private
45605      * virtual mouseenter handler called when it is detected that the mouseout event
45606      * signified the mouse entering the encapsulating element.
45607      * @param e
45608      */
45609     onMouseEnter: function(e) {
45610         var me = this;
45611         me.addClsWithUI(me.overCls);
45612         me.fireEvent('mouseover', me, e);
45613     },
45614
45615     /**
45616      * @private
45617      * virtual mouseleave handler called when it is detected that the mouseover event
45618      * signified the mouse entering the encapsulating element.
45619      * @param e
45620      */
45621     onMouseLeave: function(e) {
45622         var me = this;
45623         me.removeClsWithUI(me.overCls);
45624         me.fireEvent('mouseout', me, e);
45625     },
45626
45627     /**
45628      * @private
45629      * virtual mouseenter handler called when it is detected that the mouseover event
45630      * signified the mouse entering the arrow area of the button - the <em>.
45631      * @param e
45632      */
45633     onMenuTriggerOver: function(e) {
45634         var me = this;
45635         me.overMenuTrigger = true;
45636         me.fireEvent('menutriggerover', me, me.menu, e);
45637     },
45638
45639     /**
45640      * @private
45641      * virtual mouseleave handler called when it is detected that the mouseout event
45642      * signified the mouse leaving the arrow area of the button - the <em>.
45643      * @param e
45644      */
45645     onMenuTriggerOut: function(e) {
45646         var me = this;
45647         delete me.overMenuTrigger;
45648         me.fireEvent('menutriggerout', me, me.menu, e);
45649     },
45650
45651     // inherit docs
45652     enable : function(silent) {
45653         var me = this;
45654
45655         me.callParent(arguments);
45656
45657         me.removeClsWithUI('disabled');
45658
45659         return me;
45660     },
45661
45662     // inherit docs
45663     disable : function(silent) {
45664         var me = this;
45665
45666         me.callParent(arguments);
45667
45668         me.addClsWithUI('disabled');
45669         me.removeClsWithUI(me.overCls);
45670
45671         return me;
45672     },
45673
45674     /**
45675      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
45676      * @param {String} scale The scale to change to.
45677      */
45678     setScale: function(scale) {
45679         var me = this,
45680             ui = me.ui.replace('-' + me.scale, '');
45681
45682         //check if it is an allowed scale
45683         if (!Ext.Array.contains(me.allowedScales, scale)) {
45684             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
45685         }
45686
45687         me.scale = scale;
45688         me.setUI(ui);
45689     },
45690
45691     // inherit docs
45692     setUI: function(ui) {
45693         var me = this;
45694
45695         //we need to append the scale to the UI, if not already done
45696         if (me.scale && !ui.match(me.scale)) {
45697             ui = ui + '-' + me.scale;
45698         }
45699
45700         me.callParent([ui]);
45701
45702         // Set all the state classNames, as they need to include the UI
45703         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
45704     },
45705
45706     // private
45707     onFocus: function(e) {
45708         var me = this;
45709         if (!me.disabled) {
45710             me.addClsWithUI(me.focusCls);
45711         }
45712     },
45713
45714     // private
45715     onBlur: function(e) {
45716         var me = this;
45717         me.removeClsWithUI(me.focusCls);
45718     },
45719
45720     // private
45721     onMouseDown: function(e) {
45722         var me = this;
45723         if (!me.disabled && e.button === 0) {
45724             me.addClsWithUI(me.pressedCls);
45725             me.doc.on('mouseup', me.onMouseUp, me);
45726         }
45727     },
45728     // private
45729     onMouseUp: function(e) {
45730         var me = this;
45731         if (e.button === 0) {
45732             if (!me.pressed) {
45733                 me.removeClsWithUI(me.pressedCls);
45734             }
45735             me.doc.un('mouseup', me.onMouseUp, me);
45736         }
45737     },
45738     // private
45739     onMenuShow: function(e) {
45740         var me = this;
45741         me.ignoreNextClick = 0;
45742         me.addClsWithUI(me.menuActiveCls);
45743         me.fireEvent('menushow', me, me.menu);
45744     },
45745
45746     // private
45747     onMenuHide: function(e) {
45748         var me = this;
45749         me.removeClsWithUI(me.menuActiveCls);
45750         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
45751         me.fireEvent('menuhide', me, me.menu);
45752     },
45753
45754     // private
45755     restoreClick: function() {
45756         this.ignoreNextClick = 0;
45757     },
45758
45759     // private
45760     onDownKey: function() {
45761         var me = this;
45762
45763         if (!me.disabled) {
45764             if (me.menu) {
45765                 me.showMenu();
45766             }
45767         }
45768     },
45769
45770     /**
45771      * @private
45772      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
45773      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
45774      * @return {Number[]} [top, right, bottom, left]
45775      */
45776     getPersistentBtnPadding: function() {
45777         var cls = Ext.button.Button,
45778             padding = cls.persistentPadding,
45779             btn, leftTop, btnEl, btnInnerEl;
45780
45781         if (!padding) {
45782             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
45783
45784             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
45785                 // Create auto-size button offscreen and measure its insides
45786                 btn = Ext.create('Ext.button.Button', {
45787                     renderTo: Ext.getBody(),
45788                     text: 'test',
45789                     style: 'position:absolute;top:-999px;'
45790                 });
45791                 btnEl = btn.btnEl;
45792                 btnInnerEl = btn.btnInnerEl;
45793                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
45794
45795                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
45796                 padding[0] = leftTop[1];
45797                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
45798                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
45799                 padding[3] = leftTop[0];
45800
45801                 btn.destroy();
45802             }
45803         }
45804
45805         return padding;
45806     }
45807
45808 }, function() {
45809     var groups = {};
45810
45811     function toggleGroup(btn, state) {
45812         var g, i, l;
45813         if (state) {
45814             g = groups[btn.toggleGroup];
45815             for (i = 0, l = g.length; i < l; i++) {
45816                 if (g[i] !== btn) {
45817                     g[i].toggle(false);
45818                 }
45819             }
45820         }
45821     }
45822
45823     /**
45824      * Private utility class used by Button
45825      * @hide
45826      */
45827     Ext.ButtonToggleManager = {
45828         register: function(btn) {
45829             if (!btn.toggleGroup) {
45830                 return;
45831             }
45832             var group = groups[btn.toggleGroup];
45833             if (!group) {
45834                 group = groups[btn.toggleGroup] = [];
45835             }
45836             group.push(btn);
45837             btn.on('toggle', toggleGroup);
45838         },
45839
45840         unregister: function(btn) {
45841             if (!btn.toggleGroup) {
45842                 return;
45843             }
45844             var group = groups[btn.toggleGroup];
45845             if (group) {
45846                 Ext.Array.remove(group, btn);
45847                 btn.un('toggle', toggleGroup);
45848             }
45849         },
45850
45851         /**
45852          * Gets the pressed button in the passed group or null
45853          * @param {String} group
45854          * @return {Ext.button.Button}
45855          */
45856         getPressed: function(group) {
45857             var g = groups[group],
45858                 i = 0,
45859                 len;
45860             if (g) {
45861                 for (len = g.length; i < len; i++) {
45862                     if (g[i].pressed === true) {
45863                         return g[i];
45864                     }
45865                 }
45866             }
45867             return null;
45868         }
45869     };
45870 });
45871
45872 /**
45873  * @class Ext.layout.container.boxOverflow.Menu
45874  * @extends Ext.layout.container.boxOverflow.None
45875  * @private
45876  */
45877 Ext.define('Ext.layout.container.boxOverflow.Menu', {
45878
45879     /* Begin Definitions */
45880
45881     extend: 'Ext.layout.container.boxOverflow.None',
45882     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
45883     alternateClassName: 'Ext.layout.boxOverflow.Menu',
45884     
45885     /* End Definitions */
45886
45887     /**
45888      * @cfg {String} afterCtCls
45889      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
45890      * which must always be present at the rightmost edge of the Container
45891      */
45892
45893     /**
45894      * @property noItemsMenuText
45895      * @type String
45896      * HTML fragment to render into the toolbar overflow menu if there are no items to display
45897      */
45898     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
45899
45900     constructor: function(layout) {
45901         var me = this;
45902
45903         me.callParent(arguments);
45904
45905         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
45906         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
45907
45908         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
45909         /**
45910          * @property menuItems
45911          * @type Array
45912          * Array of all items that are currently hidden and should go into the dropdown menu
45913          */
45914         me.menuItems = [];
45915     },
45916     
45917     onRemove: function(comp){
45918         Ext.Array.remove(this.menuItems, comp);
45919     },
45920
45921     handleOverflow: function(calculations, targetSize) {
45922         var me = this,
45923             layout = me.layout,
45924             methodName = 'get' + layout.parallelPrefixCap,
45925             newSize = {},
45926             posArgs = [null, null];
45927
45928         me.callParent(arguments);
45929         this.createMenu(calculations, targetSize);
45930         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
45931         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
45932
45933         // Center the menuTrigger button.
45934         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
45935         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
45936         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
45937
45938         return { targetSize: newSize };
45939     },
45940
45941     /**
45942      * @private
45943      * Called by the layout, when it determines that there is no overflow.
45944      * Also called as an interceptor to the layout's onLayout method to reshow
45945      * previously hidden overflowing items.
45946      */
45947     clearOverflow: function(calculations, targetSize) {
45948         var me = this,
45949             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
45950             items = me.menuItems,
45951             i = 0,
45952             length = items.length,
45953             item;
45954
45955         me.hideTrigger();
45956         for (; i < length; i++) {
45957             items[i].show();
45958         }
45959         items.length = 0;
45960
45961         return targetSize ? {
45962             targetSize: {
45963                 height: targetSize.height,
45964                 width : newWidth
45965             }
45966         } : null;
45967     },
45968
45969     /**
45970      * @private
45971      */
45972     showTrigger: function() {
45973         this.menuTrigger.show();
45974     },
45975
45976     /**
45977      * @private
45978      */
45979     hideTrigger: function() {
45980         if (this.menuTrigger !== undefined) {
45981             this.menuTrigger.hide();
45982         }
45983     },
45984
45985     /**
45986      * @private
45987      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
45988      */
45989     beforeMenuShow: function(menu) {
45990         var me = this,
45991             items = me.menuItems,
45992             i = 0,
45993             len   = items.length,
45994             item,
45995             prev;
45996
45997         var needsSep = function(group, prev){
45998             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
45999         };
46000
46001         me.clearMenu();
46002         menu.removeAll();
46003
46004         for (; i < len; i++) {
46005             item = items[i];
46006
46007             // Do not show a separator as a first item
46008             if (!i && (item instanceof Ext.toolbar.Separator)) {
46009                 continue;
46010             }
46011             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
46012                 menu.add('-');
46013             }
46014
46015             me.addComponentToMenu(menu, item);
46016             prev = item;
46017         }
46018
46019         // put something so the menu isn't empty if no compatible items found
46020         if (menu.items.length < 1) {
46021             menu.add(me.noItemsMenuText);
46022         }
46023     },
46024     
46025     /**
46026      * @private
46027      * Returns a menu config for a given component. This config is used to create a menu item
46028      * to be added to the expander menu
46029      * @param {Ext.Component} component The component to create the config for
46030      * @param {Boolean} hideOnClick Passed through to the menu item
46031      */
46032     createMenuConfig : function(component, hideOnClick) {
46033         var config = Ext.apply({}, component.initialConfig),
46034             group  = component.toggleGroup;
46035
46036         Ext.copyTo(config, component, [
46037             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
46038         ]);
46039
46040         Ext.apply(config, {
46041             text       : component.overflowText || component.text,
46042             hideOnClick: hideOnClick,
46043             destroyMenu: false
46044         });
46045
46046         if (group || component.enableToggle) {
46047             Ext.apply(config, {
46048                 group  : group,
46049                 checked: component.pressed,
46050                 listeners: {
46051                     checkchange: function(item, checked){
46052                         component.toggle(checked);
46053                     }
46054                 }
46055             });
46056         }
46057
46058         delete config.ownerCt;
46059         delete config.xtype;
46060         delete config.id;
46061         return config;
46062     },
46063
46064     /**
46065      * @private
46066      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
46067      * @param {Ext.menu.Menu} menu The menu to add to
46068      * @param {Ext.Component} component The component to add
46069      */
46070     addComponentToMenu : function(menu, component) {
46071         var me = this;
46072         if (component instanceof Ext.toolbar.Separator) {
46073             menu.add('-');
46074         } else if (component.isComponent) {
46075             if (component.isXType('splitbutton')) {
46076                 menu.add(me.createMenuConfig(component, true));
46077
46078             } else if (component.isXType('button')) {
46079                 menu.add(me.createMenuConfig(component, !component.menu));
46080
46081             } else if (component.isXType('buttongroup')) {
46082                 component.items.each(function(item){
46083                      me.addComponentToMenu(menu, item);
46084                 });
46085             } else {
46086                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
46087             }
46088         }
46089     },
46090
46091     /**
46092      * @private
46093      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
46094      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
46095      */
46096     clearMenu : function() {
46097         var menu = this.moreMenu;
46098         if (menu && menu.items) {
46099             menu.items.each(function(item) {
46100                 if (item.menu) {
46101                     delete item.menu;
46102                 }
46103             });
46104         }
46105     },
46106
46107     /**
46108      * @private
46109      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
46110      * in the layout are too wide to fit in the space available
46111      */
46112     createMenu: function(calculations, targetSize) {
46113         var me = this,
46114             layout = me.layout,
46115             startProp = layout.parallelBefore,
46116             sizeProp = layout.parallelPrefix,
46117             available = targetSize[sizeProp],
46118             boxes = calculations.boxes,
46119             i = 0,
46120             len = boxes.length,
46121             box;
46122
46123         if (!me.menuTrigger) {
46124             me.createInnerElements();
46125
46126             /**
46127              * @private
46128              * @property menu
46129              * @type Ext.menu.Menu
46130              * The expand menu - holds items for every item that cannot be shown
46131              * because the container is currently not large enough.
46132              */
46133             me.menu = Ext.create('Ext.menu.Menu', {
46134                 listeners: {
46135                     scope: me,
46136                     beforeshow: me.beforeMenuShow
46137                 }
46138             });
46139
46140             /**
46141              * @private
46142              * @property menuTrigger
46143              * @type Ext.button.Button
46144              * The expand button which triggers the overflow menu to be shown
46145              */
46146             me.menuTrigger = Ext.create('Ext.button.Button', {
46147                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
46148                 iconCls : me.layout.owner.menuTriggerCls,
46149                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
46150                 menu    : me.menu,
46151                 getSplitCls: function() { return '';},
46152                 renderTo: me.afterCt
46153             });
46154         }
46155         me.showTrigger();
46156         available -= me.afterCt.getWidth();
46157
46158         // Hide all items which are off the end, and store them to allow them to be restored
46159         // before each layout operation.
46160         me.menuItems.length = 0;
46161         for (; i < len; i++) {
46162             box = boxes[i];
46163             if (box[startProp] + box[sizeProp] > available) {
46164                 me.menuItems.push(box.component);
46165                 box.component.hide();
46166             }
46167         }
46168     },
46169
46170     /**
46171      * @private
46172      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
46173      * @param {Ext.container.Container} container The Container attached to this Layout instance
46174      * @param {Ext.Element} target The target Element
46175      */
46176     createInnerElements: function() {
46177         var me = this,
46178             target = me.layout.getRenderTarget();
46179
46180         if (!this.afterCt) {
46181             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
46182             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
46183         }
46184     },
46185
46186     /**
46187      * @private
46188      */
46189     destroy: function() {
46190         Ext.destroy(this.menu, this.menuTrigger);
46191     }
46192 });
46193 /**
46194  * This class represents a rectangular region in X,Y space, and performs geometric
46195  * transformations or tests upon the region.
46196  *
46197  * This class may be used to compare the document regions occupied by elements.
46198  */
46199 Ext.define('Ext.util.Region', {
46200
46201     /* Begin Definitions */
46202
46203     requires: ['Ext.util.Offset'],
46204
46205     statics: {
46206         /**
46207          * @static
46208          * Retrieves an Ext.util.Region for a particular element.
46209          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
46210          * @returns {Ext.util.Region} region
46211          */
46212         getRegion: function(el) {
46213             return Ext.fly(el).getPageBox(true);
46214         },
46215
46216         /**
46217          * @static
46218          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
46219          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
46220          * @return {Ext.util.Region} region The Region constructed based on the passed object
46221          */
46222         from: function(o) {
46223             return new this(o.top, o.right, o.bottom, o.left);
46224         }
46225     },
46226
46227     /* End Definitions */
46228
46229     /**
46230      * Creates a region from the bounding sides.
46231      * @param {Number} top Top The topmost pixel of the Region.
46232      * @param {Number} right Right The rightmost pixel of the Region.
46233      * @param {Number} bottom Bottom The bottom pixel of the Region.
46234      * @param {Number} left Left The leftmost pixel of the Region.
46235      */
46236     constructor : function(t, r, b, l) {
46237         var me = this;
46238         me.y = me.top = me[1] = t;
46239         me.right = r;
46240         me.bottom = b;
46241         me.x = me.left = me[0] = l;
46242     },
46243
46244     /**
46245      * Checks if this region completely contains the region that is passed in.
46246      * @param {Ext.util.Region} region
46247      * @return {Boolean}
46248      */
46249     contains : function(region) {
46250         var me = this;
46251         return (region.x >= me.x &&
46252                 region.right <= me.right &&
46253                 region.y >= me.y &&
46254                 region.bottom <= me.bottom);
46255
46256     },
46257
46258     /**
46259      * Checks if this region intersects the region passed in.
46260      * @param {Ext.util.Region} region
46261      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
46262      */
46263     intersect : function(region) {
46264         var me = this,
46265             t = Math.max(me.y, region.y),
46266             r = Math.min(me.right, region.right),
46267             b = Math.min(me.bottom, region.bottom),
46268             l = Math.max(me.x, region.x);
46269
46270         if (b > t && r > l) {
46271             return new this.self(t, r, b, l);
46272         }
46273         else {
46274             return false;
46275         }
46276     },
46277
46278     /**
46279      * Returns the smallest region that contains the current AND targetRegion.
46280      * @param {Ext.util.Region} region
46281      * @return {Ext.util.Region} a new region
46282      */
46283     union : function(region) {
46284         var me = this,
46285             t = Math.min(me.y, region.y),
46286             r = Math.max(me.right, region.right),
46287             b = Math.max(me.bottom, region.bottom),
46288             l = Math.min(me.x, region.x);
46289
46290         return new this.self(t, r, b, l);
46291     },
46292
46293     /**
46294      * Modifies the current region to be constrained to the targetRegion.
46295      * @param {Ext.util.Region} targetRegion
46296      * @return {Ext.util.Region} this
46297      */
46298     constrainTo : function(r) {
46299         var me = this,
46300             constrain = Ext.Number.constrain;
46301         me.top = me.y = constrain(me.top, r.y, r.bottom);
46302         me.bottom = constrain(me.bottom, r.y, r.bottom);
46303         me.left = me.x = constrain(me.left, r.x, r.right);
46304         me.right = constrain(me.right, r.x, r.right);
46305         return me;
46306     },
46307
46308     /**
46309      * Modifies the current region to be adjusted by offsets.
46310      * @param {Number} top top offset
46311      * @param {Number} right right offset
46312      * @param {Number} bottom bottom offset
46313      * @param {Number} left left offset
46314      * @return {Ext.util.Region} this
46315      */
46316     adjust : function(t, r, b, l) {
46317         var me = this;
46318         me.top = me.y += t;
46319         me.left = me.x += l;
46320         me.right += r;
46321         me.bottom += b;
46322         return me;
46323     },
46324
46325     /**
46326      * Get the offset amount of a point outside the region
46327      * @param {String} [axis]
46328      * @param {Ext.util.Point} [p] the point
46329      * @return {Ext.util.Offset}
46330      */
46331     getOutOfBoundOffset: function(axis, p) {
46332         if (!Ext.isObject(axis)) {
46333             if (axis == 'x') {
46334                 return this.getOutOfBoundOffsetX(p);
46335             } else {
46336                 return this.getOutOfBoundOffsetY(p);
46337             }
46338         } else {
46339             p = axis;
46340             var d = Ext.create('Ext.util.Offset');
46341             d.x = this.getOutOfBoundOffsetX(p.x);
46342             d.y = this.getOutOfBoundOffsetY(p.y);
46343             return d;
46344         }
46345
46346     },
46347
46348     /**
46349      * Get the offset amount on the x-axis
46350      * @param {Number} p the offset
46351      * @return {Number}
46352      */
46353     getOutOfBoundOffsetX: function(p) {
46354         if (p <= this.x) {
46355             return this.x - p;
46356         } else if (p >= this.right) {
46357             return this.right - p;
46358         }
46359
46360         return 0;
46361     },
46362
46363     /**
46364      * Get the offset amount on the y-axis
46365      * @param {Number} p the offset
46366      * @return {Number}
46367      */
46368     getOutOfBoundOffsetY: function(p) {
46369         if (p <= this.y) {
46370             return this.y - p;
46371         } else if (p >= this.bottom) {
46372             return this.bottom - p;
46373         }
46374
46375         return 0;
46376     },
46377
46378     /**
46379      * Check whether the point / offset is out of bound
46380      * @param {String} [axis]
46381      * @param {Ext.util.Point/Number} [p] the point / offset
46382      * @return {Boolean}
46383      */
46384     isOutOfBound: function(axis, p) {
46385         if (!Ext.isObject(axis)) {
46386             if (axis == 'x') {
46387                 return this.isOutOfBoundX(p);
46388             } else {
46389                 return this.isOutOfBoundY(p);
46390             }
46391         } else {
46392             p = axis;
46393             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
46394         }
46395     },
46396
46397     /**
46398      * Check whether the offset is out of bound in the x-axis
46399      * @param {Number} p the offset
46400      * @return {Boolean}
46401      */
46402     isOutOfBoundX: function(p) {
46403         return (p < this.x || p > this.right);
46404     },
46405
46406     /**
46407      * Check whether the offset is out of bound in the y-axis
46408      * @param {Number} p the offset
46409      * @return {Boolean}
46410      */
46411     isOutOfBoundY: function(p) {
46412         return (p < this.y || p > this.bottom);
46413     },
46414
46415     /**
46416      * Restrict a point within the region by a certain factor.
46417      * @param {String} [axis]
46418      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
46419      * @param {Number} [factor]
46420      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
46421      * @private
46422      */
46423     restrict: function(axis, p, factor) {
46424         if (Ext.isObject(axis)) {
46425             var newP;
46426
46427             factor = p;
46428             p = axis;
46429
46430             if (p.copy) {
46431                 newP = p.copy();
46432             }
46433             else {
46434                 newP = {
46435                     x: p.x,
46436                     y: p.y
46437                 };
46438             }
46439
46440             newP.x = this.restrictX(p.x, factor);
46441             newP.y = this.restrictY(p.y, factor);
46442             return newP;
46443         } else {
46444             if (axis == 'x') {
46445                 return this.restrictX(p, factor);
46446             } else {
46447                 return this.restrictY(p, factor);
46448             }
46449         }
46450     },
46451
46452     /**
46453      * Restrict an offset within the region by a certain factor, on the x-axis
46454      * @param {Number} p
46455      * @param {Number} [factor=1] The factor.
46456      * @return {Number}
46457      * @private
46458      */
46459     restrictX : function(p, factor) {
46460         if (!factor) {
46461             factor = 1;
46462         }
46463
46464         if (p <= this.x) {
46465             p -= (p - this.x) * factor;
46466         }
46467         else if (p >= this.right) {
46468             p -= (p - this.right) * factor;
46469         }
46470         return p;
46471     },
46472
46473     /**
46474      * Restrict an offset within the region by a certain factor, on the y-axis
46475      * @param {Number} p
46476      * @param {Number} [factor] The factor, defaults to 1
46477      * @return {Number}
46478      * @private
46479      */
46480     restrictY : function(p, factor) {
46481         if (!factor) {
46482             factor = 1;
46483         }
46484
46485         if (p <= this.y) {
46486             p -= (p - this.y) * factor;
46487         }
46488         else if (p >= this.bottom) {
46489             p -= (p - this.bottom) * factor;
46490         }
46491         return p;
46492     },
46493
46494     /**
46495      * Get the width / height of this region
46496      * @return {Object} an object with width and height properties
46497      * @private
46498      */
46499     getSize: function() {
46500         return {
46501             width: this.right - this.x,
46502             height: this.bottom - this.y
46503         };
46504     },
46505
46506     /**
46507      * Create a copy of this Region.
46508      * @return {Ext.util.Region}
46509      */
46510     copy: function() {
46511         return new this.self(this.y, this.right, this.bottom, this.x);
46512     },
46513
46514     /**
46515      * Copy the values of another Region to this Region
46516      * @param {Ext.util.Region} p The region to copy from.
46517      * @return {Ext.util.Region} This Region
46518      */
46519     copyFrom: function(p) {
46520         var me = this;
46521         me.top = me.y = me[1] = p.y;
46522         me.right = p.right;
46523         me.bottom = p.bottom;
46524         me.left = me.x = me[0] = p.x;
46525
46526         return this;
46527     },
46528
46529     /*
46530      * Dump this to an eye-friendly string, great for debugging
46531      * @return {String}
46532      */
46533     toString: function() {
46534         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
46535     },
46536
46537     /**
46538      * Translate this region by the given offset amount
46539      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
46540      * Or the x value is using the two argument form.
46541      * @param {Number} y The y value unless using an Offset object.
46542      * @return {Ext.util.Region} this This Region
46543      */
46544     translateBy: function(x, y) {
46545         if (arguments.length == 1) {
46546             y = x.y;
46547             x = x.x;
46548         }
46549         var me = this;
46550         me.top = me.y += y;
46551         me.right += x;
46552         me.bottom += y;
46553         me.left = me.x += x;
46554
46555         return me;
46556     },
46557
46558     /**
46559      * Round all the properties of this region
46560      * @return {Ext.util.Region} this This Region
46561      */
46562     round: function() {
46563         var me = this;
46564         me.top = me.y = Math.round(me.y);
46565         me.right = Math.round(me.right);
46566         me.bottom = Math.round(me.bottom);
46567         me.left = me.x = Math.round(me.x);
46568
46569         return me;
46570     },
46571
46572     /**
46573      * Check whether this region is equivalent to the given region
46574      * @param {Ext.util.Region} region The region to compare with
46575      * @return {Boolean}
46576      */
46577     equals: function(region) {
46578         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
46579     }
46580 });
46581
46582 /*
46583  * This is a derivative of the similarly named class in the YUI Library.
46584  * The original license:
46585  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
46586  * Code licensed under the BSD License:
46587  * http://developer.yahoo.net/yui/license.txt
46588  */
46589
46590
46591 /**
46592  * @class Ext.dd.DragDropManager
46593  * DragDropManager is a singleton that tracks the element interaction for
46594  * all DragDrop items in the window.  Generally, you will not call
46595  * this class directly, but it does have helper methods that could
46596  * be useful in your DragDrop implementations.
46597  * @singleton
46598  */
46599 Ext.define('Ext.dd.DragDropManager', {
46600     singleton: true,
46601
46602     requires: ['Ext.util.Region'],
46603
46604     uses: ['Ext.tip.QuickTipManager'],
46605
46606     // shorter ClassName, to save bytes and use internally
46607     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
46608
46609     /**
46610      * Two dimensional Array of registered DragDrop objects.  The first
46611      * dimension is the DragDrop item group, the second the DragDrop
46612      * object.
46613      * @property ids
46614      * @type String[]
46615      * @private
46616      */
46617     ids: {},
46618
46619     /**
46620      * Array of element ids defined as drag handles.  Used to determine
46621      * if the element that generated the mousedown event is actually the
46622      * handle and not the html element itself.
46623      * @property handleIds
46624      * @type String[]
46625      * @private
46626      */
46627     handleIds: {},
46628
46629     /**
46630      * the DragDrop object that is currently being dragged
46631      * @property {Ext.dd.DragDrop} dragCurrent
46632      * @private
46633      **/
46634     dragCurrent: null,
46635
46636     /**
46637      * the DragDrop object(s) that are being hovered over
46638      * @property {Ext.dd.DragDrop[]} dragOvers
46639      * @private
46640      */
46641     dragOvers: {},
46642
46643     /**
46644      * the X distance between the cursor and the object being dragged
46645      * @property deltaX
46646      * @type Number
46647      * @private
46648      */
46649     deltaX: 0,
46650
46651     /**
46652      * the Y distance between the cursor and the object being dragged
46653      * @property deltaY
46654      * @type Number
46655      * @private
46656      */
46657     deltaY: 0,
46658
46659     /**
46660      * Flag to determine if we should prevent the default behavior of the
46661      * events we define. By default this is true, but this can be set to
46662      * false if you need the default behavior (not recommended)
46663      * @property preventDefault
46664      * @type Boolean
46665      */
46666     preventDefault: true,
46667
46668     /**
46669      * Flag to determine if we should stop the propagation of the events
46670      * we generate. This is true by default but you may want to set it to
46671      * false if the html element contains other features that require the
46672      * mouse click.
46673      * @property stopPropagation
46674      * @type Boolean
46675      */
46676     stopPropagation: true,
46677
46678     /**
46679      * Internal flag that is set to true when drag and drop has been
46680      * intialized
46681      * @property initialized
46682      * @private
46683      */
46684     initialized: false,
46685
46686     /**
46687      * All drag and drop can be disabled.
46688      * @property locked
46689      * @private
46690      */
46691     locked: false,
46692
46693     /**
46694      * Called the first time an element is registered.
46695      * @method init
46696      * @private
46697      */
46698     init: function() {
46699         this.initialized = true;
46700     },
46701
46702     /**
46703      * In point mode, drag and drop interaction is defined by the
46704      * location of the cursor during the drag/drop
46705      * @property POINT
46706      * @type Number
46707      */
46708     POINT: 0,
46709
46710     /**
46711      * In intersect mode, drag and drop interaction is defined by the
46712      * overlap of two or more drag and drop objects.
46713      * @property INTERSECT
46714      * @type Number
46715      */
46716     INTERSECT: 1,
46717
46718     /**
46719      * The current drag and drop mode.  Default: POINT
46720      * @property mode
46721      * @type Number
46722      */
46723     mode: 0,
46724
46725     /**
46726      * Runs method on all drag and drop objects
46727      * @method _execOnAll
46728      * @private
46729      */
46730     _execOnAll: function(sMethod, args) {
46731         for (var i in this.ids) {
46732             for (var j in this.ids[i]) {
46733                 var oDD = this.ids[i][j];
46734                 if (! this.isTypeOfDD(oDD)) {
46735                     continue;
46736                 }
46737                 oDD[sMethod].apply(oDD, args);
46738             }
46739         }
46740     },
46741
46742     /**
46743      * Drag and drop initialization.  Sets up the global event handlers
46744      * @method _onLoad
46745      * @private
46746      */
46747     _onLoad: function() {
46748
46749         this.init();
46750
46751         var Event = Ext.EventManager;
46752         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
46753         Event.on(document, "mousemove", this.handleMouseMove, this, true);
46754         Event.on(window,   "unload",    this._onUnload, this, true);
46755         Event.on(window,   "resize",    this._onResize, this, true);
46756         // Event.on(window,   "mouseout",    this._test);
46757
46758     },
46759
46760     /**
46761      * Reset constraints on all drag and drop objs
46762      * @method _onResize
46763      * @private
46764      */
46765     _onResize: function(e) {
46766         this._execOnAll("resetConstraints", []);
46767     },
46768
46769     /**
46770      * Lock all drag and drop functionality
46771      * @method lock
46772      */
46773     lock: function() { this.locked = true; },
46774
46775     /**
46776      * Unlock all drag and drop functionality
46777      * @method unlock
46778      */
46779     unlock: function() { this.locked = false; },
46780
46781     /**
46782      * Is drag and drop locked?
46783      * @method isLocked
46784      * @return {Boolean} True if drag and drop is locked, false otherwise.
46785      */
46786     isLocked: function() { return this.locked; },
46787
46788     /**
46789      * Location cache that is set for all drag drop objects when a drag is
46790      * initiated, cleared when the drag is finished.
46791      * @property locationCache
46792      * @private
46793      */
46794     locationCache: {},
46795
46796     /**
46797      * Set useCache to false if you want to force object the lookup of each
46798      * drag and drop linked element constantly during a drag.
46799      * @property useCache
46800      * @type Boolean
46801      */
46802     useCache: true,
46803
46804     /**
46805      * The number of pixels that the mouse needs to move after the
46806      * mousedown before the drag is initiated.  Default=3;
46807      * @property clickPixelThresh
46808      * @type Number
46809      */
46810     clickPixelThresh: 3,
46811
46812     /**
46813      * The number of milliseconds after the mousedown event to initiate the
46814      * drag if we don't get a mouseup event. Default=350
46815      * @property clickTimeThresh
46816      * @type Number
46817      */
46818     clickTimeThresh: 350,
46819
46820     /**
46821      * Flag that indicates that either the drag pixel threshold or the
46822      * mousdown time threshold has been met
46823      * @property dragThreshMet
46824      * @type Boolean
46825      * @private
46826      */
46827     dragThreshMet: false,
46828
46829     /**
46830      * Timeout used for the click time threshold
46831      * @property clickTimeout
46832      * @type Object
46833      * @private
46834      */
46835     clickTimeout: null,
46836
46837     /**
46838      * The X position of the mousedown event stored for later use when a
46839      * drag threshold is met.
46840      * @property startX
46841      * @type Number
46842      * @private
46843      */
46844     startX: 0,
46845
46846     /**
46847      * The Y position of the mousedown event stored for later use when a
46848      * drag threshold is met.
46849      * @property startY
46850      * @type Number
46851      * @private
46852      */
46853     startY: 0,
46854
46855     /**
46856      * Each DragDrop instance must be registered with the DragDropManager.
46857      * This is executed in DragDrop.init()
46858      * @method regDragDrop
46859      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
46860      * @param {String} sGroup the name of the group this element belongs to
46861      */
46862     regDragDrop: function(oDD, sGroup) {
46863         if (!this.initialized) { this.init(); }
46864
46865         if (!this.ids[sGroup]) {
46866             this.ids[sGroup] = {};
46867         }
46868         this.ids[sGroup][oDD.id] = oDD;
46869     },
46870
46871     /**
46872      * Removes the supplied dd instance from the supplied group. Executed
46873      * by DragDrop.removeFromGroup, so don't call this function directly.
46874      * @method removeDDFromGroup
46875      * @private
46876      */
46877     removeDDFromGroup: function(oDD, sGroup) {
46878         if (!this.ids[sGroup]) {
46879             this.ids[sGroup] = {};
46880         }
46881
46882         var obj = this.ids[sGroup];
46883         if (obj && obj[oDD.id]) {
46884             delete obj[oDD.id];
46885         }
46886     },
46887
46888     /**
46889      * Unregisters a drag and drop item.  This is executed in
46890      * DragDrop.unreg, use that method instead of calling this directly.
46891      * @method _remove
46892      * @private
46893      */
46894     _remove: function(oDD) {
46895         for (var g in oDD.groups) {
46896             if (g && this.ids[g] && this.ids[g][oDD.id]) {
46897                 delete this.ids[g][oDD.id];
46898             }
46899         }
46900         delete this.handleIds[oDD.id];
46901     },
46902
46903     /**
46904      * Each DragDrop handle element must be registered.  This is done
46905      * automatically when executing DragDrop.setHandleElId()
46906      * @method regHandle
46907      * @param {String} sDDId the DragDrop id this element is a handle for
46908      * @param {String} sHandleId the id of the element that is the drag
46909      * handle
46910      */
46911     regHandle: function(sDDId, sHandleId) {
46912         if (!this.handleIds[sDDId]) {
46913             this.handleIds[sDDId] = {};
46914         }
46915         this.handleIds[sDDId][sHandleId] = sHandleId;
46916     },
46917
46918     /**
46919      * Utility function to determine if a given element has been
46920      * registered as a drag drop item.
46921      * @method isDragDrop
46922      * @param {String} id the element id to check
46923      * @return {Boolean} true if this element is a DragDrop item,
46924      * false otherwise
46925      */
46926     isDragDrop: function(id) {
46927         return ( this.getDDById(id) ) ? true : false;
46928     },
46929
46930     /**
46931      * Returns the drag and drop instances that are in all groups the
46932      * passed in instance belongs to.
46933      * @method getRelated
46934      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
46935      * @param {Boolean} bTargetsOnly if true, only return targetable objs
46936      * @return {Ext.dd.DragDrop[]} the related instances
46937      */
46938     getRelated: function(p_oDD, bTargetsOnly) {
46939         var oDDs = [];
46940         for (var i in p_oDD.groups) {
46941             for (var j in this.ids[i]) {
46942                 var dd = this.ids[i][j];
46943                 if (! this.isTypeOfDD(dd)) {
46944                     continue;
46945                 }
46946                 if (!bTargetsOnly || dd.isTarget) {
46947                     oDDs[oDDs.length] = dd;
46948                 }
46949             }
46950         }
46951
46952         return oDDs;
46953     },
46954
46955     /**
46956      * Returns true if the specified dd target is a legal target for
46957      * the specifice drag obj
46958      * @method isLegalTarget
46959      * @param {Ext.dd.DragDrop} oDD the drag obj
46960      * @param {Ext.dd.DragDrop} oTargetDD the target
46961      * @return {Boolean} true if the target is a legal target for the
46962      * dd obj
46963      */
46964     isLegalTarget: function (oDD, oTargetDD) {
46965         var targets = this.getRelated(oDD, true);
46966         for (var i=0, len=targets.length;i<len;++i) {
46967             if (targets[i].id == oTargetDD.id) {
46968                 return true;
46969             }
46970         }
46971
46972         return false;
46973     },
46974
46975     /**
46976      * My goal is to be able to transparently determine if an object is
46977      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
46978      * returns "object", oDD.constructor.toString() always returns
46979      * "DragDrop" and not the name of the subclass.  So for now it just
46980      * evaluates a well-known variable in DragDrop.
46981      * @method isTypeOfDD
46982      * @param {Object} the object to evaluate
46983      * @return {Boolean} true if typeof oDD = DragDrop
46984      */
46985     isTypeOfDD: function (oDD) {
46986         return (oDD && oDD.__ygDragDrop);
46987     },
46988
46989     /**
46990      * Utility function to determine if a given element has been
46991      * registered as a drag drop handle for the given Drag Drop object.
46992      * @method isHandle
46993      * @param {String} id the element id to check
46994      * @return {Boolean} true if this element is a DragDrop handle, false
46995      * otherwise
46996      */
46997     isHandle: function(sDDId, sHandleId) {
46998         return ( this.handleIds[sDDId] &&
46999                         this.handleIds[sDDId][sHandleId] );
47000     },
47001
47002     /**
47003      * Returns the DragDrop instance for a given id
47004      * @method getDDById
47005      * @param {String} id the id of the DragDrop object
47006      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
47007      */
47008     getDDById: function(id) {
47009         for (var i in this.ids) {
47010             if (this.ids[i][id]) {
47011                 return this.ids[i][id];
47012             }
47013         }
47014         return null;
47015     },
47016
47017     /**
47018      * Fired after a registered DragDrop object gets the mousedown event.
47019      * Sets up the events required to track the object being dragged
47020      * @method handleMouseDown
47021      * @param {Event} e the event
47022      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
47023      * @private
47024      */
47025     handleMouseDown: function(e, oDD) {
47026         if(Ext.tip.QuickTipManager){
47027             Ext.tip.QuickTipManager.ddDisable();
47028         }
47029         if(this.dragCurrent){
47030             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
47031             // so clean up first to avoid breaking the next drag
47032             this.handleMouseUp(e);
47033         }
47034
47035         this.currentTarget = e.getTarget();
47036         this.dragCurrent = oDD;
47037
47038         var el = oDD.getEl();
47039
47040         // track start position
47041         this.startX = e.getPageX();
47042         this.startY = e.getPageY();
47043
47044         this.deltaX = this.startX - el.offsetLeft;
47045         this.deltaY = this.startY - el.offsetTop;
47046
47047         this.dragThreshMet = false;
47048
47049         this.clickTimeout = setTimeout(
47050                 function() {
47051                     var DDM = Ext.dd.DragDropManager;
47052                     DDM.startDrag(DDM.startX, DDM.startY);
47053                 },
47054                 this.clickTimeThresh );
47055     },
47056
47057     /**
47058      * Fired when either the drag pixel threshol or the mousedown hold
47059      * time threshold has been met.
47060      * @method startDrag
47061      * @param {Number} x the X position of the original mousedown
47062      * @param {Number} y the Y position of the original mousedown
47063      */
47064     startDrag: function(x, y) {
47065         clearTimeout(this.clickTimeout);
47066         if (this.dragCurrent) {
47067             this.dragCurrent.b4StartDrag(x, y);
47068             this.dragCurrent.startDrag(x, y);
47069         }
47070         this.dragThreshMet = true;
47071     },
47072
47073     /**
47074      * Internal function to handle the mouseup event.  Will be invoked
47075      * from the context of the document.
47076      * @method handleMouseUp
47077      * @param {Event} e the event
47078      * @private
47079      */
47080     handleMouseUp: function(e) {
47081
47082         if(Ext.tip && Ext.tip.QuickTipManager){
47083             Ext.tip.QuickTipManager.ddEnable();
47084         }
47085         if (! this.dragCurrent) {
47086             return;
47087         }
47088
47089         clearTimeout(this.clickTimeout);
47090
47091         if (this.dragThreshMet) {
47092             this.fireEvents(e, true);
47093         } else {
47094         }
47095
47096         this.stopDrag(e);
47097
47098         this.stopEvent(e);
47099     },
47100
47101     /**
47102      * Utility to stop event propagation and event default, if these
47103      * features are turned on.
47104      * @method stopEvent
47105      * @param {Event} e the event as returned by this.getEvent()
47106      */
47107     stopEvent: function(e){
47108         if(this.stopPropagation) {
47109             e.stopPropagation();
47110         }
47111
47112         if (this.preventDefault) {
47113             e.preventDefault();
47114         }
47115     },
47116
47117     /**
47118      * Internal function to clean up event handlers after the drag
47119      * operation is complete
47120      * @method stopDrag
47121      * @param {Event} e the event
47122      * @private
47123      */
47124     stopDrag: function(e) {
47125         // Fire the drag end event for the item that was dragged
47126         if (this.dragCurrent) {
47127             if (this.dragThreshMet) {
47128                 this.dragCurrent.b4EndDrag(e);
47129                 this.dragCurrent.endDrag(e);
47130             }
47131
47132             this.dragCurrent.onMouseUp(e);
47133         }
47134
47135         this.dragCurrent = null;
47136         this.dragOvers = {};
47137     },
47138
47139     /**
47140      * Internal function to handle the mousemove event.  Will be invoked
47141      * from the context of the html element.
47142      *
47143      * @TODO figure out what we can do about mouse events lost when the
47144      * user drags objects beyond the window boundary.  Currently we can
47145      * detect this in internet explorer by verifying that the mouse is
47146      * down during the mousemove event.  Firefox doesn't give us the
47147      * button state on the mousemove event.
47148      * @method handleMouseMove
47149      * @param {Event} e the event
47150      * @private
47151      */
47152     handleMouseMove: function(e) {
47153         if (! this.dragCurrent) {
47154             return true;
47155         }
47156         // var button = e.which || e.button;
47157
47158         // check for IE mouseup outside of page boundary
47159         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
47160             this.stopEvent(e);
47161             return this.handleMouseUp(e);
47162         }
47163
47164         if (!this.dragThreshMet) {
47165             var diffX = Math.abs(this.startX - e.getPageX());
47166             var diffY = Math.abs(this.startY - e.getPageY());
47167             if (diffX > this.clickPixelThresh ||
47168                         diffY > this.clickPixelThresh) {
47169                 this.startDrag(this.startX, this.startY);
47170             }
47171         }
47172
47173         if (this.dragThreshMet) {
47174             this.dragCurrent.b4Drag(e);
47175             this.dragCurrent.onDrag(e);
47176             if(!this.dragCurrent.moveOnly){
47177                 this.fireEvents(e, false);
47178             }
47179         }
47180
47181         this.stopEvent(e);
47182
47183         return true;
47184     },
47185
47186     /**
47187      * Iterates over all of the DragDrop elements to find ones we are
47188      * hovering over or dropping on
47189      * @method fireEvents
47190      * @param {Event} e the event
47191      * @param {Boolean} isDrop is this a drop op or a mouseover op?
47192      * @private
47193      */
47194     fireEvents: function(e, isDrop) {
47195         var dc = this.dragCurrent;
47196
47197         // If the user did the mouse up outside of the window, we could
47198         // get here even though we have ended the drag.
47199         if (!dc || dc.isLocked()) {
47200             return;
47201         }
47202
47203         var pt = e.getPoint();
47204
47205         // cache the previous dragOver array
47206         var oldOvers = [];
47207
47208         var outEvts   = [];
47209         var overEvts  = [];
47210         var dropEvts  = [];
47211         var enterEvts = [];
47212
47213         // Check to see if the object(s) we were hovering over is no longer
47214         // being hovered over so we can fire the onDragOut event
47215         for (var i in this.dragOvers) {
47216
47217             var ddo = this.dragOvers[i];
47218
47219             if (! this.isTypeOfDD(ddo)) {
47220                 continue;
47221             }
47222
47223             if (! this.isOverTarget(pt, ddo, this.mode)) {
47224                 outEvts.push( ddo );
47225             }
47226
47227             oldOvers[i] = true;
47228             delete this.dragOvers[i];
47229         }
47230
47231         for (var sGroup in dc.groups) {
47232
47233             if ("string" != typeof sGroup) {
47234                 continue;
47235             }
47236
47237             for (i in this.ids[sGroup]) {
47238                 var oDD = this.ids[sGroup][i];
47239                 if (! this.isTypeOfDD(oDD)) {
47240                     continue;
47241                 }
47242
47243                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
47244                     if (this.isOverTarget(pt, oDD, this.mode)) {
47245                         // look for drop interactions
47246                         if (isDrop) {
47247                             dropEvts.push( oDD );
47248                         // look for drag enter and drag over interactions
47249                         } else {
47250
47251                             // initial drag over: dragEnter fires
47252                             if (!oldOvers[oDD.id]) {
47253                                 enterEvts.push( oDD );
47254                             // subsequent drag overs: dragOver fires
47255                             } else {
47256                                 overEvts.push( oDD );
47257                             }
47258
47259                             this.dragOvers[oDD.id] = oDD;
47260                         }
47261                     }
47262                 }
47263             }
47264         }
47265
47266         if (this.mode) {
47267             if (outEvts.length) {
47268                 dc.b4DragOut(e, outEvts);
47269                 dc.onDragOut(e, outEvts);
47270             }
47271
47272             if (enterEvts.length) {
47273                 dc.onDragEnter(e, enterEvts);
47274             }
47275
47276             if (overEvts.length) {
47277                 dc.b4DragOver(e, overEvts);
47278                 dc.onDragOver(e, overEvts);
47279             }
47280
47281             if (dropEvts.length) {
47282                 dc.b4DragDrop(e, dropEvts);
47283                 dc.onDragDrop(e, dropEvts);
47284             }
47285
47286         } else {
47287             // fire dragout events
47288             var len = 0;
47289             for (i=0, len=outEvts.length; i<len; ++i) {
47290                 dc.b4DragOut(e, outEvts[i].id);
47291                 dc.onDragOut(e, outEvts[i].id);
47292             }
47293
47294             // fire enter events
47295             for (i=0,len=enterEvts.length; i<len; ++i) {
47296                 // dc.b4DragEnter(e, oDD.id);
47297                 dc.onDragEnter(e, enterEvts[i].id);
47298             }
47299
47300             // fire over events
47301             for (i=0,len=overEvts.length; i<len; ++i) {
47302                 dc.b4DragOver(e, overEvts[i].id);
47303                 dc.onDragOver(e, overEvts[i].id);
47304             }
47305
47306             // fire drop events
47307             for (i=0, len=dropEvts.length; i<len; ++i) {
47308                 dc.b4DragDrop(e, dropEvts[i].id);
47309                 dc.onDragDrop(e, dropEvts[i].id);
47310             }
47311
47312         }
47313
47314         // notify about a drop that did not find a target
47315         if (isDrop && !dropEvts.length) {
47316             dc.onInvalidDrop(e);
47317         }
47318
47319     },
47320
47321     /**
47322      * Helper function for getting the best match from the list of drag
47323      * and drop objects returned by the drag and drop events when we are
47324      * in INTERSECT mode.  It returns either the first object that the
47325      * cursor is over, or the object that has the greatest overlap with
47326      * the dragged element.
47327      * @method getBestMatch
47328      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
47329      * targeted
47330      * @return {Ext.dd.DragDrop}       The best single match
47331      */
47332     getBestMatch: function(dds) {
47333         var winner = null;
47334         // Return null if the input is not what we expect
47335         //if (!dds || !dds.length || dds.length == 0) {
47336            // winner = null;
47337         // If there is only one item, it wins
47338         //} else if (dds.length == 1) {
47339
47340         var len = dds.length;
47341
47342         if (len == 1) {
47343             winner = dds[0];
47344         } else {
47345             // Loop through the targeted items
47346             for (var i=0; i<len; ++i) {
47347                 var dd = dds[i];
47348                 // If the cursor is over the object, it wins.  If the
47349                 // cursor is over multiple matches, the first one we come
47350                 // to wins.
47351                 if (dd.cursorIsOver) {
47352                     winner = dd;
47353                     break;
47354                 // Otherwise the object with the most overlap wins
47355                 } else {
47356                     if (!winner ||
47357                         winner.overlap.getArea() < dd.overlap.getArea()) {
47358                         winner = dd;
47359                     }
47360                 }
47361             }
47362         }
47363
47364         return winner;
47365     },
47366
47367     /**
47368      * Refreshes the cache of the top-left and bottom-right points of the
47369      * drag and drop objects in the specified group(s).  This is in the
47370      * format that is stored in the drag and drop instance, so typical
47371      * usage is:
47372      * <code>
47373      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
47374      * </code>
47375      * Alternatively:
47376      * <code>
47377      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
47378      * </code>
47379      * @TODO this really should be an indexed array.  Alternatively this
47380      * method could accept both.
47381      * @method refreshCache
47382      * @param {Object} groups an associative array of groups to refresh
47383      */
47384     refreshCache: function(groups) {
47385         for (var sGroup in groups) {
47386             if ("string" != typeof sGroup) {
47387                 continue;
47388             }
47389             for (var i in this.ids[sGroup]) {
47390                 var oDD = this.ids[sGroup][i];
47391
47392                 if (this.isTypeOfDD(oDD)) {
47393                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
47394                     var loc = this.getLocation(oDD);
47395                     if (loc) {
47396                         this.locationCache[oDD.id] = loc;
47397                     } else {
47398                         delete this.locationCache[oDD.id];
47399                         // this will unregister the drag and drop object if
47400                         // the element is not in a usable state
47401                         // oDD.unreg();
47402                     }
47403                 }
47404             }
47405         }
47406     },
47407
47408     /**
47409      * This checks to make sure an element exists and is in the DOM.  The
47410      * main purpose is to handle cases where innerHTML is used to remove
47411      * drag and drop objects from the DOM.  IE provides an 'unspecified
47412      * error' when trying to access the offsetParent of such an element
47413      * @method verifyEl
47414      * @param {HTMLElement} el the element to check
47415      * @return {Boolean} true if the element looks usable
47416      */
47417     verifyEl: function(el) {
47418         if (el) {
47419             var parent;
47420             if(Ext.isIE){
47421                 try{
47422                     parent = el.offsetParent;
47423                 }catch(e){}
47424             }else{
47425                 parent = el.offsetParent;
47426             }
47427             if (parent) {
47428                 return true;
47429             }
47430         }
47431
47432         return false;
47433     },
47434
47435     /**
47436      * Returns a Region object containing the drag and drop element's position
47437      * and size, including the padding configured for it
47438      * @method getLocation
47439      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
47440      * @return {Ext.util.Region} a Region object representing the total area
47441      * the element occupies, including any padding
47442      * the instance is configured for.
47443      */
47444     getLocation: function(oDD) {
47445         if (! this.isTypeOfDD(oDD)) {
47446             return null;
47447         }
47448
47449         //delegate getLocation method to the
47450         //drag and drop target.
47451         if (oDD.getRegion) {
47452             return oDD.getRegion();
47453         }
47454
47455         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
47456
47457         try {
47458             pos= Ext.Element.getXY(el);
47459         } catch (e) { }
47460
47461         if (!pos) {
47462             return null;
47463         }
47464
47465         x1 = pos[0];
47466         x2 = x1 + el.offsetWidth;
47467         y1 = pos[1];
47468         y2 = y1 + el.offsetHeight;
47469
47470         t = y1 - oDD.padding[0];
47471         r = x2 + oDD.padding[1];
47472         b = y2 + oDD.padding[2];
47473         l = x1 - oDD.padding[3];
47474
47475         return Ext.create('Ext.util.Region', t, r, b, l);
47476     },
47477
47478     /**
47479      * Checks the cursor location to see if it over the target
47480      * @method isOverTarget
47481      * @param {Ext.util.Point} pt The point to evaluate
47482      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
47483      * @return {Boolean} true if the mouse is over the target
47484      * @private
47485      */
47486     isOverTarget: function(pt, oTarget, intersect) {
47487         // use cache if available
47488         var loc = this.locationCache[oTarget.id];
47489         if (!loc || !this.useCache) {
47490             loc = this.getLocation(oTarget);
47491             this.locationCache[oTarget.id] = loc;
47492
47493         }
47494
47495         if (!loc) {
47496             return false;
47497         }
47498
47499         oTarget.cursorIsOver = loc.contains( pt );
47500
47501         // DragDrop is using this as a sanity check for the initial mousedown
47502         // in this case we are done.  In POINT mode, if the drag obj has no
47503         // contraints, we are also done. Otherwise we need to evaluate the
47504         // location of the target as related to the actual location of the
47505         // dragged element.
47506         var dc = this.dragCurrent;
47507         if (!dc || !dc.getTargetCoord ||
47508                 (!intersect && !dc.constrainX && !dc.constrainY)) {
47509             return oTarget.cursorIsOver;
47510         }
47511
47512         oTarget.overlap = null;
47513
47514         // Get the current location of the drag element, this is the
47515         // location of the mouse event less the delta that represents
47516         // where the original mousedown happened on the element.  We
47517         // need to consider constraints and ticks as well.
47518         var pos = dc.getTargetCoord(pt.x, pt.y);
47519
47520         var el = dc.getDragEl();
47521         var curRegion = Ext.create('Ext.util.Region', pos.y,
47522                                                pos.x + el.offsetWidth,
47523                                                pos.y + el.offsetHeight,
47524                                                pos.x );
47525
47526         var overlap = curRegion.intersect(loc);
47527
47528         if (overlap) {
47529             oTarget.overlap = overlap;
47530             return (intersect) ? true : oTarget.cursorIsOver;
47531         } else {
47532             return false;
47533         }
47534     },
47535
47536     /**
47537      * unload event handler
47538      * @method _onUnload
47539      * @private
47540      */
47541     _onUnload: function(e, me) {
47542         Ext.dd.DragDropManager.unregAll();
47543     },
47544
47545     /**
47546      * Cleans up the drag and drop events and objects.
47547      * @method unregAll
47548      * @private
47549      */
47550     unregAll: function() {
47551
47552         if (this.dragCurrent) {
47553             this.stopDrag();
47554             this.dragCurrent = null;
47555         }
47556
47557         this._execOnAll("unreg", []);
47558
47559         for (var i in this.elementCache) {
47560             delete this.elementCache[i];
47561         }
47562
47563         this.elementCache = {};
47564         this.ids = {};
47565     },
47566
47567     /**
47568      * A cache of DOM elements
47569      * @property elementCache
47570      * @private
47571      */
47572     elementCache: {},
47573
47574     /**
47575      * Get the wrapper for the DOM element specified
47576      * @method getElWrapper
47577      * @param {String} id the id of the element to get
47578      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
47579      * @private
47580      * @deprecated This wrapper isn't that useful
47581      */
47582     getElWrapper: function(id) {
47583         var oWrapper = this.elementCache[id];
47584         if (!oWrapper || !oWrapper.el) {
47585             oWrapper = this.elementCache[id] =
47586                 new this.ElementWrapper(Ext.getDom(id));
47587         }
47588         return oWrapper;
47589     },
47590
47591     /**
47592      * Returns the actual DOM element
47593      * @method getElement
47594      * @param {String} id the id of the elment to get
47595      * @return {Object} The element
47596      * @deprecated use Ext.lib.Ext.getDom instead
47597      */
47598     getElement: function(id) {
47599         return Ext.getDom(id);
47600     },
47601
47602     /**
47603      * Returns the style property for the DOM element (i.e.,
47604      * document.getElById(id).style)
47605      * @method getCss
47606      * @param {String} id the id of the elment to get
47607      * @return {Object} The style property of the element
47608      */
47609     getCss: function(id) {
47610         var el = Ext.getDom(id);
47611         return (el) ? el.style : null;
47612     },
47613
47614     /**
47615      * @class Ext.dd.DragDropManager.ElementWrapper
47616      * Inner class for cached elements
47617      * @private
47618      * @deprecated
47619      */
47620     ElementWrapper: function(el) {
47621         /**
47622          * The element
47623          * @property el
47624          */
47625         this.el = el || null;
47626         /**
47627          * The element id
47628          * @property id
47629          */
47630         this.id = this.el && el.id;
47631         /**
47632          * A reference to the style property
47633          * @property css
47634          */
47635         this.css = this.el && el.style;
47636     },
47637
47638     // The DragDropManager class continues
47639     /** @class Ext.dd.DragDropManager */
47640
47641     /**
47642      * Returns the X position of an html element
47643      * @param {HTMLElement} el the element for which to get the position
47644      * @return {Number} the X coordinate
47645      */
47646     getPosX: function(el) {
47647         return Ext.Element.getX(el);
47648     },
47649
47650     /**
47651      * Returns the Y position of an html element
47652      * @param {HTMLElement} el the element for which to get the position
47653      * @return {Number} the Y coordinate
47654      */
47655     getPosY: function(el) {
47656         return Ext.Element.getY(el);
47657     },
47658
47659     /**
47660      * Swap two nodes.  In IE, we use the native method, for others we
47661      * emulate the IE behavior
47662      * @param {HTMLElement} n1 the first node to swap
47663      * @param {HTMLElement} n2 the other node to swap
47664      */
47665     swapNode: function(n1, n2) {
47666         if (n1.swapNode) {
47667             n1.swapNode(n2);
47668         } else {
47669             var p = n2.parentNode;
47670             var s = n2.nextSibling;
47671
47672             if (s == n1) {
47673                 p.insertBefore(n1, n2);
47674             } else if (n2 == n1.nextSibling) {
47675                 p.insertBefore(n2, n1);
47676             } else {
47677                 n1.parentNode.replaceChild(n2, n1);
47678                 p.insertBefore(n1, s);
47679             }
47680         }
47681     },
47682
47683     /**
47684      * Returns the current scroll position
47685      * @private
47686      */
47687     getScroll: function () {
47688         var doc   = window.document,
47689             docEl = doc.documentElement,
47690             body  = doc.body,
47691             top   = 0,
47692             left  = 0;
47693
47694         if (Ext.isGecko4) {
47695             top  = window.scrollYOffset;
47696             left = window.scrollXOffset;
47697         } else {
47698             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
47699                 top  = docEl.scrollTop;
47700                 left = docEl.scrollLeft;
47701             } else if (body) {
47702                 top  = body.scrollTop;
47703                 left = body.scrollLeft;
47704             }
47705         }
47706         return {
47707             top: top,
47708             left: left
47709         };
47710     },
47711
47712     /**
47713      * Returns the specified element style property
47714      * @param {HTMLElement} el          the element
47715      * @param {String}      styleProp   the style property
47716      * @return {String} The value of the style property
47717      */
47718     getStyle: function(el, styleProp) {
47719         return Ext.fly(el).getStyle(styleProp);
47720     },
47721
47722     /**
47723      * Gets the scrollTop
47724      * @return {Number} the document's scrollTop
47725      */
47726     getScrollTop: function () {
47727         return this.getScroll().top;
47728     },
47729
47730     /**
47731      * Gets the scrollLeft
47732      * @return {Number} the document's scrollTop
47733      */
47734     getScrollLeft: function () {
47735         return this.getScroll().left;
47736     },
47737
47738     /**
47739      * Sets the x/y position of an element to the location of the
47740      * target element.
47741      * @param {HTMLElement} moveEl      The element to move
47742      * @param {HTMLElement} targetEl    The position reference element
47743      */
47744     moveToEl: function (moveEl, targetEl) {
47745         var aCoord = Ext.Element.getXY(targetEl);
47746         Ext.Element.setXY(moveEl, aCoord);
47747     },
47748
47749     /**
47750      * Numeric array sort function
47751      * @param {Number} a
47752      * @param {Number} b
47753      * @returns {Number} positive, negative or 0
47754      */
47755     numericSort: function(a, b) {
47756         return (a - b);
47757     },
47758
47759     /**
47760      * Internal counter
47761      * @property {Number} _timeoutCount
47762      * @private
47763      */
47764     _timeoutCount: 0,
47765
47766     /**
47767      * Trying to make the load order less important.  Without this we get
47768      * an error if this file is loaded before the Event Utility.
47769      * @private
47770      */
47771     _addListeners: function() {
47772         if ( document ) {
47773             this._onLoad();
47774         } else {
47775             if (this._timeoutCount > 2000) {
47776             } else {
47777                 setTimeout(this._addListeners, 10);
47778                 if (document && document.body) {
47779                     this._timeoutCount += 1;
47780                 }
47781             }
47782         }
47783     },
47784
47785     /**
47786      * Recursively searches the immediate parent and all child nodes for
47787      * the handle element in order to determine wheter or not it was
47788      * clicked.
47789      * @param {HTMLElement} node the html element to inspect
47790      */
47791     handleWasClicked: function(node, id) {
47792         if (this.isHandle(id, node.id)) {
47793             return true;
47794         } else {
47795             // check to see if this is a text node child of the one we want
47796             var p = node.parentNode;
47797
47798             while (p) {
47799                 if (this.isHandle(id, p.id)) {
47800                     return true;
47801                 } else {
47802                     p = p.parentNode;
47803                 }
47804             }
47805         }
47806
47807         return false;
47808     }
47809 }, function() {
47810     this._addListeners();
47811 });
47812
47813 /**
47814  * @class Ext.layout.container.Box
47815  * @extends Ext.layout.container.Container
47816  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
47817  */
47818
47819 Ext.define('Ext.layout.container.Box', {
47820
47821     /* Begin Definitions */
47822
47823     alias: ['layout.box'],
47824     extend: 'Ext.layout.container.Container',
47825     alternateClassName: 'Ext.layout.BoxLayout',
47826
47827     requires: [
47828         'Ext.layout.container.boxOverflow.None',
47829         'Ext.layout.container.boxOverflow.Menu',
47830         'Ext.layout.container.boxOverflow.Scroller',
47831         'Ext.util.Format',
47832         'Ext.dd.DragDropManager'
47833     ],
47834
47835     /* End Definitions */
47836
47837     /**
47838      * @cfg {Boolean/Number/Object} animate
47839      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
47840      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
47841      * <p>May be set as a property at any time.</p>
47842      */
47843
47844     /**
47845      * @cfg {Object} defaultMargins
47846      * <p>If the individual contained items do not have a <tt>margins</tt>
47847      * property specified or margin specified via CSS, the default margins from this property will be
47848      * applied to each item.</p>
47849      * <br><p>This property may be specified as an object containing margins
47850      * to apply in the format:</p><pre><code>
47851 {
47852     top: (top margin),
47853     right: (right margin),
47854     bottom: (bottom margin),
47855     left: (left margin)
47856 }</code></pre>
47857      * <p>This property may also be specified as a string containing
47858      * space-separated, numeric margin values. The order of the sides associated
47859      * with each value matches the way CSS processes margin values:</p>
47860      * <div class="mdetail-params"><ul>
47861      * <li>If there is only one value, it applies to all sides.</li>
47862      * <li>If there are two values, the top and bottom borders are set to the
47863      * first value and the right and left are set to the second.</li>
47864      * <li>If there are three values, the top is set to the first value, the left
47865      * and right are set to the second, and the bottom is set to the third.</li>
47866      * <li>If there are four values, they apply to the top, right, bottom, and
47867      * left, respectively.</li>
47868      * </ul></div>
47869      */
47870     defaultMargins: {
47871         top: 0,
47872         right: 0,
47873         bottom: 0,
47874         left: 0
47875     },
47876
47877     /**
47878      * @cfg {String} padding
47879      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
47880      * <p>This property must be specified as a string containing
47881      * space-separated, numeric padding values. The order of the sides associated
47882      * with each value matches the way CSS processes padding values:</p>
47883      * <div class="mdetail-params"><ul>
47884      * <li>If there is only one value, it applies to all sides.</li>
47885      * <li>If there are two values, the top and bottom borders are set to the
47886      * first value and the right and left are set to the second.</li>
47887      * <li>If there are three values, the top is set to the first value, the left
47888      * and right are set to the second, and the bottom is set to the third.</li>
47889      * <li>If there are four values, they apply to the top, right, bottom, and
47890      * left, respectively.</li>
47891      * </ul></div>
47892      */
47893     padding: '0',
47894     // documented in subclasses
47895     pack: 'start',
47896
47897     /**
47898      * @cfg {String} pack
47899      * Controls how the child items of the container are packed together. Acceptable configuration values
47900      * for this property are:
47901      * <div class="mdetail-params"><ul>
47902      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
47903      * <b>left</b> side of container</div></li>
47904      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
47905      * <b>mid-width</b> of container</div></li>
47906      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
47907      * side of container</div></li>
47908      * </ul></div>
47909      */
47910     /**
47911      * @cfg {Number} flex
47912      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
47913      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
47914      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
47915      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
47916      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
47917      */
47918
47919     type: 'box',
47920     scrollOffset: 0,
47921     itemCls: Ext.baseCSSPrefix + 'box-item',
47922     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
47923     innerCls: Ext.baseCSSPrefix + 'box-inner',
47924
47925     bindToOwnerCtContainer: true,
47926
47927     // availableSpaceOffset is used to adjust the availableWidth, typically used
47928     // to reserve space for a scrollbar
47929     availableSpaceOffset: 0,
47930
47931     // whether or not to reserve the availableSpaceOffset in layout calculations
47932     reserveOffset: true,
47933
47934     /**
47935      * @cfg {Boolean} shrinkToFit
47936      * True (the default) to allow fixed size components to shrink (limited to their
47937      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
47938      * overflow.
47939      */
47940     shrinkToFit: true,
47941
47942     /**
47943      * @cfg {Boolean} clearInnerCtOnLayout
47944      */
47945     clearInnerCtOnLayout: false,
47946
47947     flexSortFn: function (a, b) {
47948         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
47949             infiniteValue = Infinity;
47950         a = a.component[maxParallelPrefix] || infiniteValue;
47951         b = b.component[maxParallelPrefix] || infiniteValue;
47952         // IE 6/7 Don't like Infinity - Infinity...
47953         if (!isFinite(a) && !isFinite(b)) {
47954             return false;
47955         }
47956         return a - b;
47957     },
47958
47959     // Sort into *descending* order.
47960     minSizeSortFn: function(a, b) {
47961         return b.available - a.available;
47962     },
47963
47964     constructor: function(config) {
47965         var me = this;
47966
47967         me.callParent(arguments);
47968
47969         // The sort function needs access to properties in this, so must be bound.
47970         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
47971
47972         me.initOverflowHandler();
47973     },
47974
47975     /**
47976      * @private
47977      * Returns the current size and positioning of the passed child item.
47978      * @param {Ext.Component} child The child Component to calculate the box for
47979      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47980      */
47981     getChildBox: function(child) {
47982         child = child.el || this.owner.getComponent(child).el;
47983         var size = child.getBox(false, true);
47984         return {
47985             left: size.left,
47986             top: size.top,
47987             width: size.width,
47988             height: size.height
47989         };
47990     },
47991
47992     /**
47993      * @private
47994      * Calculates the size and positioning of the passed child item.
47995      * @param {Ext.Component} child The child Component to calculate the box for
47996      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47997      */
47998     calculateChildBox: function(child) {
47999         var me = this,
48000             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
48001             ln = boxes.length,
48002             i = 0;
48003
48004         child = me.owner.getComponent(child);
48005         for (; i < ln; i++) {
48006             if (boxes[i].component === child) {
48007                 return boxes[i];
48008             }
48009         }
48010     },
48011
48012     /**
48013      * @private
48014      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
48015      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
48016      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
48017      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
48018      * @param {Object} targetSize Object containing target size and height
48019      * @return {Object} Object containing box measurements for each child, plus meta data
48020      */
48021     calculateChildBoxes: function(visibleItems, targetSize) {
48022         var me = this,
48023             math = Math,
48024             mmax = math.max,
48025             infiniteValue = Infinity,
48026             undefinedValue,
48027
48028             parallelPrefix = me.parallelPrefix,
48029             parallelPrefixCap = me.parallelPrefixCap,
48030             perpendicularPrefix = me.perpendicularPrefix,
48031             perpendicularPrefixCap = me.perpendicularPrefixCap,
48032             parallelMinString = 'min' + parallelPrefixCap,
48033             perpendicularMinString = 'min' + perpendicularPrefixCap,
48034             perpendicularMaxString = 'max' + perpendicularPrefixCap,
48035
48036             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
48037             perpendicularSize = targetSize[perpendicularPrefix],
48038             padding = me.padding,
48039             parallelOffset = padding[me.parallelBefore],
48040             paddingParallel = parallelOffset + padding[me.parallelAfter],
48041             perpendicularOffset = padding[me.perpendicularLeftTop],
48042             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
48043             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
48044
48045             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
48046
48047             isStart = me.pack == 'start',
48048             isCenter = me.pack == 'center',
48049             isEnd = me.pack == 'end',
48050
48051             constrain = Ext.Number.constrain,
48052             visibleCount = visibleItems.length,
48053             nonFlexSize = 0,
48054             totalFlex = 0,
48055             desiredSize = 0,
48056             minimumSize = 0,
48057             maxSize = 0,
48058             boxes = [],
48059             minSizes = [],
48060             calculatedWidth,
48061
48062             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
48063             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
48064             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
48065             perpendicularMargins, stretchSize;
48066
48067         //gather the total flex of all flexed items and the width taken up by fixed width items
48068         for (i = 0; i < visibleCount; i++) {
48069             child = visibleItems[i];
48070             childPerpendicular = child[perpendicularPrefix];
48071             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
48072                 if (child.componentLayout.initialized !== true) {
48073                     me.layoutItem(child);
48074                 }
48075             }
48076
48077             childMargins = child.margins;
48078             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
48079
48080             // Create the box description object for this child item.
48081             tmpObj = {
48082                 component: child,
48083                 margins: childMargins
48084             };
48085
48086             // flex and not 'auto' width
48087             if (child.flex) {
48088                 totalFlex += child.flex;
48089                 childParallel = undefinedValue;
48090             }
48091             // Not flexed or 'auto' width or undefined width
48092             else {
48093                 if (!(child[parallelPrefix] && childPerpendicular)) {
48094                     childSize = child.getSize();
48095                 }
48096                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
48097                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
48098             }
48099
48100             nonFlexSize += parallelMargins + (childParallel || 0);
48101             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
48102             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
48103
48104             // Max height for align - force layout of non-laid out subcontainers without a numeric height
48105             if (typeof childPerpendicular != 'number') {
48106                 // Clear any static sizing and revert to flow so we can get a proper measurement
48107                 // child['set' + perpendicularPrefixCap](null);
48108                 childPerpendicular = child['get' + perpendicularPrefixCap]();
48109             }
48110
48111             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
48112             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
48113             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
48114
48115             tmpObj[parallelPrefix] = childParallel || undefinedValue;
48116             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
48117             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
48118             boxes.push(tmpObj);
48119         }
48120
48121         // Only calculate parallel overflow indicators if we are not auto sizing
48122         if (!me.autoSize) {
48123             shortfall = desiredSize - parallelSize;
48124             tooNarrow = minimumSize > parallelSize;
48125         }
48126
48127         //the space available to the flexed items
48128         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
48129
48130         if (tooNarrow) {
48131             for (i = 0; i < visibleCount; i++) {
48132                 box = boxes[i];
48133                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
48134                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48135                 box[parallelPrefix] = minSize;
48136             }
48137         }
48138         else {
48139             //all flexed items should be sized to their minimum size, other items should be shrunk down until
48140             //the shortfall has been accounted for
48141             if (shortfall > 0) {
48142                 /*
48143                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
48144                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
48145                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
48146                  */
48147                 for (i = 0; i < visibleCount; i++) {
48148                     item = visibleItems[i];
48149                     minSize = item[parallelMinString] || 0;
48150
48151                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
48152                     //shrunk to their minSize because they're flexible and should be the first to lose size
48153                     if (item.flex) {
48154                         box = boxes[i];
48155                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48156                         box[parallelPrefix] = minSize;
48157                     } else if (me.shrinkToFit) {
48158                         minSizes.push({
48159                             minSize: minSize,
48160                             available: boxes[i][parallelPrefix] - minSize,
48161                             index: i
48162                         });
48163                     }
48164                 }
48165
48166                 //sort by descending amount of width remaining before minWidth is reached
48167                 Ext.Array.sort(minSizes, me.minSizeSortFn);
48168
48169                 /*
48170                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
48171                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
48172                  * smallest difference between their size and minSize first, so that if reducing the size by the average
48173                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
48174                  */
48175                 for (i = 0, length = minSizes.length; i < length; i++) {
48176                     itemIndex = minSizes[i].index;
48177
48178                     if (itemIndex == undefinedValue) {
48179                         continue;
48180                     }
48181                     item = visibleItems[itemIndex];
48182                     minSize = minSizes[i].minSize;
48183
48184                     box = boxes[itemIndex];
48185                     oldSize = box[parallelPrefix];
48186                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
48187                     reduction = oldSize - newSize;
48188
48189                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
48190                     box[parallelPrefix] = newSize;
48191                     shortfall -= reduction;
48192                 }
48193                 tooNarrow = (shortfall > 0);
48194             }
48195             else {
48196                 remainingSpace = availableSpace;
48197                 remainingFlex = totalFlex;
48198                 flexedBoxes = [];
48199
48200                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
48201                 for (i = 0; i < visibleCount; i++) {
48202                     child = visibleItems[i];
48203                     if (isStart && child.flex) {
48204                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
48205                     }
48206                 }
48207                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
48208                 // so that unallocated space caused by maxWidth being less than flexed width
48209                 // can be reallocated to subsequent flexed boxes.
48210                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
48211
48212                 // Calculate the size of each flexed item, and attempt to set it.
48213                 for (i = 0; i < flexedBoxes.length; i++) {
48214                     calcs = flexedBoxes[i];
48215                     child = calcs.component;
48216                     childMargins = calcs.margins;
48217
48218                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
48219
48220                     // Implement maxSize and minSize check
48221                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
48222
48223                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
48224                     remainingSpace -= flexedSize;
48225                     remainingFlex -= child.flex;
48226
48227                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
48228                     calcs[parallelPrefix] = flexedSize;
48229                 }
48230             }
48231         }
48232
48233         if (isCenter) {
48234             parallelOffset += availableSpace / 2;
48235         }
48236         else if (isEnd) {
48237             parallelOffset += availableSpace;
48238         }
48239
48240         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
48241         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
48242         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
48243         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
48244         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
48245
48246             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
48247             if (me.owner.frameSize) {
48248                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
48249             }
48250             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
48251             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
48252         }
48253
48254         //finally, calculate the left and top position of each item
48255         for (i = 0; i < visibleCount; i++) {
48256             child = visibleItems[i];
48257             calcs = boxes[i];
48258
48259             childMargins = calcs.margins;
48260
48261             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
48262
48263             // Advance past the "before" margin
48264             parallelOffset += childMargins[me.parallelBefore];
48265
48266             calcs[me.parallelBefore] = parallelOffset;
48267             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
48268
48269             if (me.align == 'stretch') {
48270                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48271                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48272                 calcs[perpendicularPrefix] = stretchSize;
48273             }
48274             else if (me.align == 'stretchmax') {
48275                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48276                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48277                 calcs[perpendicularPrefix] = stretchSize;
48278             }
48279             else if (me.align == me.alignCenteringString) {
48280                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
48281                 // the size to yield the space available to center within.
48282                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
48283                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
48284                 if (diff > 0) {
48285                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
48286                 }
48287             }
48288
48289             // Advance past the box size and the "after" margin
48290             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
48291         }
48292
48293         return {
48294             boxes: boxes,
48295             meta : {
48296                 calculatedWidth: calculatedWidth,
48297                 maxSize: maxSize,
48298                 nonFlexSize: nonFlexSize,
48299                 desiredSize: desiredSize,
48300                 minimumSize: minimumSize,
48301                 shortfall: shortfall,
48302                 tooNarrow: tooNarrow
48303             }
48304         };
48305     },
48306
48307     onRemove: function(comp){
48308         this.callParent(arguments);
48309         if (this.overflowHandler) {
48310             this.overflowHandler.onRemove(comp);
48311         }
48312     },
48313
48314     /**
48315      * @private
48316      */
48317     initOverflowHandler: function() {
48318         var handler = this.overflowHandler;
48319
48320         if (typeof handler == 'string') {
48321             handler = {
48322                 type: handler
48323             };
48324         }
48325
48326         var handlerType = 'None';
48327         if (handler && handler.type !== undefined) {
48328             handlerType = handler.type;
48329         }
48330
48331         var constructor = Ext.layout.container.boxOverflow[handlerType];
48332         if (constructor[this.type]) {
48333             constructor = constructor[this.type];
48334         }
48335
48336         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
48337     },
48338
48339     /**
48340      * @private
48341      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
48342      * when laying out
48343      */
48344     onLayout: function() {
48345         this.callParent();
48346         // Clear the innerCt size so it doesn't influence the child items.
48347         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
48348             this.innerCt.setSize(null, null);
48349         }
48350
48351         var me = this,
48352             targetSize = me.getLayoutTargetSize(),
48353             items = me.getVisibleItems(),
48354             calcs = me.calculateChildBoxes(items, targetSize),
48355             boxes = calcs.boxes,
48356             meta = calcs.meta,
48357             handler, method, results;
48358
48359         if (me.autoSize && calcs.meta.desiredSize) {
48360             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
48361         }
48362
48363         //invoke the overflow handler, if one is configured
48364         if (meta.shortfall > 0) {
48365             handler = me.overflowHandler;
48366             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
48367
48368             results = handler[method](calcs, targetSize);
48369
48370             if (results) {
48371                 if (results.targetSize) {
48372                     targetSize = results.targetSize;
48373                 }
48374
48375                 if (results.recalculate) {
48376                     items = me.getVisibleItems();
48377                     calcs = me.calculateChildBoxes(items, targetSize);
48378                     boxes = calcs.boxes;
48379                 }
48380             }
48381         } else {
48382             me.overflowHandler.clearOverflow();
48383         }
48384
48385         /**
48386          * @private
48387          * @property layoutTargetLastSize
48388          * @type Object
48389          * Private cache of the last measured size of the layout target. This should never be used except by
48390          * BoxLayout subclasses during their onLayout run.
48391          */
48392         me.layoutTargetLastSize = targetSize;
48393
48394         /**
48395          * @private
48396          * @property childBoxCache
48397          * @type Array
48398          * Array of the last calculated height, width, top and left positions of each visible rendered component
48399          * within the Box layout.
48400          */
48401         me.childBoxCache = calcs;
48402
48403         me.updateInnerCtSize(targetSize, calcs);
48404         me.updateChildBoxes(boxes);
48405         me.handleTargetOverflow(targetSize);
48406     },
48407     
48408     animCallback: Ext.emptyFn,
48409
48410     /**
48411      * Resizes and repositions each child component
48412      * @param {Object[]} boxes The box measurements
48413      */
48414     updateChildBoxes: function(boxes) {
48415         var me = this,
48416             i = 0,
48417             length = boxes.length,
48418             animQueue = [],
48419             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
48420             oldBox, newBox, changed, comp, boxAnim, animCallback;
48421
48422         for (; i < length; i++) {
48423             newBox = boxes[i];
48424             comp = newBox.component;
48425
48426             // If a Component is being drag/dropped, skip positioning it.
48427             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
48428             if (dd && (dd.getDragEl() === comp.el.dom)) {
48429                 continue;
48430             }
48431
48432             changed = false;
48433
48434             oldBox = me.getChildBox(comp);
48435
48436             // If we are animating, we build up an array of Anim config objects, one for each
48437             // child Component which has any changed box properties. Those with unchanged
48438             // properties are not animated.
48439             if (me.animate) {
48440                 // Animate may be a config object containing callback.
48441                 animCallback = me.animate.callback || me.animate;
48442                 boxAnim = {
48443                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
48444                     target: comp,
48445                     from: {},
48446                     to: {},
48447                     listeners: {}
48448                 };
48449                 // Only set from and to properties when there's a change.
48450                 // Perform as few Component setter methods as possible.
48451                 // Temporarily set the property values that we are not animating
48452                 // so that doComponentLayout does not auto-size them.
48453                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
48454                     changed = true;
48455                     // boxAnim.from.width = oldBox.width;
48456                     boxAnim.to.width = newBox.width;
48457                 }
48458                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
48459                     changed = true;
48460                     // boxAnim.from.height = oldBox.height;
48461                     boxAnim.to.height = newBox.height;
48462                 }
48463                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
48464                     changed = true;
48465                     // boxAnim.from.left = oldBox.left;
48466                     boxAnim.to.left = newBox.left;
48467                 }
48468                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
48469                     changed = true;
48470                     // boxAnim.from.top = oldBox.top;
48471                     boxAnim.to.top = newBox.top;
48472                 }
48473                 if (changed) {
48474                     animQueue.push(boxAnim);
48475                 }
48476             } else {
48477                 if (newBox.dirtySize) {
48478                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
48479                         me.setItemSize(comp, newBox.width, newBox.height);
48480                     }
48481                 }
48482                 // Don't set positions to NaN
48483                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
48484                     continue;
48485                 }
48486                 comp.setPosition(newBox.left, newBox.top);
48487             }
48488         }
48489
48490         // Kick off any queued animations
48491         length = animQueue.length;
48492         if (length) {
48493
48494             // A function which cleans up when a Component's animation is done.
48495             // The last one to finish calls the callback.
48496             var afterAnimate = function(anim) {
48497                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
48498                 length -= 1;
48499                 if (!length) {
48500                     me.animCallback(anim);
48501                     me.layoutBusy = false;
48502                     if (Ext.isFunction(animCallback)) {
48503                         animCallback();
48504                     }
48505                 }
48506             };
48507
48508             var beforeAnimate = function() {
48509                 me.layoutBusy = true;
48510             };
48511
48512             // Start each box animation off
48513             for (i = 0, length = animQueue.length; i < length; i++) {
48514                 boxAnim = animQueue[i];
48515
48516                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
48517                 boxAnim.listeners.afteranimate = afterAnimate;
48518
48519                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
48520                 if (!i) {
48521                     boxAnim.listeners.beforeanimate = beforeAnimate;
48522                 }
48523                 if (me.animate.duration) {
48524                     boxAnim.duration = me.animate.duration;
48525                 }
48526                 comp = boxAnim.target;
48527                 delete boxAnim.target;
48528                 // Stop any currently running animation
48529                 comp.stopAnimation();
48530                 comp.animate(boxAnim);
48531             }
48532         }
48533     },
48534
48535     /**
48536      * @private
48537      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
48538      * to make sure all child items fit within it. We call this before sizing the children because if our child
48539      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
48540      * again immediately afterwards, giving a performance hit.
48541      * Subclasses should provide an implementation.
48542      * @param {Object} currentSize The current height and width of the innerCt
48543      * @param {Object} calculations The new box calculations of all items to be laid out
48544      */
48545     updateInnerCtSize: function(tSize, calcs) {
48546         var me = this,
48547             mmax = Math.max,
48548             align = me.align,
48549             padding = me.padding,
48550             width = tSize.width,
48551             height = tSize.height,
48552             meta = calcs.meta,
48553             innerCtWidth,
48554             innerCtHeight;
48555
48556         if (me.direction == 'horizontal') {
48557             innerCtWidth = width;
48558             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
48559
48560             if (align == 'stretch') {
48561                 innerCtHeight = height;
48562             }
48563             else if (align == 'middle') {
48564                 innerCtHeight = mmax(height, innerCtHeight);
48565             }
48566         } else {
48567             innerCtHeight = height;
48568             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
48569
48570             if (align == 'stretch') {
48571                 innerCtWidth = width;
48572             }
48573             else if (align == 'center') {
48574                 innerCtWidth = mmax(width, innerCtWidth);
48575             }
48576         }
48577         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
48578
48579         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
48580         // then, if the Component has not assumed the size of its content, set it to do so.
48581         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
48582             me.owner.el.setWidth(meta.calculatedWidth);
48583         }
48584
48585         if (me.innerCt.dom.scrollTop) {
48586             me.innerCt.dom.scrollTop = 0;
48587         }
48588     },
48589
48590     /**
48591      * @private
48592      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
48593      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
48594      * target. Having a Box layout inside such a target is therefore not recommended.
48595      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
48596      * @param {Ext.container.Container} container The container
48597      * @param {Ext.Element} target The target element
48598      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
48599      */
48600     handleTargetOverflow: function(previousTargetSize) {
48601         var target = this.getTarget(),
48602             overflow = target.getStyle('overflow'),
48603             newTargetSize;
48604
48605         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
48606             newTargetSize = this.getLayoutTargetSize();
48607             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
48608                 this.adjustmentPass = true;
48609                 this.onLayout();
48610                 return true;
48611             }
48612         }
48613
48614         delete this.adjustmentPass;
48615     },
48616
48617     // private
48618     isValidParent : function(item, target, position) {
48619         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
48620         // We only care whether the item is a direct child of the innerCt element.
48621         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
48622         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
48623     },
48624
48625     // Overridden method from AbstractContainer.
48626     // Used in the base AbstractLayout.beforeLayout method to render all items into.
48627     getRenderTarget: function() {
48628         if (!this.innerCt) {
48629             // the innerCt prevents wrapping and shuffling while the container is resizing
48630             this.innerCt = this.getTarget().createChild({
48631                 cls: this.innerCls,
48632                 role: 'presentation'
48633             });
48634             this.padding = Ext.util.Format.parseBox(this.padding);
48635         }
48636         return this.innerCt;
48637     },
48638
48639     // private
48640     renderItem: function(item, target) {
48641         this.callParent(arguments);
48642         var me = this,
48643             itemEl = item.getEl(),
48644             style = itemEl.dom.style,
48645             margins = item.margins || item.margin;
48646
48647         // Parse the item's margin/margins specification
48648         if (margins) {
48649             if (Ext.isString(margins) || Ext.isNumber(margins)) {
48650                 margins = Ext.util.Format.parseBox(margins);
48651             } else {
48652                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
48653             }
48654         } else {
48655             margins = Ext.apply({}, me.defaultMargins);
48656         }
48657
48658         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
48659         margins.top    += itemEl.getMargin('t');
48660         margins.right  += itemEl.getMargin('r');
48661         margins.bottom += itemEl.getMargin('b');
48662         margins.left   += itemEl.getMargin('l');
48663         margins.height  = margins.top  + margins.bottom;
48664         margins.width   = margins.left + margins.right;
48665         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
48666
48667         // Item must reference calculated margins.
48668         item.margins = margins;
48669     },
48670
48671     /**
48672      * @private
48673      */
48674     destroy: function() {
48675         Ext.destroy(this.innerCt, this.overflowHandler);
48676         this.callParent(arguments);
48677     }
48678 });
48679 /**
48680  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
48681  * space between child items containing a numeric `flex` configuration.
48682  *
48683  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
48684  *
48685  *     @example
48686  *     Ext.create('Ext.Panel', {
48687  *         width: 500,
48688  *         height: 300,
48689  *         title: "HBoxLayout Panel",
48690  *         layout: {
48691  *             type: 'hbox',
48692  *             align: 'stretch'
48693  *         },
48694  *         renderTo: document.body,
48695  *         items: [{
48696  *             xtype: 'panel',
48697  *             title: 'Inner Panel One',
48698  *             flex: 2
48699  *         },{
48700  *             xtype: 'panel',
48701  *             title: 'Inner Panel Two',
48702  *             flex: 1
48703  *         },{
48704  *             xtype: 'panel',
48705  *             title: 'Inner Panel Three',
48706  *             flex: 1
48707  *         }]
48708  *     });
48709  */
48710 Ext.define('Ext.layout.container.HBox', {
48711
48712     /* Begin Definitions */
48713
48714     alias: ['layout.hbox'],
48715     extend: 'Ext.layout.container.Box',
48716     alternateClassName: 'Ext.layout.HBoxLayout',
48717
48718     /* End Definitions */
48719
48720     /**
48721      * @cfg {String} align
48722      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48723      *
48724      * - **top** : **Default** child items are aligned vertically at the **top** of the container
48725      * - **middle** : child items are aligned vertically in the **middle** of the container
48726      * - **stretch** : child items are stretched vertically to fill the height of the container
48727      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
48728      */
48729     align: 'top', // top, middle, stretch, strechmax
48730
48731     //@private
48732     alignCenteringString: 'middle',
48733
48734     type : 'hbox',
48735
48736     direction: 'horizontal',
48737
48738     // When creating an argument list to setSize, use this order
48739     parallelSizeIndex: 0,
48740     perpendicularSizeIndex: 1,
48741
48742     parallelPrefix: 'width',
48743     parallelPrefixCap: 'Width',
48744     parallelLT: 'l',
48745     parallelRB: 'r',
48746     parallelBefore: 'left',
48747     parallelBeforeCap: 'Left',
48748     parallelAfter: 'right',
48749     parallelPosition: 'x',
48750
48751     perpendicularPrefix: 'height',
48752     perpendicularPrefixCap: 'Height',
48753     perpendicularLT: 't',
48754     perpendicularRB: 'b',
48755     perpendicularLeftTop: 'top',
48756     perpendicularRightBottom: 'bottom',
48757     perpendicularPosition: 'y',
48758     configureItem: function(item) {
48759         if (item.flex) {
48760             item.layoutManagedWidth = 1;
48761         } else {
48762             item.layoutManagedWidth = 2;
48763         }
48764
48765         if (this.align === 'stretch' || this.align === 'stretchmax') {
48766             item.layoutManagedHeight = 1;
48767         } else {
48768             item.layoutManagedHeight = 2;
48769         }
48770         this.callParent(arguments);
48771     }
48772 });
48773 /**
48774  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
48775  * between child items containing a numeric `flex` configuration.
48776  *
48777  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
48778  *
48779  *     @example
48780  *     Ext.create('Ext.Panel', {
48781  *         width: 500,
48782  *         height: 400,
48783  *         title: "VBoxLayout Panel",
48784  *         layout: {
48785  *             type: 'vbox',
48786  *             align: 'center'
48787  *         },
48788  *         renderTo: document.body,
48789  *         items: [{
48790  *             xtype: 'panel',
48791  *             title: 'Inner Panel One',
48792  *             width: 250,
48793  *             flex: 2
48794  *         },
48795  *         {
48796  *             xtype: 'panel',
48797  *             title: 'Inner Panel Two',
48798  *             width: 250,
48799  *             flex: 4
48800  *         },
48801  *         {
48802  *             xtype: 'panel',
48803  *             title: 'Inner Panel Three',
48804  *             width: '50%',
48805  *             flex: 4
48806  *         }]
48807  *     });
48808  */
48809 Ext.define('Ext.layout.container.VBox', {
48810
48811     /* Begin Definitions */
48812
48813     alias: ['layout.vbox'],
48814     extend: 'Ext.layout.container.Box',
48815     alternateClassName: 'Ext.layout.VBoxLayout',
48816
48817     /* End Definitions */
48818
48819     /**
48820      * @cfg {String} align
48821      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48822      *
48823      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
48824      * - **center** : child items are aligned horizontally at the **mid-width** of the container
48825      * - **stretch** : child items are stretched horizontally to fill the width of the container
48826      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
48827      */
48828     align : 'left', // left, center, stretch, strechmax
48829
48830     //@private
48831     alignCenteringString: 'center',
48832
48833     type: 'vbox',
48834
48835     direction: 'vertical',
48836
48837     // When creating an argument list to setSize, use this order
48838     parallelSizeIndex: 1,
48839     perpendicularSizeIndex: 0,
48840
48841     parallelPrefix: 'height',
48842     parallelPrefixCap: 'Height',
48843     parallelLT: 't',
48844     parallelRB: 'b',
48845     parallelBefore: 'top',
48846     parallelBeforeCap: 'Top',
48847     parallelAfter: 'bottom',
48848     parallelPosition: 'y',
48849
48850     perpendicularPrefix: 'width',
48851     perpendicularPrefixCap: 'Width',
48852     perpendicularLT: 'l',
48853     perpendicularRB: 'r',
48854     perpendicularLeftTop: 'left',
48855     perpendicularRightBottom: 'right',
48856     perpendicularPosition: 'x',
48857     configureItem: function(item) {
48858         if (item.flex) {
48859             item.layoutManagedHeight = 1;
48860         } else {
48861             item.layoutManagedHeight = 2;
48862         }
48863
48864         if (this.align === 'stretch' || this.align === 'stretchmax') {
48865             item.layoutManagedWidth = 1;
48866         } else {
48867             item.layoutManagedWidth = 2;
48868         }
48869         this.callParent(arguments);
48870     }
48871 });
48872 /**
48873  * @class Ext.FocusManager
48874
48875 The FocusManager is responsible for globally:
48876
48877 1. Managing component focus
48878 2. Providing basic keyboard navigation
48879 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
48880
48881 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
48882 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
48883 FocusManager is disabled by default.
48884
48885 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
48886
48887 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
48888 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
48889 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
48890 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
48891
48892  * @singleton
48893  * @author Jarred Nicholls <jarred@sencha.com>
48894  * @docauthor Jarred Nicholls <jarred@sencha.com>
48895  */
48896 Ext.define('Ext.FocusManager', {
48897     singleton: true,
48898     alternateClassName: 'Ext.FocusMgr',
48899
48900     mixins: {
48901         observable: 'Ext.util.Observable'
48902     },
48903
48904     requires: [
48905         'Ext.ComponentManager',
48906         'Ext.ComponentQuery',
48907         'Ext.util.HashMap',
48908         'Ext.util.KeyNav'
48909     ],
48910
48911     /**
48912      * @property {Boolean} enabled
48913      * Whether or not the FocusManager is currently enabled
48914      */
48915     enabled: false,
48916
48917     /**
48918      * @property {Ext.Component} focusedCmp
48919      * The currently focused component. Defaults to `undefined`.
48920      */
48921
48922     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
48923
48924     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
48925
48926     /**
48927      * @property {String[]} whitelist
48928      * A list of xtypes that should ignore certain navigation input keys and
48929      * allow for the default browser event/behavior. These input keys include:
48930      *
48931      * 1. Backspace
48932      * 2. Delete
48933      * 3. Left
48934      * 4. Right
48935      * 5. Up
48936      * 6. Down
48937      *
48938      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
48939      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
48940      * the user to move the input cursor left and right, and to delete characters, etc.
48941      */
48942     whitelist: [
48943         'textfield'
48944     ],
48945
48946     tabIndexWhitelist: [
48947         'a',
48948         'button',
48949         'embed',
48950         'frame',
48951         'iframe',
48952         'img',
48953         'input',
48954         'object',
48955         'select',
48956         'textarea'
48957     ],
48958
48959     constructor: function() {
48960         var me = this,
48961             CQ = Ext.ComponentQuery;
48962
48963         me.addEvents(
48964             /**
48965              * @event beforecomponentfocus
48966              * Fires before a component becomes focused. Return `false` to prevent
48967              * the component from gaining focus.
48968              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48969              * @param {Ext.Component} cmp The component that is being focused
48970              * @param {Ext.Component} previousCmp The component that was previously focused,
48971              * or `undefined` if there was no previously focused component.
48972              */
48973             'beforecomponentfocus',
48974
48975             /**
48976              * @event componentfocus
48977              * Fires after a component becomes focused.
48978              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48979              * @param {Ext.Component} cmp The component that has been focused
48980              * @param {Ext.Component} previousCmp The component that was previously focused,
48981              * or `undefined` if there was no previously focused component.
48982              */
48983             'componentfocus',
48984
48985             /**
48986              * @event disable
48987              * Fires when the FocusManager is disabled
48988              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48989              */
48990             'disable',
48991
48992             /**
48993              * @event enable
48994              * Fires when the FocusManager is enabled
48995              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48996              */
48997             'enable'
48998         );
48999
49000         // Setup KeyNav that's bound to document to catch all
49001         // unhandled/bubbled key events for navigation
49002         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
49003             disabled: true,
49004             scope: me,
49005
49006             backspace: me.focusLast,
49007             enter: me.navigateIn,
49008             esc: me.navigateOut,
49009             tab: me.navigateSiblings
49010
49011             //space: me.navigateIn,
49012             //del: me.focusLast,
49013             //left: me.navigateSiblings,
49014             //right: me.navigateSiblings,
49015             //down: me.navigateSiblings,
49016             //up: me.navigateSiblings
49017         });
49018
49019         me.focusData = {};
49020         me.subscribers = Ext.create('Ext.util.HashMap');
49021         me.focusChain = {};
49022
49023         // Setup some ComponentQuery pseudos
49024         Ext.apply(CQ.pseudos, {
49025             focusable: function(cmps) {
49026                 var len = cmps.length,
49027                     results = [],
49028                     i = 0,
49029                     c,
49030
49031                     isFocusable = function(x) {
49032                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
49033                     };
49034
49035                 for (; i < len; i++) {
49036                     c = cmps[i];
49037                     if (isFocusable(c)) {
49038                         results.push(c);
49039                     }
49040                 }
49041
49042                 return results;
49043             },
49044
49045             nextFocus: function(cmps, idx, step) {
49046                 step = step || 1;
49047                 idx = parseInt(idx, 10);
49048
49049                 var len = cmps.length,
49050                     i = idx + step,
49051                     c;
49052
49053                 for (; i != idx; i += step) {
49054                     if (i >= len) {
49055                         i = 0;
49056                     } else if (i < 0) {
49057                         i = len - 1;
49058                     }
49059
49060                     c = cmps[i];
49061                     if (CQ.is(c, ':focusable')) {
49062                         return [c];
49063                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
49064                         return [c.placeholder];
49065                     }
49066                 }
49067
49068                 return [];
49069             },
49070
49071             prevFocus: function(cmps, idx) {
49072                 return this.nextFocus(cmps, idx, -1);
49073             },
49074
49075             root: function(cmps) {
49076                 var len = cmps.length,
49077                     results = [],
49078                     i = 0,
49079                     c;
49080
49081                 for (; i < len; i++) {
49082                     c = cmps[i];
49083                     if (!c.ownerCt) {
49084                         results.push(c);
49085                     }
49086                 }
49087
49088                 return results;
49089             }
49090         });
49091     },
49092
49093     /**
49094      * Adds the specified xtype to the {@link #whitelist}.
49095      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
49096      */
49097     addXTypeToWhitelist: function(xtype) {
49098         var me = this;
49099
49100         if (Ext.isArray(xtype)) {
49101             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
49102             return;
49103         }
49104
49105         if (!Ext.Array.contains(me.whitelist, xtype)) {
49106             me.whitelist.push(xtype);
49107         }
49108     },
49109
49110     clearComponent: function(cmp) {
49111         clearTimeout(this.cmpFocusDelay);
49112         if (!cmp.isDestroyed) {
49113             cmp.blur();
49114         }
49115     },
49116
49117     /**
49118      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
49119      */
49120     disable: function() {
49121         var me = this;
49122
49123         if (!me.enabled) {
49124             return;
49125         }
49126
49127         delete me.options;
49128         me.enabled = false;
49129
49130         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
49131
49132         me.removeDOM();
49133
49134         // Stop handling key navigation
49135         me.keyNav.disable();
49136
49137         // disable focus for all components
49138         me.setFocusAll(false);
49139
49140         me.fireEvent('disable', me);
49141     },
49142
49143     /**
49144      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
49145      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
49146         - focusFrame : Boolean
49147             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49148      * @markdown
49149      */
49150     enable: function(options) {
49151         var me = this;
49152
49153         if (options === true) {
49154             options = { focusFrame: true };
49155         }
49156         me.options = options = options || {};
49157
49158         if (me.enabled) {
49159             return;
49160         }
49161
49162         // Handle components that are newly added after we are enabled
49163         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
49164
49165         me.initDOM(options);
49166
49167         // Start handling key navigation
49168         me.keyNav.enable();
49169
49170         // enable focus for all components
49171         me.setFocusAll(true, options);
49172
49173         // Finally, let's focus our global focus el so we start fresh
49174         me.focusEl.focus();
49175         delete me.focusedCmp;
49176
49177         me.enabled = true;
49178         me.fireEvent('enable', me);
49179     },
49180
49181     focusLast: function(e) {
49182         var me = this;
49183
49184         if (me.isWhitelisted(me.focusedCmp)) {
49185             return true;
49186         }
49187
49188         // Go back to last focused item
49189         if (me.previousFocusedCmp) {
49190             me.previousFocusedCmp.focus();
49191         }
49192     },
49193
49194     getRootComponents: function() {
49195         var me = this,
49196             CQ = Ext.ComponentQuery,
49197             inline = CQ.query(':focusable:root:not([floating])'),
49198             floating = CQ.query(':focusable:root[floating]');
49199
49200         // Floating items should go to the top of our root stack, and be ordered
49201         // by their z-index (highest first)
49202         floating.sort(function(a, b) {
49203             return a.el.getZIndex() > b.el.getZIndex();
49204         });
49205
49206         return floating.concat(inline);
49207     },
49208
49209     initDOM: function(options) {
49210         var me = this,
49211             sp = '&#160',
49212             cls = me.focusFrameCls;
49213
49214         if (!Ext.isReady) {
49215             Ext.onReady(me.initDOM, me);
49216             return;
49217         }
49218
49219         // Create global focus element
49220         if (!me.focusEl) {
49221             me.focusEl = Ext.getBody().createChild({
49222                 tabIndex: '-1',
49223                 cls: me.focusElementCls,
49224                 html: sp
49225             });
49226         }
49227
49228         // Create global focus frame
49229         if (!me.focusFrame && options.focusFrame) {
49230             me.focusFrame = Ext.getBody().createChild({
49231                 cls: cls,
49232                 children: [
49233                     { cls: cls + '-top' },
49234                     { cls: cls + '-bottom' },
49235                     { cls: cls + '-left' },
49236                     { cls: cls + '-right' }
49237                 ],
49238                 style: 'top: -100px; left: -100px;'
49239             });
49240             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
49241             me.focusFrameWidth = 2;
49242             me.focusFrame.hide().setLeftTop(0, 0);
49243         }
49244     },
49245
49246     isWhitelisted: function(cmp) {
49247         return cmp && Ext.Array.some(this.whitelist, function(x) {
49248             return cmp.isXType(x);
49249         });
49250     },
49251
49252     navigateIn: function(e) {
49253         var me = this,
49254             focusedCmp = me.focusedCmp,
49255             rootCmps,
49256             firstChild;
49257
49258         if (!focusedCmp) {
49259             // No focus yet, so focus the first root cmp on the page
49260             rootCmps = me.getRootComponents();
49261             if (rootCmps.length) {
49262                 rootCmps[0].focus();
49263             }
49264         } else {
49265             // Drill into child ref items of the focused cmp, if applicable.
49266             // This works for any Component with a getRefItems implementation.
49267             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
49268             if (firstChild) {
49269                 firstChild.focus();
49270             } else {
49271                 // Let's try to fire a click event, as if it came from the mouse
49272                 if (Ext.isFunction(focusedCmp.onClick)) {
49273                     e.button = 0;
49274                     focusedCmp.onClick(e);
49275                     focusedCmp.focus();
49276                 }
49277             }
49278         }
49279     },
49280
49281     navigateOut: function(e) {
49282         var me = this,
49283             parent;
49284
49285         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
49286             me.focusEl.focus();
49287         } else {
49288             parent.focus();
49289         }
49290
49291         // In some browsers (Chrome) FocusManager can handle this before other
49292         // handlers. Ext Windows have their own Esc key handling, so we need to
49293         // return true here to allow the event to bubble.
49294         return true;
49295     },
49296
49297     navigateSiblings: function(e, source, parent) {
49298         var me = this,
49299             src = source || me,
49300             key = e.getKey(),
49301             EO = Ext.EventObject,
49302             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
49303             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
49304             nextSelector = goBack ? 'prev' : 'next',
49305             idx, next, focusedCmp;
49306
49307         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
49308         if (!focusedCmp && !parent) {
49309             return;
49310         }
49311
49312         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
49313             return true;
49314         }
49315
49316         parent = parent || focusedCmp.up();
49317         if (parent) {
49318             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
49319             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
49320             if (next && focusedCmp !== next) {
49321                 next.focus();
49322                 return next;
49323             }
49324         }
49325     },
49326
49327     onComponentBlur: function(cmp, e) {
49328         var me = this;
49329
49330         if (me.focusedCmp === cmp) {
49331             me.previousFocusedCmp = cmp;
49332             delete me.focusedCmp;
49333         }
49334
49335         if (me.focusFrame) {
49336             me.focusFrame.hide();
49337         }
49338     },
49339
49340     onComponentCreated: function(hash, id, cmp) {
49341         this.setFocus(cmp, true, this.options);
49342     },
49343
49344     onComponentDestroy: function(cmp) {
49345         this.setFocus(cmp, false);
49346     },
49347
49348     onComponentFocus: function(cmp, e) {
49349         var me = this,
49350             chain = me.focusChain;
49351
49352         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
49353             me.clearComponent(cmp);
49354
49355             // Check our focus chain, so we don't run into a never ending recursion
49356             // If we've attempted (unsuccessfully) to focus this component before,
49357             // then we're caught in a loop of child->parent->...->child and we
49358             // need to cut the loop off rather than feed into it.
49359             if (chain[cmp.id]) {
49360                 return;
49361             }
49362
49363             // Try to focus the parent instead
49364             var parent = cmp.up();
49365             if (parent) {
49366                 // Add component to our focus chain to detect infinite focus loop
49367                 // before we fire off an attempt to focus our parent.
49368                 // See the comments above.
49369                 chain[cmp.id] = true;
49370                 parent.focus();
49371             }
49372
49373             return;
49374         }
49375
49376         // Clear our focus chain when we have a focusable component
49377         me.focusChain = {};
49378
49379         // Defer focusing for 90ms so components can do a layout/positioning
49380         // and give us an ability to buffer focuses
49381         clearTimeout(me.cmpFocusDelay);
49382         if (arguments.length !== 2) {
49383             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
49384             return;
49385         }
49386
49387         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
49388             me.clearComponent(cmp);
49389             return;
49390         }
49391
49392         me.focusedCmp = cmp;
49393
49394         // If we have a focus frame, show it around the focused component
49395         if (me.shouldShowFocusFrame(cmp)) {
49396             var cls = '.' + me.focusFrameCls + '-',
49397                 ff = me.focusFrame,
49398                 fw = me.focusFrameWidth,
49399                 box = cmp.el.getPageBox(),
49400
49401             // Size the focus frame's t/b/l/r according to the box
49402             // This leaves a hole in the middle of the frame so user
49403             // interaction w/ the mouse can continue
49404                 bt = box.top,
49405                 bl = box.left,
49406                 bw = box.width,
49407                 bh = box.height,
49408                 ft = ff.child(cls + 'top'),
49409                 fb = ff.child(cls + 'bottom'),
49410                 fl = ff.child(cls + 'left'),
49411                 fr = ff.child(cls + 'right');
49412
49413             ft.setWidth(bw).setLeftTop(bl, bt);
49414             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
49415             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
49416             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
49417
49418             ff.show();
49419         }
49420
49421         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
49422     },
49423
49424     onComponentHide: function(cmp) {
49425         var me = this,
49426             CQ = Ext.ComponentQuery,
49427             cmpHadFocus = false,
49428             focusedCmp,
49429             parent;
49430
49431         if (me.focusedCmp) {
49432             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
49433             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
49434
49435             if (focusedCmp) {
49436                 me.clearComponent(focusedCmp);
49437             }
49438         }
49439
49440         me.clearComponent(cmp);
49441
49442         if (cmpHadFocus) {
49443             parent = CQ.query('^:focusable', cmp)[0];
49444             if (parent) {
49445                 parent.focus();
49446             }
49447         }
49448     },
49449
49450     removeDOM: function() {
49451         var me = this;
49452
49453         // If we are still enabled globally, or there are still subscribers
49454         // then we will halt here, since our DOM stuff is still being used
49455         if (me.enabled || me.subscribers.length) {
49456             return;
49457         }
49458
49459         Ext.destroy(
49460             me.focusEl,
49461             me.focusFrame
49462         );
49463         delete me.focusEl;
49464         delete me.focusFrame;
49465         delete me.focusFrameWidth;
49466     },
49467
49468     /**
49469      * Removes the specified xtype from the {@link #whitelist}.
49470      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
49471      */
49472     removeXTypeFromWhitelist: function(xtype) {
49473         var me = this;
49474
49475         if (Ext.isArray(xtype)) {
49476             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
49477             return;
49478         }
49479
49480         Ext.Array.remove(me.whitelist, xtype);
49481     },
49482
49483     setFocus: function(cmp, focusable, options) {
49484         var me = this,
49485             el, dom, data,
49486
49487             needsTabIndex = function(n) {
49488                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
49489                     && n.tabIndex <= 0;
49490             };
49491
49492         options = options || {};
49493
49494         // Come back and do this after the component is rendered
49495         if (!cmp.rendered) {
49496             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
49497             return;
49498         }
49499
49500         el = cmp.getFocusEl();
49501         dom = el.dom;
49502
49503         // Decorate the component's focus el for focus-ability
49504         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
49505             if (focusable) {
49506                 data = {
49507                     focusFrame: options.focusFrame
49508                 };
49509
49510                 // Only set -1 tabIndex if we need it
49511                 // inputs, buttons, and anchor tags do not need it,
49512                 // and neither does any DOM that has it set already
49513                 // programmatically or in markup.
49514                 if (needsTabIndex(dom)) {
49515                     data.tabIndex = dom.tabIndex;
49516                     dom.tabIndex = -1;
49517                 }
49518
49519                 el.on({
49520                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
49521                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
49522                     scope: me
49523                 });
49524                 cmp.on({
49525                     hide: me.onComponentHide,
49526                     close: me.onComponentHide,
49527                     beforedestroy: me.onComponentDestroy,
49528                     scope: me
49529                 });
49530
49531                 me.focusData[cmp.id] = data;
49532             } else {
49533                 data = me.focusData[cmp.id];
49534                 if ('tabIndex' in data) {
49535                     dom.tabIndex = data.tabIndex;
49536                 }
49537                 el.un('focus', data.focusFn, me);
49538                 el.un('blur', data.blurFn, me);
49539                 cmp.un('hide', me.onComponentHide, me);
49540                 cmp.un('close', me.onComponentHide, me);
49541                 cmp.un('beforedestroy', me.onComponentDestroy, me);
49542
49543                 delete me.focusData[cmp.id];
49544             }
49545         }
49546     },
49547
49548     setFocusAll: function(focusable, options) {
49549         var me = this,
49550             cmps = Ext.ComponentManager.all.getArray(),
49551             len = cmps.length,
49552             cmp,
49553             i = 0;
49554
49555         for (; i < len; i++) {
49556             me.setFocus(cmps[i], focusable, options);
49557         }
49558     },
49559
49560     setupSubscriberKeys: function(container, keys) {
49561         var me = this,
49562             el = container.getFocusEl(),
49563             scope = keys.scope,
49564             handlers = {
49565                 backspace: me.focusLast,
49566                 enter: me.navigateIn,
49567                 esc: me.navigateOut,
49568                 scope: me
49569             },
49570
49571             navSiblings = function(e) {
49572                 if (me.focusedCmp === container) {
49573                     // Root the sibling navigation to this container, so that we
49574                     // can automatically dive into the container, rather than forcing
49575                     // the user to hit the enter key to dive in.
49576                     return me.navigateSiblings(e, me, container);
49577                 } else {
49578                     return me.navigateSiblings(e);
49579                 }
49580             };
49581
49582         Ext.iterate(keys, function(key, cb) {
49583             handlers[key] = function(e) {
49584                 var ret = navSiblings(e);
49585
49586                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
49587                     return true;
49588                 }
49589
49590                 return ret;
49591             };
49592         }, me);
49593
49594         return Ext.create('Ext.util.KeyNav', el, handlers);
49595     },
49596
49597     shouldShowFocusFrame: function(cmp) {
49598         var me = this,
49599             opts = me.options || {};
49600
49601         if (!me.focusFrame || !cmp) {
49602             return false;
49603         }
49604
49605         // Global trumps
49606         if (opts.focusFrame) {
49607             return true;
49608         }
49609
49610         if (me.focusData[cmp.id].focusFrame) {
49611             return true;
49612         }
49613
49614         return false;
49615     },
49616
49617     /**
49618      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
49619      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
49620      * @param {Boolean/Object} options An object of the following options
49621      * @param {Array/Object} options.keys
49622      * An array containing the string names of navigation keys to be supported. The allowed values are:
49623      *
49624      *   - 'left'
49625      *   - 'right'
49626      *   - 'up'
49627      *   - 'down'
49628      *
49629      * Or, an object containing those key names as keys with `true` or a callback function as their value. A scope may also be passed. E.g.:
49630      *
49631      *     {
49632      *         left: this.onLeftKey,
49633      *         right: this.onRightKey,
49634      *         scope: this
49635      *     }
49636      *
49637      * @param {Boolean} options.focusFrame
49638      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49639      */
49640     subscribe: function(container, options) {
49641         var me = this,
49642             EA = Ext.Array,
49643             data = {},
49644             subs = me.subscribers,
49645
49646             // Recursively add focus ability as long as a descendent container isn't
49647             // itself subscribed to the FocusManager, or else we'd have unwanted side
49648             // effects for subscribing a descendent container twice.
49649             safeSetFocus = function(cmp) {
49650                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49651                     EA.forEach(cmp.query('>'), safeSetFocus);
49652                     me.setFocus(cmp, true, options);
49653                     cmp.on('add', data.onAdd, me);
49654                 } else if (!cmp.isContainer) {
49655                     me.setFocus(cmp, true, options);
49656                 }
49657             };
49658
49659         // We only accept containers
49660         if (!container || !container.isContainer) {
49661             return;
49662         }
49663
49664         if (!container.rendered) {
49665             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
49666             return;
49667         }
49668
49669         // Init the DOM, incase this is the first time it will be used
49670         me.initDOM(options);
49671
49672         // Create key navigation for subscriber based on keys option
49673         data.keyNav = me.setupSubscriberKeys(container, options.keys);
49674
49675         // We need to keep track of components being added to our subscriber
49676         // and any containers nested deeply within it (omg), so let's do that.
49677         // Components that are removed are globally handled.
49678         // Also keep track of destruction of our container for auto-unsubscribe.
49679         data.onAdd = function(ct, cmp, idx) {
49680             safeSetFocus(cmp);
49681         };
49682         container.on('beforedestroy', me.unsubscribe, me);
49683
49684         // Now we setup focusing abilities for the container and all its components
49685         safeSetFocus(container);
49686
49687         // Add to our subscribers list
49688         subs.add(container.id, data);
49689     },
49690
49691     /**
49692      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
49693      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
49694      */
49695     unsubscribe: function(container) {
49696         var me = this,
49697             EA = Ext.Array,
49698             subs = me.subscribers,
49699             data,
49700
49701             // Recursively remove focus ability as long as a descendent container isn't
49702             // itself subscribed to the FocusManager, or else we'd have unwanted side
49703             // effects for unsubscribing an ancestor container.
49704             safeSetFocus = function(cmp) {
49705                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49706                     EA.forEach(cmp.query('>'), safeSetFocus);
49707                     me.setFocus(cmp, false);
49708                     cmp.un('add', data.onAdd, me);
49709                 } else if (!cmp.isContainer) {
49710                     me.setFocus(cmp, false);
49711                 }
49712             };
49713
49714         if (!container || !subs.containsKey(container.id)) {
49715             return;
49716         }
49717
49718         data = subs.get(container.id);
49719         data.keyNav.destroy();
49720         container.un('beforedestroy', me.unsubscribe, me);
49721         subs.removeAtKey(container.id);
49722         safeSetFocus(container);
49723         me.removeDOM();
49724     }
49725 });
49726 /**
49727  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
49728  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
49729  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
49730  *
49731  * ## Some items have shortcut strings for creation:
49732  *
49733  * | Shortcut | xtype         | Class                         | Description
49734  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
49735  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
49736  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
49737  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
49738  *
49739  *     @example
49740  *     Ext.create('Ext.toolbar.Toolbar', {
49741  *         renderTo: document.body,
49742  *         width   : 500,
49743  *         items: [
49744  *             {
49745  *                 // xtype: 'button', // default for Toolbars
49746  *                 text: 'Button'
49747  *             },
49748  *             {
49749  *                 xtype: 'splitbutton',
49750  *                 text : 'Split Button'
49751  *             },
49752  *             // begin using the right-justified button container
49753  *             '->', // same as { xtype: 'tbfill' }
49754  *             {
49755  *                 xtype    : 'textfield',
49756  *                 name     : 'field1',
49757  *                 emptyText: 'enter search term'
49758  *             },
49759  *             // add a vertical separator bar between toolbar items
49760  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
49761  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
49762  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
49763  *             'text 2',
49764  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
49765  *             'text 3'
49766  *         ]
49767  *     });
49768  *
49769  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
49770  *
49771  *     @example
49772  *     Ext.create('Ext.toolbar.Toolbar', {
49773  *         renderTo: document.body,
49774  *         width   : 400,
49775  *         items: [
49776  *             {
49777  *                 text: 'Button'
49778  *             },
49779  *             {
49780  *                 xtype: 'splitbutton',
49781  *                 text : 'Split Button'
49782  *             },
49783  *             '->',
49784  *             {
49785  *                 xtype    : 'textfield',
49786  *                 name     : 'field1',
49787  *                 emptyText: 'enter search term'
49788  *             }
49789  *         ]
49790  *     });
49791  *
49792  * Example
49793  *
49794  *     @example
49795  *     var enableBtn = Ext.create('Ext.button.Button', {
49796  *         text    : 'Enable All Items',
49797  *         disabled: true,
49798  *         scope   : this,
49799  *         handler : function() {
49800  *             //disable the enable button and enable the disable button
49801  *             enableBtn.disable();
49802  *             disableBtn.enable();
49803  *
49804  *             //enable the toolbar
49805  *             toolbar.enable();
49806  *         }
49807  *     });
49808  *
49809  *     var disableBtn = Ext.create('Ext.button.Button', {
49810  *         text    : 'Disable All Items',
49811  *         scope   : this,
49812  *         handler : function() {
49813  *             //enable the enable button and disable button
49814  *             disableBtn.disable();
49815  *             enableBtn.enable();
49816  *
49817  *             //disable the toolbar
49818  *             toolbar.disable();
49819  *         }
49820  *     });
49821  *
49822  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49823  *         renderTo: document.body,
49824  *         width   : 400,
49825  *         margin  : '5 0 0 0',
49826  *         items   : [enableBtn, disableBtn]
49827  *     });
49828  *
49829  * Adding items to and removing items from a toolbar is as simple as calling the {@link #add} and {@link #remove} methods. There is also a {@link #removeAll} method
49830  * which remove all items within the toolbar.
49831  *
49832  *     @example
49833  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49834  *         renderTo: document.body,
49835  *         width   : 700,
49836  *         items: [
49837  *             {
49838  *                 text: 'Example Button'
49839  *             }
49840  *         ]
49841  *     });
49842  *
49843  *     var addedItems = [];
49844  *
49845  *     Ext.create('Ext.toolbar.Toolbar', {
49846  *         renderTo: document.body,
49847  *         width   : 700,
49848  *         margin  : '5 0 0 0',
49849  *         items   : [
49850  *             {
49851  *                 text   : 'Add a button',
49852  *                 scope  : this,
49853  *                 handler: function() {
49854  *                     var text = prompt('Please enter the text for your button:');
49855  *                     addedItems.push(toolbar.add({
49856  *                         text: text
49857  *                     }));
49858  *                 }
49859  *             },
49860  *             {
49861  *                 text   : 'Add a text item',
49862  *                 scope  : this,
49863  *                 handler: function() {
49864  *                     var text = prompt('Please enter the text for your item:');
49865  *                     addedItems.push(toolbar.add(text));
49866  *                 }
49867  *             },
49868  *             {
49869  *                 text   : 'Add a toolbar seperator',
49870  *                 scope  : this,
49871  *                 handler: function() {
49872  *                     addedItems.push(toolbar.add('-'));
49873  *                 }
49874  *             },
49875  *             {
49876  *                 text   : 'Add a toolbar spacer',
49877  *                 scope  : this,
49878  *                 handler: function() {
49879  *                     addedItems.push(toolbar.add('->'));
49880  *                 }
49881  *             },
49882  *             '->',
49883  *             {
49884  *                 text   : 'Remove last inserted item',
49885  *                 scope  : this,
49886  *                 handler: function() {
49887  *                     if (addedItems.length) {
49888  *                         toolbar.remove(addedItems.pop());
49889  *                     } else if (toolbar.items.length) {
49890  *                         toolbar.remove(toolbar.items.last());
49891  *                     } else {
49892  *                         alert('No items in the toolbar');
49893  *                     }
49894  *                 }
49895  *             },
49896  *             {
49897  *                 text   : 'Remove all items',
49898  *                 scope  : this,
49899  *                 handler: function() {
49900  *                     toolbar.removeAll();
49901  *                 }
49902  *             }
49903  *         ]
49904  *     });
49905  *
49906  * @constructor
49907  * Creates a new Toolbar
49908  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
49909  * @docauthor Robert Dougan <rob@sencha.com>
49910  */
49911 Ext.define('Ext.toolbar.Toolbar', {
49912     extend: 'Ext.container.Container',
49913     requires: [
49914         'Ext.toolbar.Fill',
49915         'Ext.layout.container.HBox',
49916         'Ext.layout.container.VBox',
49917         'Ext.FocusManager'
49918     ],
49919     uses: [
49920         'Ext.toolbar.Separator'
49921     ],
49922     alias: 'widget.toolbar',
49923     alternateClassName: 'Ext.Toolbar',
49924
49925     isToolbar: true,
49926     baseCls  : Ext.baseCSSPrefix + 'toolbar',
49927     ariaRole : 'toolbar',
49928
49929     defaultType: 'button',
49930
49931     /**
49932      * @cfg {Boolean} vertical
49933      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
49934      */
49935     vertical: false,
49936
49937     /**
49938      * @cfg {String/Object} layout
49939      * This class assigns a default layout (`layout: 'hbox'`).
49940      * Developers _may_ override this configuration option if another layout
49941      * is required (the constructor must be passed a configuration object in this
49942      * case instead of an array).
49943      * See {@link Ext.container.Container#layout} for additional information.
49944      */
49945
49946     /**
49947      * @cfg {Boolean} enableOverflow
49948      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
49949      * items which overflow the Toolbar's width.
49950      */
49951     enableOverflow: false,
49952
49953     /**
49954      * @cfg {String} menuTriggerCls
49955      * Configure the icon class of the overflow button.
49956      */
49957     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
49958     
49959     // private
49960     trackMenus: true,
49961
49962     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
49963
49964     initComponent: function() {
49965         var me = this,
49966             keys;
49967
49968         // check for simplified (old-style) overflow config:
49969         if (!me.layout && me.enableOverflow) {
49970             me.layout = { overflowHandler: 'Menu' };
49971         }
49972
49973         if (me.dock === 'right' || me.dock === 'left') {
49974             me.vertical = true;
49975         }
49976
49977         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
49978             type: me.layout
49979         } : me.layout || {}, {
49980             type: me.vertical ? 'vbox' : 'hbox',
49981             align: me.vertical ? 'stretchmax' : 'middle',
49982             clearInnerCtOnLayout: true
49983         });
49984
49985         if (me.vertical) {
49986             me.addClsWithUI('vertical');
49987         }
49988
49989         // @TODO: remove this hack and implement a more general solution
49990         if (me.ui === 'footer') {
49991             me.ignoreBorderManagement = true;
49992         }
49993
49994         me.callParent();
49995
49996         /**
49997          * @event overflowchange
49998          * Fires after the overflow state has changed.
49999          * @param {Object} c The Container
50000          * @param {Boolean} lastOverflow overflow state
50001          */
50002         me.addEvents('overflowchange');
50003
50004         // Subscribe to Ext.FocusManager for key navigation
50005         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
50006         Ext.FocusManager.subscribe(me, {
50007             keys: keys
50008         });
50009     },
50010
50011     getRefItems: function(deep) {
50012         var me = this,
50013             items = me.callParent(arguments),
50014             layout = me.layout,
50015             handler;
50016
50017         if (deep && me.enableOverflow) {
50018             handler = layout.overflowHandler;
50019             if (handler && handler.menu) {
50020                 items = items.concat(handler.menu.getRefItems(deep));
50021             }
50022         }
50023         return items;
50024     },
50025
50026     /**
50027      * Adds element(s) to the toolbar -- this function takes a variable number of
50028      * arguments of mixed type and adds them to the toolbar.
50029      *
50030      * **Note**: See the notes within {@link Ext.container.Container#add}.
50031      *
50032      * @param {Object...} args The following types of arguments are all valid:
50033      *  - `{@link Ext.button.Button config}`: A valid button config object
50034      *  - `HtmlElement`: Any standard HTML element
50035      *  - `Field`: Any form field
50036      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
50037      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
50038      *  Note that there are a few special strings that are treated differently as explained next.
50039      *  - `'-'`: Creates a separator element
50040      *  - `' '`: Creates a spacer element
50041      *  - `'->'`: Creates a fill element
50042      *
50043      * @method add
50044      */
50045
50046     // private
50047     lookupComponent: function(c) {
50048         if (Ext.isString(c)) {
50049             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
50050             if (shortcut) {
50051                 c = {
50052                     xtype: shortcut
50053                 };
50054             } else {
50055                 c = {
50056                     xtype: 'tbtext',
50057                     text: c
50058                 };
50059             }
50060             this.applyDefaults(c);
50061         }
50062         return this.callParent(arguments);
50063     },
50064
50065     // private
50066     applyDefaults: function(c) {
50067         if (!Ext.isString(c)) {
50068             c = this.callParent(arguments);
50069             var d = this.internalDefaults;
50070             if (c.events) {
50071                 Ext.applyIf(c.initialConfig, d);
50072                 Ext.apply(c, d);
50073             } else {
50074                 Ext.applyIf(c, d);
50075             }
50076         }
50077         return c;
50078     },
50079
50080     // private
50081     trackMenu: function(item, remove) {
50082         if (this.trackMenus && item.menu) {
50083             var method = remove ? 'mun' : 'mon',
50084                 me = this;
50085
50086             me[method](item, 'mouseover', me.onButtonOver, me);
50087             me[method](item, 'menushow', me.onButtonMenuShow, me);
50088             me[method](item, 'menuhide', me.onButtonMenuHide, me);
50089         }
50090     },
50091
50092     // private
50093     constructButton: function(item) {
50094         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
50095     },
50096
50097     // private
50098     onBeforeAdd: function(component) {
50099         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
50100             component.ui = component.ui + '-toolbar';
50101         }
50102
50103         // Any separators needs to know if is vertical or not
50104         if (component instanceof Ext.toolbar.Separator) {
50105             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
50106         }
50107
50108         this.callParent(arguments);
50109     },
50110
50111     // private
50112     onAdd: function(component) {
50113         this.callParent(arguments);
50114
50115         this.trackMenu(component);
50116         if (this.disabled) {
50117             component.disable();
50118         }
50119     },
50120
50121     // private
50122     onRemove: function(c) {
50123         this.callParent(arguments);
50124         this.trackMenu(c, true);
50125     },
50126
50127     // private
50128     onButtonOver: function(btn){
50129         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
50130             this.activeMenuBtn.hideMenu();
50131             btn.showMenu();
50132             this.activeMenuBtn = btn;
50133         }
50134     },
50135
50136     // private
50137     onButtonMenuShow: function(btn) {
50138         this.activeMenuBtn = btn;
50139     },
50140
50141     // private
50142     onButtonMenuHide: function(btn) {
50143         delete this.activeMenuBtn;
50144     }
50145 }, function() {
50146     this.shortcuts = {
50147         '-' : 'tbseparator',
50148         ' ' : 'tbspacer',
50149         '->': 'tbfill'
50150     };
50151 });
50152 /**
50153  * @class Ext.panel.AbstractPanel
50154  * @extends Ext.container.Container
50155  * A base class which provides methods common to Panel classes across the Sencha product range.
50156  * @private
50157  */
50158 Ext.define('Ext.panel.AbstractPanel', {
50159
50160     /* Begin Definitions */
50161
50162     extend: 'Ext.container.Container',
50163
50164     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
50165
50166     /* End Definitions */
50167
50168     /**
50169      * @cfg {String} [baseCls='x-panel']
50170      * The base CSS class to apply to this panel's element.
50171      */
50172     baseCls : Ext.baseCSSPrefix + 'panel',
50173
50174     /**
50175      * @cfg {Number/String} bodyPadding
50176      * A shortcut for setting a padding style on the body element. The value can either be
50177      * a number to be applied to all sides, or a normal css string describing padding.
50178      */
50179
50180     /**
50181      * @cfg {Boolean} bodyBorder
50182      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
50183      * which has the {@link #frame} configuration set to `true`.
50184      */
50185
50186     /**
50187      * @cfg {String/Object/Function} bodyStyle
50188      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
50189      * an object containing style property name/value pairs or a function that returns such a string or object.
50190      * For example, these two formats are interpreted to be equivalent:<pre><code>
50191 bodyStyle: 'background:#ffc; padding:10px;'
50192
50193 bodyStyle: {
50194     background: '#ffc',
50195     padding: '10px'
50196 }
50197      * </code></pre>
50198      */
50199
50200     /**
50201      * @cfg {String/String[]} bodyCls
50202      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
50203      * The following examples are all valid:<pre><code>
50204 bodyCls: 'foo'
50205 bodyCls: 'foo bar'
50206 bodyCls: ['foo', 'bar']
50207      * </code></pre>
50208      */
50209
50210     isPanel: true,
50211
50212     componentLayout: 'dock',
50213
50214     /**
50215      * @cfg {Object} defaultDockWeights
50216      * This object holds the default weights applied to dockedItems that have no weight. These start with a
50217      * weight of 1, to allow negative weights to insert before top items and are odd numbers
50218      * so that even weights can be used to get between different dock orders.
50219      *
50220      * To make default docking order match border layout, do this:
50221      * <pre><code>
50222 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
50223      * Changing these defaults as above or individually on this object will effect all Panels.
50224      * To change the defaults on a single panel, you should replace the entire object:
50225      * <pre><code>
50226 initComponent: function () {
50227     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50228     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
50229
50230     this.callParent();
50231 }</code></pre>
50232      *
50233      * To change only one of the default values, you do this:
50234      * <pre><code>
50235 initComponent: function () {
50236     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50237     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
50238
50239     this.callParent();
50240 }</code></pre>
50241      */
50242     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
50243
50244     renderTpl: [
50245         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50246             ' {baseCls}-body-{ui}<tpl if="uiCls">',
50247                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50248             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
50249         '</div>'
50250     ],
50251
50252     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
50253     /**
50254      * @cfg {Object/Object[]} dockedItems
50255      * A component or series of components to be added as docked items to this panel.
50256      * The docked items can be docked to either the top, right, left or bottom of a panel.
50257      * This is typically used for things like toolbars or tab bars:
50258      * <pre><code>
50259 var panel = new Ext.panel.Panel({
50260     fullscreen: true,
50261     dockedItems: [{
50262         xtype: 'toolbar',
50263         dock: 'top',
50264         items: [{
50265             text: 'Docked to the top'
50266         }]
50267     }]
50268 });</code></pre>
50269      */
50270
50271     border: true,
50272
50273     initComponent : function() {
50274         var me = this;
50275
50276         me.addEvents(
50277             /**
50278              * @event bodyresize
50279              * Fires after the Panel has been resized.
50280              * @param {Ext.panel.Panel} p the Panel which has been resized.
50281              * @param {Number} width The Panel body's new width.
50282              * @param {Number} height The Panel body's new height.
50283              */
50284             'bodyresize'
50285             // // inherited
50286             // 'activate',
50287             // // inherited
50288             // 'deactivate'
50289         );
50290
50291         me.addChildEls('body');
50292
50293         //!frame
50294         //!border
50295
50296         if (me.frame && me.border && me.bodyBorder === undefined) {
50297             me.bodyBorder = false;
50298         }
50299         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
50300             me.manageBodyBorders = true;
50301         }
50302
50303         me.callParent();
50304     },
50305
50306     // @private
50307     initItems : function() {
50308         var me = this,
50309             items = me.dockedItems;
50310
50311         me.callParent();
50312         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
50313         if (items) {
50314             me.addDocked(items);
50315         }
50316     },
50317
50318     /**
50319      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
50320      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
50321      * @return {Ext.Component} The docked component (if found)
50322      */
50323     getDockedComponent: function(comp) {
50324         if (Ext.isObject(comp)) {
50325             comp = comp.getItemId();
50326         }
50327         return this.dockedItems.get(comp);
50328     },
50329
50330     /**
50331      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
50332      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
50333      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
50334      * @param {String/Number} comp The component id, itemId or position to find
50335      * @return {Ext.Component} The component (if found)
50336      */
50337     getComponent: function(comp) {
50338         var component = this.callParent(arguments);
50339         if (component === undefined && !Ext.isNumber(comp)) {
50340             // If the arg is a numeric index skip docked items
50341             component = this.getDockedComponent(comp);
50342         }
50343         return component;
50344     },
50345
50346     /**
50347      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
50348      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
50349      * @return {String} A CSS style string with body styles, padding and border.
50350      * @private
50351      */
50352     initBodyStyles: function() {
50353         var me = this,
50354             bodyStyle = me.bodyStyle,
50355             styles = [],
50356             Element = Ext.Element,
50357             prop;
50358
50359         if (Ext.isFunction(bodyStyle)) {
50360             bodyStyle = bodyStyle();
50361         }
50362         if (Ext.isString(bodyStyle)) {
50363             styles = bodyStyle.split(';');
50364         } else {
50365             for (prop in bodyStyle) {
50366                 if (bodyStyle.hasOwnProperty(prop)) {
50367                     styles.push(prop + ':' + bodyStyle[prop]);
50368                 }
50369             }
50370         }
50371
50372         if (me.bodyPadding !== undefined) {
50373             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
50374         }
50375         if (me.frame && me.bodyBorder) {
50376             if (!Ext.isNumber(me.bodyBorder)) {
50377                 me.bodyBorder = 1;
50378             }
50379             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
50380         }
50381         delete me.bodyStyle;
50382         return styles.length ? styles.join(';') : undefined;
50383     },
50384
50385     /**
50386      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
50387      * CSS classes to be applied to the body element.
50388      * @return {String} The CSS class(es)
50389      * @private
50390      */
50391     initBodyCls: function() {
50392         var me = this,
50393             cls = '',
50394             bodyCls = me.bodyCls;
50395
50396         if (bodyCls) {
50397             Ext.each(bodyCls, function(v) {
50398                 cls += " " + v;
50399             });
50400             delete me.bodyCls;
50401         }
50402         return cls.length > 0 ? cls : undefined;
50403     },
50404
50405     /**
50406      * Initialized the renderData to be used when rendering the renderTpl.
50407      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
50408      * @private
50409      */
50410     initRenderData: function() {
50411         return Ext.applyIf(this.callParent(), {
50412             bodyStyle: this.initBodyStyles(),
50413             bodyCls: this.initBodyCls()
50414         });
50415     },
50416
50417     /**
50418      * Adds docked item(s) to the panel.
50419      * @param {Object/Object[]} component The Component or array of components to add. The components
50420      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
50421      * 'bottom', 'left').
50422      * @param {Number} pos (optional) The index at which the Component will be added
50423      */
50424     addDocked : function(items, pos) {
50425         var me = this,
50426             i = 0,
50427             item, length;
50428
50429         items = me.prepareItems(items);
50430         length = items.length;
50431
50432         for (; i < length; i++) {
50433             item = items[i];
50434             item.dock = item.dock || 'top';
50435
50436             // Allow older browsers to target docked items to style without borders
50437             if (me.border === false) {
50438                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
50439             }
50440
50441             if (pos !== undefined) {
50442                 me.dockedItems.insert(pos + i, item);
50443             }
50444             else {
50445                 me.dockedItems.add(item);
50446             }
50447             item.onAdded(me, i);
50448             me.onDockedAdd(item);
50449         }
50450
50451         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50452         me.componentLayout.childrenChanged = true;
50453         if (me.rendered && !me.suspendLayout) {
50454             me.doComponentLayout();
50455         }
50456         return items;
50457     },
50458
50459     // Placeholder empty functions
50460     onDockedAdd : Ext.emptyFn,
50461     onDockedRemove : Ext.emptyFn,
50462
50463     /**
50464      * Inserts docked item(s) to the panel at the indicated position.
50465      * @param {Number} pos The index at which the Component will be inserted
50466      * @param {Object/Object[]} component. The Component or array of components to add. The components
50467      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
50468      * 'bottom', 'left').
50469      */
50470     insertDocked : function(pos, items) {
50471         this.addDocked(items, pos);
50472     },
50473
50474     /**
50475      * Removes the docked item from the panel.
50476      * @param {Ext.Component} item. The Component to remove.
50477      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
50478      */
50479     removeDocked : function(item, autoDestroy) {
50480         var me = this,
50481             layout,
50482             hasLayout;
50483
50484         if (!me.dockedItems.contains(item)) {
50485             return item;
50486         }
50487
50488         layout = me.componentLayout;
50489         hasLayout = layout && me.rendered;
50490
50491         if (hasLayout) {
50492             layout.onRemove(item);
50493         }
50494
50495         me.dockedItems.remove(item);
50496         item.onRemoved();
50497         me.onDockedRemove(item);
50498
50499         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
50500             item.destroy();
50501         } else if (hasLayout) {
50502             // not destroying, make any layout related removals
50503             layout.afterRemove(item);    
50504         }
50505
50506
50507         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50508         me.componentLayout.childrenChanged = true;
50509         if (!me.destroying && !me.suspendLayout) {
50510             me.doComponentLayout();
50511         }
50512
50513         return item;
50514     },
50515
50516     /**
50517      * Retrieve an array of all currently docked Components.
50518      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
50519      * @return {Ext.Component[]} An array of components.
50520      */
50521     getDockedItems : function(cqSelector) {
50522         var me = this,
50523             defaultWeight = me.defaultDockWeights,
50524             dockedItems;
50525
50526         if (me.dockedItems && me.dockedItems.items.length) {
50527             // Allow filtering of returned docked items by CQ selector.
50528             if (cqSelector) {
50529                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
50530             } else {
50531                 dockedItems = me.dockedItems.items.slice();
50532             }
50533
50534             Ext.Array.sort(dockedItems, function(a, b) {
50535                 // Docked items are ordered by their visual representation by default (t,l,r,b)
50536                 var aw = a.weight || defaultWeight[a.dock],
50537                     bw = b.weight || defaultWeight[b.dock];
50538                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
50539                     return aw - bw;
50540                 }
50541                 return 0;
50542             });
50543
50544             return dockedItems;
50545         }
50546         return [];
50547     },
50548
50549     // inherit docs
50550     addUIClsToElement: function(cls, force) {
50551         var me = this,
50552             result = me.callParent(arguments),
50553             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50554             array, i;
50555
50556         if (!force && me.rendered) {
50557             if (me.bodyCls) {
50558                 me.body.addCls(me.bodyCls);
50559             } else {
50560                 me.body.addCls(classes);
50561             }
50562         } else {
50563             if (me.bodyCls) {
50564                 array = me.bodyCls.split(' ');
50565
50566                 for (i = 0; i < classes.length; i++) {
50567                     if (!Ext.Array.contains(array, classes[i])) {
50568                         array.push(classes[i]);
50569                     }
50570                 }
50571
50572                 me.bodyCls = array.join(' ');
50573             } else {
50574                 me.bodyCls = classes.join(' ');
50575             }
50576         }
50577
50578         return result;
50579     },
50580
50581     // inherit docs
50582     removeUIClsFromElement: function(cls, force) {
50583         var me = this,
50584             result = me.callParent(arguments),
50585             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50586             array, i;
50587
50588         if (!force && me.rendered) {
50589             if (me.bodyCls) {
50590                 me.body.removeCls(me.bodyCls);
50591             } else {
50592                 me.body.removeCls(classes);
50593             }
50594         } else {
50595             if (me.bodyCls) {
50596                 array = me.bodyCls.split(' ');
50597
50598                 for (i = 0; i < classes.length; i++) {
50599                     Ext.Array.remove(array, classes[i]);
50600                 }
50601
50602                 me.bodyCls = array.join(' ');
50603             }
50604         }
50605
50606         return result;
50607     },
50608
50609     // inherit docs
50610     addUIToElement: function(force) {
50611         var me = this,
50612             cls = me.baseCls + '-body-' + me.ui,
50613             array;
50614
50615         me.callParent(arguments);
50616
50617         if (!force && me.rendered) {
50618             if (me.bodyCls) {
50619                 me.body.addCls(me.bodyCls);
50620             } else {
50621                 me.body.addCls(cls);
50622             }
50623         } else {
50624             if (me.bodyCls) {
50625                 array = me.bodyCls.split(' ');
50626
50627                 if (!Ext.Array.contains(array, cls)) {
50628                     array.push(cls);
50629                 }
50630
50631                 me.bodyCls = array.join(' ');
50632             } else {
50633                 me.bodyCls = cls;
50634             }
50635         }
50636     },
50637
50638     // inherit docs
50639     removeUIFromElement: function() {
50640         var me = this,
50641             cls = me.baseCls + '-body-' + me.ui,
50642             array;
50643
50644         me.callParent(arguments);
50645
50646         if (me.rendered) {
50647             if (me.bodyCls) {
50648                 me.body.removeCls(me.bodyCls);
50649             } else {
50650                 me.body.removeCls(cls);
50651             }
50652         } else {
50653             if (me.bodyCls) {
50654                 array = me.bodyCls.split(' ');
50655                 Ext.Array.remove(array, cls);
50656                 me.bodyCls = array.join(' ');
50657             } else {
50658                 me.bodyCls = cls;
50659             }
50660         }
50661     },
50662
50663     // @private
50664     getTargetEl : function() {
50665         return this.body;
50666     },
50667
50668     getRefItems: function(deep) {
50669         var items = this.callParent(arguments),
50670             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
50671             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
50672             ln = dockedItems.length,
50673             i = 0,
50674             item;
50675
50676         // Find the index where we go from top/left docked items to right/bottom docked items
50677         for (; i < ln; i++) {
50678             item = dockedItems[i];
50679             if (item.dock === 'right' || item.dock === 'bottom') {
50680                 break;
50681             }
50682         }
50683
50684         // Return docked items in the top/left position before our container items, and
50685         // return right/bottom positioned items after our container items.
50686         // See AbstractDock.renderItems() for more information.
50687         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
50688     },
50689
50690     beforeDestroy: function(){
50691         var docked = this.dockedItems,
50692             c;
50693
50694         if (docked) {
50695             while ((c = docked.first())) {
50696                 this.removeDocked(c, true);
50697             }
50698         }
50699         this.callParent();
50700     },
50701
50702     setBorder: function(border) {
50703         var me = this;
50704         me.border = (border !== undefined) ? border : true;
50705         if (me.rendered) {
50706             me.doComponentLayout();
50707         }
50708     }
50709 });
50710 /**
50711  * @class Ext.panel.Header
50712  * @extends Ext.container.Container
50713  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
50714  */
50715 Ext.define('Ext.panel.Header', {
50716     extend: 'Ext.container.Container',
50717     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
50718     alias: 'widget.header',
50719
50720     isHeader       : true,
50721     defaultType    : 'tool',
50722     indicateDrag   : false,
50723     weight         : -1,
50724
50725     renderTpl: [
50726         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50727         '<tpl if="uiCls">',
50728             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50729         '</tpl>"',
50730         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
50731
50732     /**
50733      * @cfg {String} title
50734      * The title text to display
50735      */
50736
50737     /**
50738      * @cfg {String} iconCls
50739      * CSS class for icon in header. Used for displaying an icon to the left of a title.
50740      */
50741
50742     initComponent: function() {
50743         var me = this,
50744             ruleStyle,
50745             rule,
50746             style,
50747             titleTextEl,
50748             ui;
50749
50750         me.indicateDragCls = me.baseCls + '-draggable';
50751         me.title = me.title || '&#160;';
50752         me.tools = me.tools || [];
50753         me.items = me.items || [];
50754         me.orientation = me.orientation || 'horizontal';
50755         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
50756
50757         //add the dock as a ui
50758         //this is so we support top/right/left/bottom headers
50759         me.addClsWithUI(me.orientation);
50760         me.addClsWithUI(me.dock);
50761
50762         me.addChildEls('body');
50763
50764         // Add Icon
50765         if (!Ext.isEmpty(me.iconCls)) {
50766             me.initIconCmp();
50767             me.items.push(me.iconCmp);
50768         }
50769
50770         // Add Title
50771         if (me.orientation == 'vertical') {
50772             // Hack for IE6/7's inability to display an inline-block
50773             if (Ext.isIE6 || Ext.isIE7) {
50774                 me.width = this.width || 24;
50775             } else if (Ext.isIEQuirks) {
50776                 me.width = this.width || 25;
50777             }
50778
50779             me.layout = {
50780                 type : 'vbox',
50781                 align: 'center',
50782                 clearInnerCtOnLayout: true,
50783                 bindToOwnerCtContainer: false
50784             };
50785             me.textConfig = {
50786                 cls: me.baseCls + '-text',
50787                 type: 'text',
50788                 text: me.title,
50789                 rotate: {
50790                     degrees: 90
50791                 }
50792             };
50793             ui = me.ui;
50794             if (Ext.isArray(ui)) {
50795                 ui = ui[0];
50796             }
50797             ruleStyle = '.' + me.baseCls + '-text-' + ui;
50798             if (Ext.scopeResetCSS) {
50799                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
50800             }
50801             rule = Ext.util.CSS.getRule(ruleStyle);
50802             if (rule) {
50803                 style = rule.style;
50804             }
50805             if (style) {
50806                 Ext.apply(me.textConfig, {
50807                     'font-family': style.fontFamily,
50808                     'font-weight': style.fontWeight,
50809                     'font-size': style.fontSize,
50810                     fill: style.color
50811                 });
50812             }
50813             me.titleCmp = Ext.create('Ext.draw.Component', {
50814                 ariaRole  : 'heading',
50815                 focusable: false,
50816                 viewBox: false,
50817                 flex : 1,
50818                 autoSize: true,
50819                 margins: '5 0 0 0',
50820                 items: [ me.textConfig ],
50821                 // this is a bit of a cheat: we are not selecting an element of titleCmp
50822                 // but rather of titleCmp.items[0] (so we cannot use childEls)
50823                 renderSelectors: {
50824                     textEl: '.' + me.baseCls + '-text'
50825                 }
50826             });
50827         } else {
50828             me.layout = {
50829                 type : 'hbox',
50830                 align: 'middle',
50831                 clearInnerCtOnLayout: true,
50832                 bindToOwnerCtContainer: false
50833             };
50834             me.titleCmp = Ext.create('Ext.Component', {
50835                 xtype     : 'component',
50836                 ariaRole  : 'heading',
50837                 focusable: false,
50838                 flex : 1,
50839                 cls: me.baseCls + '-text-container',
50840                 renderTpl : [
50841                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
50842                 ],
50843                 renderData: {
50844                     title: me.title,
50845                     cls  : me.baseCls,
50846                     ui   : me.ui
50847                 },
50848                 childEls: ['textEl']
50849             });
50850         }
50851         me.items.push(me.titleCmp);
50852
50853         // Add Tools
50854         me.items = me.items.concat(me.tools);
50855         this.callParent();
50856     },
50857
50858     initIconCmp: function() {
50859         this.iconCmp = Ext.create('Ext.Component', {
50860             focusable: false,
50861             renderTpl : [
50862                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
50863             ],
50864             renderData: {
50865                 blank  : Ext.BLANK_IMAGE_URL,
50866                 cls    : this.baseCls,
50867                 iconCls: this.iconCls,
50868                 orientation: this.orientation
50869             },
50870             childEls: ['iconEl'],
50871             iconCls: this.iconCls
50872         });
50873     },
50874
50875     afterRender: function() {
50876         var me = this;
50877
50878         me.el.unselectable();
50879         if (me.indicateDrag) {
50880             me.el.addCls(me.indicateDragCls);
50881         }
50882         me.mon(me.el, {
50883             click: me.onClick,
50884             scope: me
50885         });
50886         me.callParent();
50887     },
50888
50889     afterLayout: function() {
50890         var me = this;
50891         me.callParent(arguments);
50892
50893         // IE7 needs a forced repaint to make the top framing div expand to full width
50894         if (Ext.isIE7) {
50895             me.el.repaint();
50896         }
50897     },
50898
50899     // inherit docs
50900     addUIClsToElement: function(cls, force) {
50901         var me = this,
50902             result = me.callParent(arguments),
50903             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50904             array, i;
50905
50906         if (!force && me.rendered) {
50907             if (me.bodyCls) {
50908                 me.body.addCls(me.bodyCls);
50909             } else {
50910                 me.body.addCls(classes);
50911             }
50912         } else {
50913             if (me.bodyCls) {
50914                 array = me.bodyCls.split(' ');
50915
50916                 for (i = 0; i < classes.length; i++) {
50917                     if (!Ext.Array.contains(array, classes[i])) {
50918                         array.push(classes[i]);
50919                     }
50920                 }
50921
50922                 me.bodyCls = array.join(' ');
50923             } else {
50924                 me.bodyCls = classes.join(' ');
50925             }
50926         }
50927
50928         return result;
50929     },
50930
50931     // inherit docs
50932     removeUIClsFromElement: function(cls, force) {
50933         var me = this,
50934             result = me.callParent(arguments),
50935             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50936             array, i;
50937
50938         if (!force && me.rendered) {
50939             if (me.bodyCls) {
50940                 me.body.removeCls(me.bodyCls);
50941             } else {
50942                 me.body.removeCls(classes);
50943             }
50944         } else {
50945             if (me.bodyCls) {
50946                 array = me.bodyCls.split(' ');
50947
50948                 for (i = 0; i < classes.length; i++) {
50949                     Ext.Array.remove(array, classes[i]);
50950                 }
50951
50952                 me.bodyCls = array.join(' ');
50953             }
50954         }
50955
50956        return result;
50957     },
50958
50959     // inherit docs
50960     addUIToElement: function(force) {
50961         var me = this,
50962             array, cls;
50963
50964         me.callParent(arguments);
50965
50966         cls = me.baseCls + '-body-' + me.ui;
50967         if (!force && me.rendered) {
50968             if (me.bodyCls) {
50969                 me.body.addCls(me.bodyCls);
50970             } else {
50971                 me.body.addCls(cls);
50972             }
50973         } else {
50974             if (me.bodyCls) {
50975                 array = me.bodyCls.split(' ');
50976
50977                 if (!Ext.Array.contains(array, cls)) {
50978                     array.push(cls);
50979                 }
50980
50981                 me.bodyCls = array.join(' ');
50982             } else {
50983                 me.bodyCls = cls;
50984             }
50985         }
50986
50987         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
50988             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
50989         }
50990     },
50991
50992     // inherit docs
50993     removeUIFromElement: function() {
50994         var me = this,
50995             array, cls;
50996
50997         me.callParent(arguments);
50998
50999         cls = me.baseCls + '-body-' + me.ui;
51000         if (me.rendered) {
51001             if (me.bodyCls) {
51002                 me.body.removeCls(me.bodyCls);
51003             } else {
51004                 me.body.removeCls(cls);
51005             }
51006         } else {
51007             if (me.bodyCls) {
51008                 array = me.bodyCls.split(' ');
51009                 Ext.Array.remove(array, cls);
51010                 me.bodyCls = array.join(' ');
51011             } else {
51012                 me.bodyCls = cls;
51013             }
51014         }
51015
51016         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51017             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
51018         }
51019     },
51020
51021     onClick: function(e) {
51022         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
51023             this.fireEvent('click', e);
51024         }
51025     },
51026
51027     getTargetEl: function() {
51028         return this.body || this.frameBody || this.el;
51029     },
51030
51031     /**
51032      * Sets the title of the header.
51033      * @param {String} title The title to be set
51034      */
51035     setTitle: function(title) {
51036         var me = this;
51037         if (me.rendered) {
51038             if (me.titleCmp.rendered) {
51039                 if (me.titleCmp.surface) {
51040                     me.title = title || '';
51041                     var sprite = me.titleCmp.surface.items.items[0],
51042                         surface = me.titleCmp.surface;
51043
51044                     surface.remove(sprite);
51045                     me.textConfig.type = 'text';
51046                     me.textConfig.text = title;
51047                     sprite = surface.add(me.textConfig);
51048                     sprite.setAttributes({
51049                         rotate: {
51050                             degrees: 90
51051                         }
51052                     }, true);
51053                     me.titleCmp.autoSizeSurface();
51054                 } else {
51055                     me.title = title || '&#160;';
51056                     me.titleCmp.textEl.update(me.title);
51057                 }
51058             } else {
51059                 me.titleCmp.on({
51060                     render: function() {
51061                         me.setTitle(title);
51062                     },
51063                     single: true
51064                 });
51065             }
51066         } else {
51067             me.on({
51068                 render: function() {
51069                     me.layout.layout();
51070                     me.setTitle(title);
51071                 },
51072                 single: true
51073             });
51074         }
51075     },
51076
51077     /**
51078      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
51079      * icon class if one has already been set.
51080      * @param {String} cls The new CSS class name
51081      */
51082     setIconCls: function(cls) {
51083         var me = this,
51084             isEmpty = !cls || !cls.length,
51085             iconCmp = me.iconCmp,
51086             el;
51087         
51088         me.iconCls = cls;
51089         if (!me.iconCmp && !isEmpty) {
51090             me.initIconCmp();
51091             me.insert(0, me.iconCmp);
51092         } else if (iconCmp) {
51093             if (isEmpty) {
51094                 me.iconCmp.destroy();
51095             } else {
51096                 el = iconCmp.iconEl;
51097                 el.removeCls(iconCmp.iconCls);
51098                 el.addCls(cls);
51099                 iconCmp.iconCls = cls;
51100             }
51101         }
51102     },
51103
51104     /**
51105      * Add a tool to the header
51106      * @param {Object} tool
51107      */
51108     addTool: function(tool) {
51109         this.tools.push(this.add(tool));
51110     },
51111
51112     /**
51113      * @private
51114      * Set up the tools.&lt;tool type> link in the owning Panel.
51115      * Bind the tool to its owning Panel.
51116      * @param component
51117      * @param index
51118      */
51119     onAdd: function(component, index) {
51120         this.callParent([arguments]);
51121         if (component instanceof Ext.panel.Tool) {
51122             component.bindTo(this.ownerCt);
51123             this.tools[component.type] = component;
51124         }
51125     }
51126 });
51127
51128 /**
51129  * @class Ext.fx.target.Element
51130  * @extends Ext.fx.target.Target
51131  * 
51132  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
51133  * created directly, the {@link Ext.Element} will be passed to the animation and
51134  * and the appropriate target will be created.
51135  */
51136 Ext.define('Ext.fx.target.Element', {
51137
51138     /* Begin Definitions */
51139     
51140     extend: 'Ext.fx.target.Target',
51141     
51142     /* End Definitions */
51143
51144     type: 'element',
51145
51146     getElVal: function(el, attr, val) {
51147         if (val == undefined) {
51148             if (attr === 'x') {
51149                 val = el.getX();
51150             }
51151             else if (attr === 'y') {
51152                 val = el.getY();
51153             }
51154             else if (attr === 'scrollTop') {
51155                 val = el.getScroll().top;
51156             }
51157             else if (attr === 'scrollLeft') {
51158                 val = el.getScroll().left;
51159             }
51160             else if (attr === 'height') {
51161                 val = el.getHeight();
51162             }
51163             else if (attr === 'width') {
51164                 val = el.getWidth();
51165             }
51166             else {
51167                 val = el.getStyle(attr);
51168             }
51169         }
51170         return val;
51171     },
51172
51173     getAttr: function(attr, val) {
51174         var el = this.target;
51175         return [[ el, this.getElVal(el, attr, val)]];
51176     },
51177
51178     setAttr: function(targetData) {
51179         var target = this.target,
51180             ln = targetData.length,
51181             attrs, attr, o, i, j, ln2, element, value;
51182         for (i = 0; i < ln; i++) {
51183             attrs = targetData[i].attrs;
51184             for (attr in attrs) {
51185                 if (attrs.hasOwnProperty(attr)) {
51186                     ln2 = attrs[attr].length;
51187                     for (j = 0; j < ln2; j++) {
51188                         o = attrs[attr][j];
51189                         element = o[0];
51190                         value = o[1];
51191                         if (attr === 'x') {
51192                             element.setX(value);
51193                         }
51194                         else if (attr === 'y') {
51195                             element.setY(value);
51196                         }
51197                         else if (attr === 'scrollTop') {
51198                             element.scrollTo('top', value);
51199                         }
51200                         else if (attr === 'scrollLeft') {
51201                             element.scrollTo('left',value);
51202                         }
51203                         else {
51204                             element.setStyle(attr, value);
51205                         }
51206                     }
51207                 }
51208             }
51209         }
51210     }
51211 });
51212
51213 /**
51214  * @class Ext.fx.target.CompositeElement
51215  * @extends Ext.fx.target.Element
51216  * 
51217  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
51218  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
51219  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
51220  * and the appropriate target will be created.
51221  */
51222 Ext.define('Ext.fx.target.CompositeElement', {
51223
51224     /* Begin Definitions */
51225
51226     extend: 'Ext.fx.target.Element',
51227
51228     /* End Definitions */
51229
51230     isComposite: true,
51231     
51232     constructor: function(target) {
51233         target.id = target.id || Ext.id(null, 'ext-composite-');
51234         this.callParent([target]);
51235     },
51236
51237     getAttr: function(attr, val) {
51238         var out = [],
51239             target = this.target;
51240         target.each(function(el) {
51241             out.push([el, this.getElVal(el, attr, val)]);
51242         }, this);
51243         return out;
51244     }
51245 });
51246
51247 /**
51248  * @class Ext.fx.Manager
51249  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
51250  * @private
51251  * @singleton
51252  */
51253
51254 Ext.define('Ext.fx.Manager', {
51255
51256     /* Begin Definitions */
51257
51258     singleton: true,
51259
51260     requires: ['Ext.util.MixedCollection',
51261                'Ext.fx.target.Element',
51262                'Ext.fx.target.CompositeElement',
51263                'Ext.fx.target.Sprite',
51264                'Ext.fx.target.CompositeSprite',
51265                'Ext.fx.target.Component'],
51266
51267     mixins: {
51268         queue: 'Ext.fx.Queue'
51269     },
51270
51271     /* End Definitions */
51272
51273     constructor: function() {
51274         this.items = Ext.create('Ext.util.MixedCollection');
51275         this.mixins.queue.constructor.call(this);
51276
51277         // this.requestAnimFrame = (function() {
51278         //     var raf = window.requestAnimationFrame ||
51279         //               window.webkitRequestAnimationFrame ||
51280         //               window.mozRequestAnimationFrame ||
51281         //               window.oRequestAnimationFrame ||
51282         //               window.msRequestAnimationFrame;
51283         //     if (raf) {
51284         //         return function(callback, element) {
51285         //             raf(callback);
51286         //         };
51287         //     }
51288         //     else {
51289         //         return function(callback, element) {
51290         //             window.setTimeout(callback, Ext.fx.Manager.interval);
51291         //         };
51292         //     }
51293         // })();
51294     },
51295
51296     /**
51297      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
51298      */
51299     interval: 16,
51300
51301     /**
51302      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
51303      */
51304     forceJS: true,
51305
51306     // @private Target factory
51307     createTarget: function(target) {
51308         var me = this,
51309             useCSS3 = !me.forceJS && Ext.supports.Transitions,
51310             targetObj;
51311
51312         me.useCSS3 = useCSS3;
51313
51314         // dom id
51315         if (Ext.isString(target)) {
51316             target = Ext.get(target);
51317         }
51318         // dom element
51319         if (target && target.tagName) {
51320             target = Ext.get(target);
51321             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51322             me.targets.add(targetObj);
51323             return targetObj;
51324         }
51325         if (Ext.isObject(target)) {
51326             // Element
51327             if (target.dom) {
51328                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51329             }
51330             // Element Composite
51331             else if (target.isComposite) {
51332                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
51333             }
51334             // Draw Sprite
51335             else if (target.isSprite) {
51336                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
51337             }
51338             // Draw Sprite Composite
51339             else if (target.isCompositeSprite) {
51340                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
51341             }
51342             // Component
51343             else if (target.isComponent) {
51344                 targetObj = Ext.create('Ext.fx.target.Component', target);
51345             }
51346             else if (target.isAnimTarget) {
51347                 return target;
51348             }
51349             else {
51350                 return null;
51351             }
51352             me.targets.add(targetObj);
51353             return targetObj;
51354         }
51355         else {
51356             return null;
51357         }
51358     },
51359
51360     /**
51361      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
51362      * @param {Ext.fx.Anim} anim
51363      */
51364     addAnim: function(anim) {
51365         var items = this.items,
51366             task = this.task;
51367         // var me = this,
51368         //     items = me.items,
51369         //     cb = function() {
51370         //         if (items.length) {
51371         //             me.task = true;
51372         //             me.runner();
51373         //             me.requestAnimFrame(cb);
51374         //         }
51375         //         else {
51376         //             me.task = false;
51377         //         }
51378         //     };
51379
51380         items.add(anim);
51381
51382         // Start the timer if not already running
51383         if (!task && items.length) {
51384             task = this.task = {
51385                 run: this.runner,
51386                 interval: this.interval,
51387                 scope: this
51388             };
51389             Ext.TaskManager.start(task);
51390         }
51391
51392         // //Start the timer if not already running
51393         // if (!me.task && items.length) {
51394         //     me.requestAnimFrame(cb);
51395         // }
51396     },
51397
51398     /**
51399      * Remove an Anim from the manager. This is done automatically when an Anim ends.
51400      * @param {Ext.fx.Anim} anim
51401      */
51402     removeAnim: function(anim) {
51403         // this.items.remove(anim);
51404         var items = this.items,
51405             task = this.task;
51406         items.remove(anim);
51407         // Stop the timer if there are no more managed Anims
51408         if (task && !items.length) {
51409             Ext.TaskManager.stop(task);
51410             delete this.task;
51411         }
51412     },
51413
51414     /**
51415      * @private
51416      * Filter function to determine which animations need to be started
51417      */
51418     startingFilter: function(o) {
51419         return o.paused === false && o.running === false && o.iterations > 0;
51420     },
51421
51422     /**
51423      * @private
51424      * Filter function to determine which animations are still running
51425      */
51426     runningFilter: function(o) {
51427         return o.paused === false && o.running === true && o.isAnimator !== true;
51428     },
51429
51430     /**
51431      * @private
51432      * Runner function being called each frame
51433      */
51434     runner: function() {
51435         var me = this,
51436             items = me.items;
51437
51438         me.targetData = {};
51439         me.targetArr = {};
51440
51441         // Single timestamp for all animations this interval
51442         me.timestamp = new Date();
51443
51444         // Start any items not current running
51445         items.filterBy(me.startingFilter).each(me.startAnim, me);
51446
51447         // Build the new attributes to be applied for all targets in this frame
51448         items.filterBy(me.runningFilter).each(me.runAnim, me);
51449
51450         // Apply all the pending changes to their targets
51451         me.applyPendingAttrs();
51452     },
51453
51454     /**
51455      * @private
51456      * Start the individual animation (initialization)
51457      */
51458     startAnim: function(anim) {
51459         anim.start(this.timestamp);
51460     },
51461
51462     /**
51463      * @private
51464      * Run the individual animation for this frame
51465      */
51466     runAnim: function(anim) {
51467         if (!anim) {
51468             return;
51469         }
51470         var me = this,
51471             targetId = anim.target.getId(),
51472             useCSS3 = me.useCSS3 && anim.target.type == 'element',
51473             elapsedTime = me.timestamp - anim.startTime,
51474             target, o;
51475
51476         this.collectTargetData(anim, elapsedTime, useCSS3);
51477
51478         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
51479         // to get a good initial state, then add the transition properties and set the final attributes.
51480         if (useCSS3) {
51481             // Flush the collected attributes, without transition
51482             anim.target.setAttr(me.targetData[targetId], true);
51483
51484             // Add the end frame data
51485             me.targetData[targetId] = [];
51486             me.collectTargetData(anim, anim.duration, useCSS3);
51487
51488             // Pause the animation so runAnim doesn't keep getting called
51489             anim.paused = true;
51490
51491             target = anim.target.target;
51492             // We only want to attach an event on the last element in a composite
51493             if (anim.target.isComposite) {
51494                 target = anim.target.target.last();
51495             }
51496
51497             // Listen for the transitionend event
51498             o = {};
51499             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
51500             o.scope = anim;
51501             o.single = true;
51502             target.on(o);
51503         }
51504         // For JS animation, trigger the lastFrame handler if this is the final frame
51505         else if (elapsedTime >= anim.duration) {
51506             me.applyPendingAttrs(true);
51507             delete me.targetData[targetId];
51508             delete me.targetArr[targetId];
51509             anim.lastFrame();
51510         }
51511     },
51512
51513     /**
51514      * Collect target attributes for the given Anim object at the given timestamp
51515      * @param {Ext.fx.Anim} anim The Anim instance
51516      * @param {Number} timestamp Time after the anim's start time
51517      */
51518     collectTargetData: function(anim, elapsedTime, useCSS3) {
51519         var targetId = anim.target.getId(),
51520             targetData = this.targetData[targetId],
51521             data;
51522         
51523         if (!targetData) {
51524             targetData = this.targetData[targetId] = [];
51525             this.targetArr[targetId] = anim.target;
51526         }
51527
51528         data = {
51529             duration: anim.duration,
51530             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
51531             attrs: {}
51532         };
51533         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
51534         targetData.push(data);
51535     },
51536
51537     /**
51538      * @private
51539      * Apply all pending attribute changes to their targets
51540      */
51541     applyPendingAttrs: function(isLastFrame) {
51542         var targetData = this.targetData,
51543             targetArr = this.targetArr,
51544             targetId;
51545         for (targetId in targetData) {
51546             if (targetData.hasOwnProperty(targetId)) {
51547                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
51548             }
51549         }
51550     }
51551 });
51552
51553 /**
51554  * @class Ext.fx.Animator
51555  *
51556  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
51557  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
51558  * at various points throughout the animation.
51559  *
51560  * ## Using Keyframes
51561  *
51562  * The {@link #keyframes} option is the most important part of specifying an animation when using this
51563  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
51564  * total animation duration. At each key frame, we can specify the target values at that time. Note that
51565  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
51566  * event that fires after each key frame is reached.
51567  *
51568  * ## Example
51569  *
51570  * In the example below, we modify the values of the element at each fifth throughout the animation.
51571  *
51572  *     @example
51573  *     Ext.create('Ext.fx.Animator', {
51574  *         target: Ext.getBody().createChild({
51575  *             style: {
51576  *                 width: '100px',
51577  *                 height: '100px',
51578  *                 'background-color': 'red'
51579  *             }
51580  *         }),
51581  *         duration: 10000, // 10 seconds
51582  *         keyframes: {
51583  *             0: {
51584  *                 opacity: 1,
51585  *                 backgroundColor: 'FF0000'
51586  *             },
51587  *             20: {
51588  *                 x: 30,
51589  *                 opacity: 0.5
51590  *             },
51591  *             40: {
51592  *                 x: 130,
51593  *                 backgroundColor: '0000FF'
51594  *             },
51595  *             60: {
51596  *                 y: 80,
51597  *                 opacity: 0.3
51598  *             },
51599  *             80: {
51600  *                 width: 200,
51601  *                 y: 200
51602  *             },
51603  *             100: {
51604  *                 opacity: 1,
51605  *                 backgroundColor: '00FF00'
51606  *             }
51607  *         }
51608  *     });
51609  */
51610 Ext.define('Ext.fx.Animator', {
51611
51612     /* Begin Definitions */
51613
51614     mixins: {
51615         observable: 'Ext.util.Observable'
51616     },
51617
51618     requires: ['Ext.fx.Manager'],
51619
51620     /* End Definitions */
51621
51622     isAnimator: true,
51623
51624     /**
51625      * @cfg {Number} duration
51626      * Time in milliseconds for the animation to last. Defaults to 250.
51627      */
51628     duration: 250,
51629
51630     /**
51631      * @cfg {Number} delay
51632      * Time to delay before starting the animation. Defaults to 0.
51633      */
51634     delay: 0,
51635
51636     /* private used to track a delayed starting time */
51637     delayStart: 0,
51638
51639     /**
51640      * @cfg {Boolean} dynamic
51641      * Currently only for Component Animation: Only set a component's outer element size bypassing layouts.  Set to true to do full layouts for every frame of the animation.  Defaults to false.
51642      */
51643     dynamic: false,
51644
51645     /**
51646      * @cfg {String} easing
51647      *
51648      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
51649      * speed over its duration.
51650      *
51651      *  - backIn
51652      *  - backOut
51653      *  - bounceIn
51654      *  - bounceOut
51655      *  - ease
51656      *  - easeIn
51657      *  - easeOut
51658      *  - easeInOut
51659      *  - elasticIn
51660      *  - elasticOut
51661      *  - cubic-bezier(x1, y1, x2, y2)
51662      *
51663      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51664      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51665      * be in the range [0, 1] or the definition is invalid.
51666      *
51667      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51668      */
51669     easing: 'ease',
51670
51671     /**
51672      * Flag to determine if the animation has started
51673      * @property running
51674      * @type Boolean
51675      */
51676     running: false,
51677
51678     /**
51679      * Flag to determine if the animation is paused. Only set this to true if you need to
51680      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
51681      * @property paused
51682      * @type Boolean
51683      */
51684     paused: false,
51685
51686     /**
51687      * @private
51688      */
51689     damper: 1,
51690
51691     /**
51692      * @cfg {Number} iterations
51693      * Number of times to execute the animation. Defaults to 1.
51694      */
51695     iterations: 1,
51696
51697     /**
51698      * Current iteration the animation is running.
51699      * @property currentIteration
51700      * @type Number
51701      */
51702     currentIteration: 0,
51703
51704     /**
51705      * Current keyframe step of the animation.
51706      * @property keyframeStep
51707      * @type Number
51708      */
51709     keyframeStep: 0,
51710
51711     /**
51712      * @private
51713      */
51714     animKeyFramesRE: /^(from|to|\d+%?)$/,
51715
51716     /**
51717      * @cfg {Ext.fx.target.Target} target
51718      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
51719      * method to apply the same animation to many targets.
51720      */
51721
51722      /**
51723       * @cfg {Object} keyframes
51724       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
51725       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
51726       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
51727       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
51728       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
51729  <pre><code>
51730 keyframes : {
51731     '0%': {
51732         left: 100
51733     },
51734     '40%': {
51735         left: 150
51736     },
51737     '60%': {
51738         left: 75
51739     },
51740     '100%': {
51741         left: 100
51742     }
51743 }
51744  </code></pre>
51745       */
51746     constructor: function(config) {
51747         var me = this;
51748         config = Ext.apply(me, config || {});
51749         me.config = config;
51750         me.id = Ext.id(null, 'ext-animator-');
51751         me.addEvents(
51752             /**
51753              * @event beforeanimate
51754              * Fires before the animation starts. A handler can return false to cancel the animation.
51755              * @param {Ext.fx.Animator} this
51756              */
51757             'beforeanimate',
51758             /**
51759               * @event keyframe
51760               * Fires at each keyframe.
51761               * @param {Ext.fx.Animator} this
51762               * @param {Number} keyframe step number
51763               */
51764             'keyframe',
51765             /**
51766              * @event afteranimate
51767              * Fires when the animation is complete.
51768              * @param {Ext.fx.Animator} this
51769              * @param {Date} startTime
51770              */
51771             'afteranimate'
51772         );
51773         me.mixins.observable.constructor.call(me, config);
51774         me.timeline = [];
51775         me.createTimeline(me.keyframes);
51776         if (me.target) {
51777             me.applyAnimator(me.target);
51778             Ext.fx.Manager.addAnim(me);
51779         }
51780     },
51781
51782     /**
51783      * @private
51784      */
51785     sorter: function (a, b) {
51786         return a.pct - b.pct;
51787     },
51788
51789     /**
51790      * @private
51791      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
51792      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
51793      */
51794     createTimeline: function(keyframes) {
51795         var me = this,
51796             attrs = [],
51797             to = me.to || {},
51798             duration = me.duration,
51799             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
51800
51801         for (pct in keyframes) {
51802             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
51803                 attr = {attrs: Ext.apply(keyframes[pct], to)};
51804                 // CSS3 spec allow for from/to to be specified.
51805                 if (pct == "from") {
51806                     pct = 0;
51807                 }
51808                 else if (pct == "to") {
51809                     pct = 100;
51810                 }
51811                 // convert % values into integers
51812                 attr.pct = parseInt(pct, 10);
51813                 attrs.push(attr);
51814             }
51815         }
51816         // Sort by pct property
51817         Ext.Array.sort(attrs, me.sorter);
51818         // Only an end
51819         //if (attrs[0].pct) {
51820         //    attrs.unshift({pct: 0, attrs: element.attrs});
51821         //}
51822
51823         ln = attrs.length;
51824         for (i = 0; i < ln; i++) {
51825             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
51826             ms = duration * (attrs[i].pct / 100);
51827             me.timeline.push({
51828                 duration: ms - prevMs,
51829                 attrs: attrs[i].attrs
51830             });
51831         }
51832     },
51833
51834     /**
51835      * Applies animation to the Ext.fx.target
51836      * @private
51837      * @param target
51838      * @type String/Object
51839      */
51840     applyAnimator: function(target) {
51841         var me = this,
51842             anims = [],
51843             timeline = me.timeline,
51844             reverse = me.reverse,
51845             ln = timeline.length,
51846             anim, easing, damper, initial, attrs, lastAttrs, i;
51847
51848         if (me.fireEvent('beforeanimate', me) !== false) {
51849             for (i = 0; i < ln; i++) {
51850                 anim = timeline[i];
51851                 attrs = anim.attrs;
51852                 easing = attrs.easing || me.easing;
51853                 damper = attrs.damper || me.damper;
51854                 delete attrs.easing;
51855                 delete attrs.damper;
51856                 anim = Ext.create('Ext.fx.Anim', {
51857                     target: target,
51858                     easing: easing,
51859                     damper: damper,
51860                     duration: anim.duration,
51861                     paused: true,
51862                     to: attrs
51863                 });
51864                 anims.push(anim);
51865             }
51866             me.animations = anims;
51867             me.target = anim.target;
51868             for (i = 0; i < ln - 1; i++) {
51869                 anim = anims[i];
51870                 anim.nextAnim = anims[i + 1];
51871                 anim.on('afteranimate', function() {
51872                     this.nextAnim.paused = false;
51873                 });
51874                 anim.on('afteranimate', function() {
51875                     this.fireEvent('keyframe', this, ++this.keyframeStep);
51876                 }, me);
51877             }
51878             anims[ln - 1].on('afteranimate', function() {
51879                 this.lastFrame();
51880             }, me);
51881         }
51882     },
51883
51884     /**
51885      * @private
51886      * Fires beforeanimate and sets the running flag.
51887      */
51888     start: function(startTime) {
51889         var me = this,
51890             delay = me.delay,
51891             delayStart = me.delayStart,
51892             delayDelta;
51893         if (delay) {
51894             if (!delayStart) {
51895                 me.delayStart = startTime;
51896                 return;
51897             }
51898             else {
51899                 delayDelta = startTime - delayStart;
51900                 if (delayDelta < delay) {
51901                     return;
51902                 }
51903                 else {
51904                     // Compensate for frame delay;
51905                     startTime = new Date(delayStart.getTime() + delay);
51906                 }
51907             }
51908         }
51909         if (me.fireEvent('beforeanimate', me) !== false) {
51910             me.startTime = startTime;
51911             me.running = true;
51912             me.animations[me.keyframeStep].paused = false;
51913         }
51914     },
51915
51916     /**
51917      * @private
51918      * Perform lastFrame cleanup and handle iterations
51919      * @returns a hash of the new attributes.
51920      */
51921     lastFrame: function() {
51922         var me = this,
51923             iter = me.iterations,
51924             iterCount = me.currentIteration;
51925
51926         iterCount++;
51927         if (iterCount < iter) {
51928             me.startTime = new Date();
51929             me.currentIteration = iterCount;
51930             me.keyframeStep = 0;
51931             me.applyAnimator(me.target);
51932             me.animations[me.keyframeStep].paused = false;
51933         }
51934         else {
51935             me.currentIteration = 0;
51936             me.end();
51937         }
51938     },
51939
51940     /**
51941      * Fire afteranimate event and end the animation. Usually called automatically when the
51942      * animation reaches its final frame, but can also be called manually to pre-emptively
51943      * stop and destroy the running animation.
51944      */
51945     end: function() {
51946         var me = this;
51947         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
51948     }
51949 });
51950 /**
51951  * @class Ext.fx.Easing
51952  *
51953  * This class contains a series of function definitions used to modify values during an animation.
51954  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
51955  * speed over its duration. The following options are available: 
51956  *
51957  * - linear The default easing type
51958  * - backIn
51959  * - backOut
51960  * - bounceIn
51961  * - bounceOut
51962  * - ease
51963  * - easeIn
51964  * - easeOut
51965  * - easeInOut
51966  * - elasticIn
51967  * - elasticOut
51968  * - cubic-bezier(x1, y1, x2, y2)
51969  *
51970  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51971  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51972  * be in the range [0, 1] or the definition is invalid.
51973  *
51974  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51975  *
51976  * @singleton
51977  */
51978 Ext.ns('Ext.fx');
51979
51980 Ext.require('Ext.fx.CubicBezier', function() {
51981     var math = Math,
51982         pi = math.PI,
51983         pow = math.pow,
51984         sin = math.sin,
51985         sqrt = math.sqrt,
51986         abs = math.abs,
51987         backInSeed = 1.70158;
51988     Ext.fx.Easing = {
51989         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
51990         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
51991         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51992         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51993         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
51994         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51995         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51996         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
51997     };
51998
51999     Ext.apply(Ext.fx.Easing, {
52000         linear: function(n) {
52001             return n;
52002         },
52003         ease: function(n) {
52004             var q = 0.07813 - n / 2,
52005                 alpha = -0.25,
52006                 Q = sqrt(0.0066 + q * q),
52007                 x = Q - q,
52008                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52009                 y = -Q - q,
52010                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52011                 t = X + Y + 0.25;
52012             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
52013         },
52014         easeIn: function (n) {
52015             return pow(n, 1.7);
52016         },
52017         easeOut: function (n) {
52018             return pow(n, 0.48);
52019         },
52020         easeInOut: function(n) {
52021             var q = 0.48 - n / 1.04,
52022                 Q = sqrt(0.1734 + q * q),
52023                 x = Q - q,
52024                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52025                 y = -Q - q,
52026                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52027                 t = X + Y + 0.5;
52028             return (1 - t) * 3 * t * t + t * t * t;
52029         },
52030         backIn: function (n) {
52031             return n * n * ((backInSeed + 1) * n - backInSeed);
52032         },
52033         backOut: function (n) {
52034             n = n - 1;
52035             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
52036         },
52037         elasticIn: function (n) {
52038             if (n === 0 || n === 1) {
52039                 return n;
52040             }
52041             var p = 0.3,
52042                 s = p / 4;
52043             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
52044         },
52045         elasticOut: function (n) {
52046             return 1 - Ext.fx.Easing.elasticIn(1 - n);
52047         },
52048         bounceIn: function (n) {
52049             return 1 - Ext.fx.Easing.bounceOut(1 - n);
52050         },
52051         bounceOut: function (n) {
52052             var s = 7.5625,
52053                 p = 2.75,
52054                 l;
52055             if (n < (1 / p)) {
52056                 l = s * n * n;
52057             } else {
52058                 if (n < (2 / p)) {
52059                     n -= (1.5 / p);
52060                     l = s * n * n + 0.75;
52061                 } else {
52062                     if (n < (2.5 / p)) {
52063                         n -= (2.25 / p);
52064                         l = s * n * n + 0.9375;
52065                     } else {
52066                         n -= (2.625 / p);
52067                         l = s * n * n + 0.984375;
52068                     }
52069                 }
52070             }
52071             return l;
52072         }
52073     });
52074     Ext.apply(Ext.fx.Easing, {
52075         'back-in': Ext.fx.Easing.backIn,
52076         'back-out': Ext.fx.Easing.backOut,
52077         'ease-in': Ext.fx.Easing.easeIn,
52078         'ease-out': Ext.fx.Easing.easeOut,
52079         'elastic-in': Ext.fx.Easing.elasticIn,
52080         'elastic-out': Ext.fx.Easing.elasticIn,
52081         'bounce-in': Ext.fx.Easing.bounceIn,
52082         'bounce-out': Ext.fx.Easing.bounceOut,
52083         'ease-in-out': Ext.fx.Easing.easeInOut
52084     });
52085 });
52086 /**
52087  * @class Ext.draw.Draw
52088  * Base Drawing class.  Provides base drawing functions.
52089  * @private
52090  */
52091 Ext.define('Ext.draw.Draw', {
52092     /* Begin Definitions */
52093
52094     singleton: true,
52095
52096     requires: ['Ext.draw.Color'],
52097
52098     /* End Definitions */
52099
52100     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
52101     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
52102     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
52103     stopsRE: /^(\d+%?)$/,
52104     radian: Math.PI / 180,
52105
52106     availableAnimAttrs: {
52107         along: "along",
52108         blur: null,
52109         "clip-rect": "csv",
52110         cx: null,
52111         cy: null,
52112         fill: "color",
52113         "fill-opacity": null,
52114         "font-size": null,
52115         height: null,
52116         opacity: null,
52117         path: "path",
52118         r: null,
52119         rotation: "csv",
52120         rx: null,
52121         ry: null,
52122         scale: "csv",
52123         stroke: "color",
52124         "stroke-opacity": null,
52125         "stroke-width": null,
52126         translation: "csv",
52127         width: null,
52128         x: null,
52129         y: null
52130     },
52131
52132     is: function(o, type) {
52133         type = String(type).toLowerCase();
52134         return (type == "object" && o === Object(o)) ||
52135             (type == "undefined" && typeof o == type) ||
52136             (type == "null" && o === null) ||
52137             (type == "array" && Array.isArray && Array.isArray(o)) ||
52138             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
52139     },
52140
52141     ellipsePath: function(sprite) {
52142         var attr = sprite.attr;
52143         return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
52144     },
52145
52146     rectPath: function(sprite) {
52147         var attr = sprite.attr;
52148         if (attr.radius) {
52149             return Ext.String.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
52150         }
52151         else {
52152             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
52153         }
52154     },
52155
52156     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
52157     path2string: function () {
52158         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52159     },
52160
52161     // Convert the passed arrayPath to a proper SVG path string (d attribute)
52162     pathToString: function(arrayPath) {
52163         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52164     },
52165
52166     parsePathString: function (pathString) {
52167         if (!pathString) {
52168             return null;
52169         }
52170         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
52171             data = [],
52172             me = this;
52173         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
52174             data = me.pathClone(pathString);
52175         }
52176         if (!data.length) {
52177             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
52178                 var params = [],
52179                     name = b.toLowerCase();
52180                 c.replace(me.pathValuesRE, function (a, b) {
52181                     b && params.push(+b);
52182                 });
52183                 if (name == "m" && params.length > 2) {
52184                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
52185                     name = "l";
52186                     b = (b == "m") ? "l" : "L";
52187                 }
52188                 while (params.length >= paramCounts[name]) {
52189                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
52190                     if (!paramCounts[name]) {
52191                         break;
52192                     }
52193                 }
52194             });
52195         }
52196         data.toString = me.path2string;
52197         return data;
52198     },
52199
52200     mapPath: function (path, matrix) {
52201         if (!matrix) {
52202             return path;
52203         }
52204         var x, y, i, ii, j, jj, pathi;
52205         path = this.path2curve(path);
52206         for (i = 0, ii = path.length; i < ii; i++) {
52207             pathi = path[i];
52208             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
52209                 x = matrix.x(pathi[j], pathi[j + 1]);
52210                 y = matrix.y(pathi[j], pathi[j + 1]);
52211                 pathi[j] = x;
52212                 pathi[j + 1] = y;
52213             }
52214         }
52215         return path;
52216     },
52217
52218     pathClone: function(pathArray) {
52219         var res = [],
52220             j, jj, i, ii;
52221         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52222             pathArray = this.parsePathString(pathArray);
52223         }
52224         for (i = 0, ii = pathArray.length; i < ii; i++) {
52225             res[i] = [];
52226             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
52227                 res[i][j] = pathArray[i][j];
52228             }
52229         }
52230         res.toString = this.path2string;
52231         return res;
52232     },
52233
52234     pathToAbsolute: function (pathArray) {
52235         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52236             pathArray = this.parsePathString(pathArray);
52237         }
52238         var res = [],
52239             x = 0,
52240             y = 0,
52241             mx = 0,
52242             my = 0,
52243             i = 0,
52244             ln = pathArray.length,
52245             r, pathSegment, j, ln2;
52246         // MoveTo initial x/y position
52247         if (ln && pathArray[0][0] == "M") {
52248             x = +pathArray[0][1];
52249             y = +pathArray[0][2];
52250             mx = x;
52251             my = y;
52252             i++;
52253             res[0] = ["M", x, y];
52254         }
52255         for (; i < ln; i++) {
52256             r = res[i] = [];
52257             pathSegment = pathArray[i];
52258             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
52259                 r[0] = pathSegment[0].toUpperCase();
52260                 switch (r[0]) {
52261                     // Elliptical Arc
52262                     case "A":
52263                         r[1] = pathSegment[1];
52264                         r[2] = pathSegment[2];
52265                         r[3] = pathSegment[3];
52266                         r[4] = pathSegment[4];
52267                         r[5] = pathSegment[5];
52268                         r[6] = +(pathSegment[6] + x);
52269                         r[7] = +(pathSegment[7] + y);
52270                         break;
52271                     // Vertical LineTo
52272                     case "V":
52273                         r[1] = +pathSegment[1] + y;
52274                         break;
52275                     // Horizontal LineTo
52276                     case "H":
52277                         r[1] = +pathSegment[1] + x;
52278                         break;
52279                     case "M":
52280                     // MoveTo
52281                         mx = +pathSegment[1] + x;
52282                         my = +pathSegment[2] + y;
52283                     default:
52284                         j = 1;
52285                         ln2 = pathSegment.length;
52286                         for (; j < ln2; j++) {
52287                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
52288                         }
52289                 }
52290             }
52291             else {
52292                 j = 0;
52293                 ln2 = pathSegment.length;
52294                 for (; j < ln2; j++) {
52295                     res[i][j] = pathSegment[j];
52296                 }
52297             }
52298             switch (r[0]) {
52299                 // ClosePath
52300                 case "Z":
52301                     x = mx;
52302                     y = my;
52303                     break;
52304                 // Horizontal LineTo
52305                 case "H":
52306                     x = r[1];
52307                     break;
52308                 // Vertical LineTo
52309                 case "V":
52310                     y = r[1];
52311                     break;
52312                 // MoveTo
52313                 case "M":
52314                     pathSegment = res[i];
52315                     ln2 = pathSegment.length;
52316                     mx = pathSegment[ln2 - 2];
52317                     my = pathSegment[ln2 - 1];
52318                 default:
52319                     pathSegment = res[i];
52320                     ln2 = pathSegment.length;
52321                     x = pathSegment[ln2 - 2];
52322                     y = pathSegment[ln2 - 1];
52323             }
52324         }
52325         res.toString = this.path2string;
52326         return res;
52327     },
52328
52329     // TO BE DEPRECATED
52330     pathToRelative: function (pathArray) {
52331         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
52332             pathArray = this.parsePathString(pathArray);
52333         }
52334         var res = [],
52335             x = 0,
52336             y = 0,
52337             mx = 0,
52338             my = 0,
52339             start = 0;
52340         if (pathArray[0][0] == "M") {
52341             x = pathArray[0][1];
52342             y = pathArray[0][2];
52343             mx = x;
52344             my = y;
52345             start++;
52346             res.push(["M", x, y]);
52347         }
52348         for (var i = start, ii = pathArray.length; i < ii; i++) {
52349             var r = res[i] = [],
52350                 pa = pathArray[i];
52351             if (pa[0] != pa[0].toLowerCase()) {
52352                 r[0] = pa[0].toLowerCase();
52353                 switch (r[0]) {
52354                     case "a":
52355                         r[1] = pa[1];
52356                         r[2] = pa[2];
52357                         r[3] = pa[3];
52358                         r[4] = pa[4];
52359                         r[5] = pa[5];
52360                         r[6] = +(pa[6] - x).toFixed(3);
52361                         r[7] = +(pa[7] - y).toFixed(3);
52362                         break;
52363                     case "v":
52364                         r[1] = +(pa[1] - y).toFixed(3);
52365                         break;
52366                     case "m":
52367                         mx = pa[1];
52368                         my = pa[2];
52369                     default:
52370                         for (var j = 1, jj = pa.length; j < jj; j++) {
52371                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
52372                         }
52373                 }
52374             } else {
52375                 r = res[i] = [];
52376                 if (pa[0] == "m") {
52377                     mx = pa[1] + x;
52378                     my = pa[2] + y;
52379                 }
52380                 for (var k = 0, kk = pa.length; k < kk; k++) {
52381                     res[i][k] = pa[k];
52382                 }
52383             }
52384             var len = res[i].length;
52385             switch (res[i][0]) {
52386                 case "z":
52387                     x = mx;
52388                     y = my;
52389                     break;
52390                 case "h":
52391                     x += +res[i][len - 1];
52392                     break;
52393                 case "v":
52394                     y += +res[i][len - 1];
52395                     break;
52396                 default:
52397                     x += +res[i][len - 2];
52398                     y += +res[i][len - 1];
52399             }
52400         }
52401         res.toString = this.path2string;
52402         return res;
52403     },
52404
52405     // Returns a path converted to a set of curveto commands
52406     path2curve: function (path) {
52407         var me = this,
52408             points = me.pathToAbsolute(path),
52409             ln = points.length,
52410             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52411             i, seg, segLn, point;
52412             
52413         for (i = 0; i < ln; i++) {
52414             points[i] = me.command2curve(points[i], attrs);
52415             if (points[i].length > 7) {
52416                     points[i].shift();
52417                     point = points[i];
52418                     while (point.length) {
52419                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
52420                     }
52421                     Ext.Array.erase(points, i, 1);
52422                     ln = points.length;
52423                 }
52424             seg = points[i];
52425             segLn = seg.length;
52426             attrs.x = seg[segLn - 2];
52427             attrs.y = seg[segLn - 1];
52428             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
52429             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
52430         }
52431         return points;
52432     },
52433     
52434     interpolatePaths: function (path, path2) {
52435         var me = this,
52436             p = me.pathToAbsolute(path),
52437             p2 = me.pathToAbsolute(path2),
52438             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52439             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52440             fixArc = function (pp, i) {
52441                 if (pp[i].length > 7) {
52442                     pp[i].shift();
52443                     var pi = pp[i];
52444                     while (pi.length) {
52445                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
52446                     }
52447                     Ext.Array.erase(pp, i, 1);
52448                     ii = Math.max(p.length, p2.length || 0);
52449                 }
52450             },
52451             fixM = function (path1, path2, a1, a2, i) {
52452                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
52453                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
52454                     a1.bx = 0;
52455                     a1.by = 0;
52456                     a1.x = path1[i][1];
52457                     a1.y = path1[i][2];
52458                     ii = Math.max(p.length, p2.length || 0);
52459                 }
52460             };
52461         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
52462             p[i] = me.command2curve(p[i], attrs);
52463             fixArc(p, i);
52464             (p2[i] = me.command2curve(p2[i], attrs2));
52465             fixArc(p2, i);
52466             fixM(p, p2, attrs, attrs2, i);
52467             fixM(p2, p, attrs2, attrs, i);
52468             var seg = p[i],
52469                 seg2 = p2[i],
52470                 seglen = seg.length,
52471                 seg2len = seg2.length;
52472             attrs.x = seg[seglen - 2];
52473             attrs.y = seg[seglen - 1];
52474             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
52475             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
52476             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
52477             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
52478             attrs2.x = seg2[seg2len - 2];
52479             attrs2.y = seg2[seg2len - 1];
52480         }
52481         return [p, p2];
52482     },
52483     
52484     //Returns any path command as a curveto command based on the attrs passed
52485     command2curve: function (pathCommand, d) {
52486         var me = this;
52487         if (!pathCommand) {
52488             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
52489         }
52490         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
52491             d.qx = d.qy = null;
52492         }
52493         switch (pathCommand[0]) {
52494             case "M":
52495                 d.X = pathCommand[1];
52496                 d.Y = pathCommand[2];
52497                 break;
52498             case "A":
52499                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
52500                 break;
52501             case "S":
52502                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
52503                 break;
52504             case "T":
52505                 d.qx = d.x + (d.x - (d.qx || d.x));
52506                 d.qy = d.y + (d.y - (d.qy || d.y));
52507                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
52508                 break;
52509             case "Q":
52510                 d.qx = pathCommand[1];
52511                 d.qy = pathCommand[2];
52512                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
52513                 break;
52514             case "L":
52515                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
52516                 break;
52517             case "H":
52518                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
52519                 break;
52520             case "V":
52521                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
52522                 break;
52523             case "Z":
52524                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
52525                 break;
52526         }
52527         return pathCommand;
52528     },
52529
52530     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
52531         var _13 = 1 / 3,
52532             _23 = 2 / 3;
52533         return [
52534                 _13 * x1 + _23 * ax,
52535                 _13 * y1 + _23 * ay,
52536                 _13 * x2 + _23 * ax,
52537                 _13 * y2 + _23 * ay,
52538                 x2,
52539                 y2
52540             ];
52541     },
52542     
52543     rotate: function (x, y, rad) {
52544         var cos = Math.cos(rad),
52545             sin = Math.sin(rad),
52546             X = x * cos - y * sin,
52547             Y = x * sin + y * cos;
52548         return {x: X, y: Y};
52549     },
52550
52551     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
52552         // for more information of where this Math came from visit:
52553         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
52554         var me = this,
52555             PI = Math.PI,
52556             radian = me.radian,
52557             _120 = PI * 120 / 180,
52558             rad = radian * (+angle || 0),
52559             res = [],
52560             math = Math,
52561             mcos = math.cos,
52562             msin = math.sin,
52563             msqrt = math.sqrt,
52564             mabs = math.abs,
52565             masin = math.asin,
52566             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
52567             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
52568         if (!recursive) {
52569             xy = me.rotate(x1, y1, -rad);
52570             x1 = xy.x;
52571             y1 = xy.y;
52572             xy = me.rotate(x2, y2, -rad);
52573             x2 = xy.x;
52574             y2 = xy.y;
52575             cos = mcos(radian * angle);
52576             sin = msin(radian * angle);
52577             x = (x1 - x2) / 2;
52578             y = (y1 - y2) / 2;
52579             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
52580             if (h > 1) {
52581                 h = msqrt(h);
52582                 rx = h * rx;
52583                 ry = h * ry;
52584             }
52585             rx2 = rx * rx;
52586             ry2 = ry * ry;
52587             k = (large_arc_flag == sweep_flag ? -1 : 1) *
52588                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
52589             cx = k * rx * y / ry + (x1 + x2) / 2;
52590             cy = k * -ry * x / rx + (y1 + y2) / 2;
52591             f1 = masin(((y1 - cy) / ry).toFixed(7));
52592             f2 = masin(((y2 - cy) / ry).toFixed(7));
52593
52594             f1 = x1 < cx ? PI - f1 : f1;
52595             f2 = x2 < cx ? PI - f2 : f2;
52596             if (f1 < 0) {
52597                 f1 = PI * 2 + f1;
52598             }
52599             if (f2 < 0) {
52600                 f2 = PI * 2 + f2;
52601             }
52602             if (sweep_flag && f1 > f2) {
52603                 f1 = f1 - PI * 2;
52604             }
52605             if (!sweep_flag && f2 > f1) {
52606                 f2 = f2 - PI * 2;
52607             }
52608         }
52609         else {
52610             f1 = recursive[0];
52611             f2 = recursive[1];
52612             cx = recursive[2];
52613             cy = recursive[3];
52614         }
52615         df = f2 - f1;
52616         if (mabs(df) > _120) {
52617             f2old = f2;
52618             x2old = x2;
52619             y2old = y2;
52620             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
52621             x2 = cx + rx * mcos(f2);
52622             y2 = cy + ry * msin(f2);
52623             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
52624         }
52625         df = f2 - f1;
52626         c1 = mcos(f1);
52627         s1 = msin(f1);
52628         c2 = mcos(f2);
52629         s2 = msin(f2);
52630         t = math.tan(df / 4);
52631         hx = 4 / 3 * rx * t;
52632         hy = 4 / 3 * ry * t;
52633         m1 = [x1, y1];
52634         m2 = [x1 + hx * s1, y1 - hy * c1];
52635         m3 = [x2 + hx * s2, y2 - hy * c2];
52636         m4 = [x2, y2];
52637         m2[0] = 2 * m1[0] - m2[0];
52638         m2[1] = 2 * m1[1] - m2[1];
52639         if (recursive) {
52640             return [m2, m3, m4].concat(res);
52641         }
52642         else {
52643             res = [m2, m3, m4].concat(res).join().split(",");
52644             newres = [];
52645             ln = res.length;
52646             for (i = 0;  i < ln; i++) {
52647                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
52648             }
52649             return newres;
52650         }
52651     },
52652
52653     // TO BE DEPRECATED
52654     rotateAndTranslatePath: function (sprite) {
52655         var alpha = sprite.rotation.degrees,
52656             cx = sprite.rotation.x,
52657             cy = sprite.rotation.y,
52658             dx = sprite.translation.x,
52659             dy = sprite.translation.y,
52660             path,
52661             i,
52662             p,
52663             xy,
52664             j,
52665             res = [];
52666         if (!alpha && !dx && !dy) {
52667             return this.pathToAbsolute(sprite.attr.path);
52668         }
52669         dx = dx || 0;
52670         dy = dy || 0;
52671         path = this.pathToAbsolute(sprite.attr.path);
52672         for (i = path.length; i--;) {
52673             p = res[i] = path[i].slice();
52674             if (p[0] == "A") {
52675                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
52676                 p[6] = xy.x + dx;
52677                 p[7] = xy.y + dy;
52678             } else {
52679                 j = 1;
52680                 while (p[j + 1] != null) {
52681                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
52682                     p[j] = xy.x + dx;
52683                     p[j + 1] = xy.y + dy;
52684                     j += 2;
52685                 }
52686             }
52687         }
52688         return res;
52689     },
52690
52691     // TO BE DEPRECATED
52692     rotatePoint: function (x, y, alpha, cx, cy) {
52693         if (!alpha) {
52694             return {
52695                 x: x,
52696                 y: y
52697             };
52698         }
52699         cx = cx || 0;
52700         cy = cy || 0;
52701         x = x - cx;
52702         y = y - cy;
52703         alpha = alpha * this.radian;
52704         var cos = Math.cos(alpha),
52705             sin = Math.sin(alpha);
52706         return {
52707             x: x * cos - y * sin + cx,
52708             y: x * sin + y * cos + cy
52709         };
52710     },
52711
52712     pathDimensions: function (path) {
52713         if (!path || !(path + "")) {
52714             return {x: 0, y: 0, width: 0, height: 0};
52715         }
52716         path = this.path2curve(path);
52717         var x = 0, 
52718             y = 0,
52719             X = [],
52720             Y = [],
52721             i = 0,
52722             ln = path.length,
52723             p, xmin, ymin, dim;
52724         for (; i < ln; i++) {
52725             p = path[i];
52726             if (p[0] == "M") {
52727                 x = p[1];
52728                 y = p[2];
52729                 X.push(x);
52730                 Y.push(y);
52731             }
52732             else {
52733                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
52734                 X = X.concat(dim.min.x, dim.max.x);
52735                 Y = Y.concat(dim.min.y, dim.max.y);
52736                 x = p[5];
52737                 y = p[6];
52738             }
52739         }
52740         xmin = Math.min.apply(0, X);
52741         ymin = Math.min.apply(0, Y);
52742         return {
52743             x: xmin,
52744             y: ymin,
52745             path: path,
52746             width: Math.max.apply(0, X) - xmin,
52747             height: Math.max.apply(0, Y) - ymin
52748         };
52749     },
52750
52751     intersectInside: function(path, cp1, cp2) {
52752         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
52753     },
52754
52755     intersectIntersection: function(s, e, cp1, cp2) {
52756         var p = [],
52757             dcx = cp1[0] - cp2[0],
52758             dcy = cp1[1] - cp2[1],
52759             dpx = s[0] - e[0],
52760             dpy = s[1] - e[1],
52761             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
52762             n2 = s[0] * e[1] - s[1] * e[0],
52763             n3 = 1 / (dcx * dpy - dcy * dpx);
52764
52765         p[0] = (n1 * dpx - n2 * dcx) * n3;
52766         p[1] = (n1 * dpy - n2 * dcy) * n3;
52767         return p;
52768     },
52769
52770     intersect: function(subjectPolygon, clipPolygon) {
52771         var me = this,
52772             i = 0,
52773             ln = clipPolygon.length,
52774             cp1 = clipPolygon[ln - 1],
52775             outputList = subjectPolygon,
52776             cp2, s, e, point, ln2, inputList, j;
52777         for (; i < ln; ++i) {
52778             cp2 = clipPolygon[i];
52779             inputList = outputList;
52780             outputList = [];
52781             s = inputList[inputList.length - 1];
52782             j = 0;
52783             ln2 = inputList.length;
52784             for (; j < ln2; j++) {
52785                 e = inputList[j];
52786                 if (me.intersectInside(e, cp1, cp2)) {
52787                     if (!me.intersectInside(s, cp1, cp2)) {
52788                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52789                     }
52790                     outputList.push(e);
52791                 }
52792                 else if (me.intersectInside(s, cp1, cp2)) {
52793                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52794                 }
52795                 s = e;
52796             }
52797             cp1 = cp2;
52798         }
52799         return outputList;
52800     },
52801
52802     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
52803         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
52804             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
52805             c = p1x - c1x,
52806             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52807             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52808             y = [p1y, p2y],
52809             x = [p1x, p2x],
52810             dot;
52811         if (Math.abs(t1) > 1e12) {
52812             t1 = 0.5;
52813         }
52814         if (Math.abs(t2) > 1e12) {
52815             t2 = 0.5;
52816         }
52817         if (t1 > 0 && t1 < 1) {
52818             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52819             x.push(dot.x);
52820             y.push(dot.y);
52821         }
52822         if (t2 > 0 && t2 < 1) {
52823             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52824             x.push(dot.x);
52825             y.push(dot.y);
52826         }
52827         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
52828         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
52829         c = p1y - c1y;
52830         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52831         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52832         if (Math.abs(t1) > 1e12) {
52833             t1 = 0.5;
52834         }
52835         if (Math.abs(t2) > 1e12) {
52836             t2 = 0.5;
52837         }
52838         if (t1 > 0 && t1 < 1) {
52839             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52840             x.push(dot.x);
52841             y.push(dot.y);
52842         }
52843         if (t2 > 0 && t2 < 1) {
52844             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52845             x.push(dot.x);
52846             y.push(dot.y);
52847         }
52848         return {
52849             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
52850             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
52851         };
52852     },
52853
52854     /**
52855      * @private
52856      *
52857      * Calculates bezier curve control anchor points for a particular point in a path, with a
52858      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
52859      * Note that this algorithm assumes that the line being smoothed is normalized going from left
52860      * to right; it makes special adjustments assuming this orientation.
52861      *
52862      * @param {Number} prevX X coordinate of the previous point in the path
52863      * @param {Number} prevY Y coordinate of the previous point in the path
52864      * @param {Number} curX X coordinate of the current point in the path
52865      * @param {Number} curY Y coordinate of the current point in the path
52866      * @param {Number} nextX X coordinate of the next point in the path
52867      * @param {Number} nextY Y coordinate of the next point in the path
52868      * @param {Number} value A value to control the smoothness of the curve; this is used to
52869      * divide the distance between points, so a value of 2 corresponds to
52870      * half the distance between points (a very smooth line) while higher values
52871      * result in less smooth curves. Defaults to 4.
52872      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
52873      * are the control point for the curve toward the previous path point, and
52874      * x2 and y2 are the control point for the curve toward the next path point.
52875      */
52876     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
52877         value = value || 4;
52878         var M = Math,
52879             PI = M.PI,
52880             halfPI = PI / 2,
52881             abs = M.abs,
52882             sin = M.sin,
52883             cos = M.cos,
52884             atan = M.atan,
52885             control1Length, control2Length, control1Angle, control2Angle,
52886             control1X, control1Y, control2X, control2Y, alpha;
52887
52888         // Find the length of each control anchor line, by dividing the horizontal distance
52889         // between points by the value parameter.
52890         control1Length = (curX - prevX) / value;
52891         control2Length = (nextX - curX) / value;
52892
52893         // Determine the angle of each control anchor line. If the middle point is a vertical
52894         // turnaround then we force it to a flat horizontal angle to prevent the curve from
52895         // dipping above or below the middle point. Otherwise we use an angle that points
52896         // toward the previous/next target point.
52897         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
52898             control1Angle = control2Angle = halfPI;
52899         } else {
52900             control1Angle = atan((curX - prevX) / abs(curY - prevY));
52901             if (prevY < curY) {
52902                 control1Angle = PI - control1Angle;
52903             }
52904             control2Angle = atan((nextX - curX) / abs(curY - nextY));
52905             if (nextY < curY) {
52906                 control2Angle = PI - control2Angle;
52907             }
52908         }
52909
52910         // Adjust the calculated angles so they point away from each other on the same line
52911         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
52912         if (alpha > halfPI) {
52913             alpha -= PI;
52914         }
52915         control1Angle += alpha;
52916         control2Angle += alpha;
52917
52918         // Find the control anchor points from the angles and length
52919         control1X = curX - control1Length * sin(control1Angle);
52920         control1Y = curY + control1Length * cos(control1Angle);
52921         control2X = curX + control2Length * sin(control2Angle);
52922         control2Y = curY + control2Length * cos(control2Angle);
52923
52924         // One last adjustment, make sure that no control anchor point extends vertically past
52925         // its target prev/next point, as that results in curves dipping above or below and
52926         // bending back strangely. If we find this happening we keep the control angle but
52927         // reduce the length of the control line so it stays within bounds.
52928         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
52929             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
52930             control1Y = prevY;
52931         }
52932         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
52933             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
52934             control2Y = nextY;
52935         }
52936         
52937         return {
52938             x1: control1X,
52939             y1: control1Y,
52940             x2: control2X,
52941             y2: control2Y
52942         };
52943     },
52944
52945     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
52946      * Defaults to a value of 4.
52947      */
52948     smooth: function (originalPath, value) {
52949         var path = this.path2curve(originalPath),
52950             newp = [path[0]],
52951             x = path[0][1],
52952             y = path[0][2],
52953             j,
52954             points,
52955             i = 1,
52956             ii = path.length,
52957             beg = 1,
52958             mx = x,
52959             my = y,
52960             cx = 0,
52961             cy = 0;
52962         for (; i < ii; i++) {
52963             var pathi = path[i],
52964                 pathil = pathi.length,
52965                 pathim = path[i - 1],
52966                 pathiml = pathim.length,
52967                 pathip = path[i + 1],
52968                 pathipl = pathip && pathip.length;
52969             if (pathi[0] == "M") {
52970                 mx = pathi[1];
52971                 my = pathi[2];
52972                 j = i + 1;
52973                 while (path[j][0] != "C") {
52974                     j++;
52975                 }
52976                 cx = path[j][5];
52977                 cy = path[j][6];
52978                 newp.push(["M", mx, my]);
52979                 beg = newp.length;
52980                 x = mx;
52981                 y = my;
52982                 continue;
52983             }
52984             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
52985                 var begl = newp[beg].length;
52986                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
52987                 newp[beg][1] = points.x2;
52988                 newp[beg][2] = points.y2;
52989             }
52990             else if (!pathip || pathip[0] == "M") {
52991                 points = {
52992                     x1: pathi[pathil - 2],
52993                     y1: pathi[pathil - 1]
52994                 };
52995             } else {
52996                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
52997             }
52998             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
52999             x = points.x2;
53000             y = points.y2;
53001         }
53002         return newp;
53003     },
53004
53005     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
53006         var t1 = 1 - t;
53007         return {
53008             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
53009             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
53010         };
53011     },
53012
53013     /**
53014      * A utility method to deduce an appropriate tick configuration for the data set of given
53015      * feature.
53016      * 
53017      * @param {Number/Date} from The minimum value in the data
53018      * @param {Number/Date} to The maximum value in the data
53019      * @param {Number} stepsMax The maximum number of ticks
53020      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
53021      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
53022      * @return {Number} return.from The result start value, which may be lower than the original start value
53023      * @return {Number} return.to The result end value, which may be higher than the original end value
53024      * @return {Number} return.power The calculate power.
53025      * @return {Number} return.step The value size of each step
53026      * @return {Number} return.steps The number of steps.
53027      */
53028     snapEnds: function (from, to, stepsMax) {
53029         if (Ext.isDate(from)) {
53030             return this.snapEndsByDate(from, to, stepsMax);
53031         }
53032         var step = (to - from) / stepsMax,
53033             level = Math.floor(Math.log(step) / Math.LN10) + 1,
53034             m = Math.pow(10, level),
53035             cur,
53036             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
53037             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
53038             stepCount = 0,
53039             value,
53040             weight,
53041             i,
53042             topValue,
53043             topWeight = 1e9,
53044             ln = interval.length;
53045         cur = from = Math.floor(from / m) * m;
53046         for (i = 0; i < ln; i++) {
53047             value = interval[i][0];
53048             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
53049             if (weight < topWeight) {
53050                 topValue = value;
53051                 topWeight = weight;
53052             }
53053         }
53054         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
53055         while (cur < to) {
53056             cur += step;
53057             stepCount++;
53058         }
53059         to = +cur.toFixed(10);
53060         return {
53061             from: from,
53062             to: to,
53063             power: level,
53064             step: step,
53065             steps: stepCount
53066         };
53067     },
53068
53069     /**
53070      * A utility method to deduce an appropriate tick configuration for the data set of given
53071      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
53072      *
53073      * @param {Date} from The minimum value in the data
53074      * @param {Date} to The maximum value in the data
53075      * @param {Number} stepsMax The maximum number of ticks
53076      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53077      * and will not be adjusted
53078      * @return {Object} The calculated step and ends info; properties are:
53079      * @return {Date} return.from The result start value, which may be lower than the original start value
53080      * @return {Date} return.to The result end value, which may be higher than the original end value
53081      * @return {Number} return.step The value size of each step
53082      * @return {Number} return.steps The number of steps.
53083      * NOTE: the steps may not divide the from/to range perfectly evenly;
53084      * there may be a smaller distance between the last step and the end value than between prior
53085      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
53086      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
53087      * `from` to find the correct point for each tick.
53088      */
53089     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
53090         var selectedStep = false, scales = [
53091                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
53092                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
53093                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
53094                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
53095                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
53096                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
53097             ], j, yearDiff;
53098
53099         // Find the most desirable scale
53100         Ext.each(scales, function(scale, i) {
53101             for (j = 0; j < scale[1].length; j++) {
53102                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
53103                     selectedStep = [scale[0], scale[1][j]];
53104                     return false;
53105                 }
53106             }
53107         });
53108         if (!selectedStep) {
53109             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
53110             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
53111         }
53112         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
53113     },
53114
53115
53116     /**
53117      * A utility method to deduce an appropriate tick configuration for the data set of given
53118      * feature and specific step size.
53119      * @param {Date} from The minimum value in the data
53120      * @param {Date} to The maximum value in the data
53121      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
53122      * The second one is the number of units for the step (1, 2, etc.).
53123      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53124      * and will not be adjusted
53125      * @return {Object} See the return value of {@link #snapEndsByDate}.
53126      */
53127     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
53128         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
53129                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
53130             steps = 0, testFrom, testTo;
53131         if (lockEnds) {
53132             testFrom = from;
53133         } else {
53134             switch (step[0]) {
53135                 case Ext.Date.MILLI:
53136                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53137                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
53138                     break;
53139                 case Ext.Date.SECOND:
53140                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53141                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
53142                     break;
53143                 case Ext.Date.MINUTE:
53144                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53145                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
53146                     break;
53147                 case Ext.Date.HOUR:
53148                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
53149                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
53150                     break;
53151                 case Ext.Date.DAY:
53152                     testFrom = new Date(fromStat[0], fromStat[1],
53153                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
53154                     break;
53155                 case Ext.Date.MONTH:
53156                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
53157                     break;
53158                 default: // Ext.Date.YEAR
53159                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
53160                     break;
53161             }
53162         }
53163
53164         testTo = testFrom;
53165         // TODO(zhangbei) : We can do it better somehow...
53166         while (testTo < to) {
53167             testTo = Ext.Date.add(testTo, step[0], step[1]);
53168             steps++;
53169         }
53170
53171         if (lockEnds) {
53172             testTo = to;
53173         }
53174         return {
53175             from : +testFrom,
53176             to : +testTo,
53177             step : (testTo - testFrom) / steps,
53178             steps : steps
53179         };
53180     },
53181
53182     sorter: function (a, b) {
53183         return a.offset - b.offset;
53184     },
53185
53186     rad: function(degrees) {
53187         return degrees % 360 * Math.PI / 180;
53188     },
53189
53190     degrees: function(radian) {
53191         return radian * 180 / Math.PI % 360;
53192     },
53193
53194     withinBox: function(x, y, bbox) {
53195         bbox = bbox || {};
53196         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
53197     },
53198
53199     parseGradient: function(gradient) {
53200         var me = this,
53201             type = gradient.type || 'linear',
53202             angle = gradient.angle || 0,
53203             radian = me.radian,
53204             stops = gradient.stops,
53205             stopsArr = [],
53206             stop,
53207             vector,
53208             max,
53209             stopObj;
53210
53211         if (type == 'linear') {
53212             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
53213             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
53214             vector[2] *= max;
53215             vector[3] *= max;
53216             if (vector[2] < 0) {
53217                 vector[0] = -vector[2];
53218                 vector[2] = 0;
53219             }
53220             if (vector[3] < 0) {
53221                 vector[1] = -vector[3];
53222                 vector[3] = 0;
53223             }
53224         }
53225
53226         for (stop in stops) {
53227             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
53228                 stopObj = {
53229                     offset: parseInt(stop, 10),
53230                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
53231                     opacity: stops[stop].opacity || 1
53232                 };
53233                 stopsArr.push(stopObj);
53234             }
53235         }
53236         // Sort by pct property
53237         Ext.Array.sort(stopsArr, me.sorter);
53238         if (type == 'linear') {
53239             return {
53240                 id: gradient.id,
53241                 type: type,
53242                 vector: vector,
53243                 stops: stopsArr
53244             };
53245         }
53246         else {
53247             return {
53248                 id: gradient.id,
53249                 type: type,
53250                 centerX: gradient.centerX,
53251                 centerY: gradient.centerY,
53252                 focalX: gradient.focalX,
53253                 focalY: gradient.focalY,
53254                 radius: gradient.radius,
53255                 vector: vector,
53256                 stops: stopsArr
53257             };
53258         }
53259     }
53260 });
53261
53262
53263 /**
53264  * @class Ext.fx.PropertyHandler
53265  * @ignore
53266  */
53267 Ext.define('Ext.fx.PropertyHandler', {
53268
53269     /* Begin Definitions */
53270
53271     requires: ['Ext.draw.Draw'],
53272
53273     statics: {
53274         defaultHandler: {
53275             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
53276             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
53277             scrollRE: /^scroll/i,
53278
53279             computeDelta: function(from, end, damper, initial, attr) {
53280                 damper = (typeof damper == 'number') ? damper : 1;
53281                 var unitRE = this.unitRE,
53282                     match = unitRE.exec(from),
53283                     start, units;
53284                 if (match) {
53285                     from = match[1];
53286                     units = match[2];
53287                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
53288                         units = 'px';
53289                     }
53290                 }
53291                 from = +from || 0;
53292
53293                 match = unitRE.exec(end);
53294                 if (match) {
53295                     end = match[1];
53296                     units = match[2] || units;
53297                 }
53298                 end = +end || 0;
53299                 start = (initial != null) ? initial : from;
53300                 return {
53301                     from: from,
53302                     delta: (end - start) * damper,
53303                     units: units
53304                 };
53305             },
53306
53307             get: function(from, end, damper, initialFrom, attr) {
53308                 var ln = from.length,
53309                     out = [],
53310                     i, initial, res, j, len;
53311                 for (i = 0; i < ln; i++) {
53312                     if (initialFrom) {
53313                         initial = initialFrom[i][1].from;
53314                     }
53315                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
53316                         res = [];
53317                         j = 0;
53318                         len = from[i][1].length;
53319                         for (; j < len; j++) {
53320                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
53321                         }
53322                         out.push([from[i][0], res]);
53323                     }
53324                     else {
53325                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
53326                     }
53327                 }
53328                 return out;
53329             },
53330
53331             set: function(values, easing) {
53332                 var ln = values.length,
53333                     out = [],
53334                     i, val, res, len, j;
53335                 for (i = 0; i < ln; i++) {
53336                     val  = values[i][1];
53337                     if (Ext.isArray(val)) {
53338                         res = [];
53339                         j = 0;
53340                         len = val.length;
53341                         for (; j < len; j++) {
53342                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
53343                         }
53344                         out.push([values[i][0], res]);
53345                     } else {
53346                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
53347                     }
53348                 }
53349                 return out;
53350             }
53351         },
53352         color: {
53353             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
53354             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
53355             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
53356
53357             parseColor : function(color, damper) {
53358                 damper = (typeof damper == 'number') ? damper : 1;
53359                 var base,
53360                     out = false,
53361                     match;
53362
53363                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
53364                     base = (idx % 2 == 0) ? 16 : 10;
53365                     match = re.exec(color);
53366                     if (match && match.length == 4) {
53367                         if (idx == 2) {
53368                             match[1] += match[1];
53369                             match[2] += match[2];
53370                             match[3] += match[3];
53371                         }
53372                         out = {
53373                             red: parseInt(match[1], base),
53374                             green: parseInt(match[2], base),
53375                             blue: parseInt(match[3], base)
53376                         };
53377                         return false;
53378                     }
53379                 });
53380                 return out || color;
53381             },
53382
53383             computeDelta: function(from, end, damper, initial) {
53384                 from = this.parseColor(from);
53385                 end = this.parseColor(end, damper);
53386                 var start = initial ? initial : from,
53387                     tfrom = typeof start,
53388                     tend = typeof end;
53389                 //Extra check for when the color string is not recognized.
53390                 if (tfrom == 'string' ||  tfrom == 'undefined' 
53391                   || tend == 'string' || tend == 'undefined') {
53392                     return end || start;
53393                 }
53394                 return {
53395                     from:  from,
53396                     delta: {
53397                         red: Math.round((end.red - start.red) * damper),
53398                         green: Math.round((end.green - start.green) * damper),
53399                         blue: Math.round((end.blue - start.blue) * damper)
53400                     }
53401                 };
53402             },
53403
53404             get: function(start, end, damper, initialFrom) {
53405                 var ln = start.length,
53406                     out = [],
53407                     i, initial;
53408                 for (i = 0; i < ln; i++) {
53409                     if (initialFrom) {
53410                         initial = initialFrom[i][1].from;
53411                     }
53412                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53413                 }
53414                 return out;
53415             },
53416
53417             set: function(values, easing) {
53418                 var ln = values.length,
53419                     out = [],
53420                     i, val, parsedString, from, delta;
53421                 for (i = 0; i < ln; i++) {
53422                     val = values[i][1];
53423                     if (val) {
53424                         from = val.from;
53425                         delta = val.delta;
53426                         //multiple checks to reformat the color if it can't recognized by computeDelta.
53427                         val = (typeof val == 'object' && 'red' in val)? 
53428                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
53429                         val = (typeof val == 'object' && val.length)? val[0] : val;
53430                         if (typeof val == 'undefined') {
53431                             return [];
53432                         }
53433                         parsedString = typeof val == 'string'? val :
53434                             'rgb(' + [
53435                                   (from.red + Math.round(delta.red * easing)) % 256,
53436                                   (from.green + Math.round(delta.green * easing)) % 256,
53437                                   (from.blue + Math.round(delta.blue * easing)) % 256
53438                               ].join(',') + ')';
53439                         out.push([
53440                             values[i][0],
53441                             parsedString
53442                         ]);
53443                     }
53444                 }
53445                 return out;
53446             }
53447         },
53448         object: {
53449             interpolate: function(prop, damper) {
53450                 damper = (typeof damper == 'number') ? damper : 1;
53451                 var out = {},
53452                     p;
53453                 for(p in prop) {
53454                     out[p] = parseInt(prop[p], 10) * damper;
53455                 }
53456                 return out;
53457             },
53458
53459             computeDelta: function(from, end, damper, initial) {
53460                 from = this.interpolate(from);
53461                 end = this.interpolate(end, damper);
53462                 var start = initial ? initial : from,
53463                     delta = {},
53464                     p;
53465
53466                 for(p in end) {
53467                     delta[p] = end[p] - start[p];
53468                 }
53469                 return {
53470                     from:  from,
53471                     delta: delta
53472                 };
53473             },
53474
53475             get: function(start, end, damper, initialFrom) {
53476                 var ln = start.length,
53477                     out = [],
53478                     i, initial;
53479                 for (i = 0; i < ln; i++) {
53480                     if (initialFrom) {
53481                         initial = initialFrom[i][1].from;
53482                     }
53483                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53484                 }
53485                 return out;
53486             },
53487
53488             set: function(values, easing) {
53489                 var ln = values.length,
53490                     out = [],
53491                     outObject = {},
53492                     i, from, delta, val, p;
53493                 for (i = 0; i < ln; i++) {
53494                     val  = values[i][1];
53495                     from = val.from;
53496                     delta = val.delta;
53497                     for (p in from) {
53498                         outObject[p] = Math.round(from[p] + delta[p] * easing);
53499                     }
53500                     out.push([
53501                         values[i][0],
53502                         outObject
53503                     ]);
53504                 }
53505                 return out;
53506             }
53507         },
53508
53509         path: {
53510             computeDelta: function(from, end, damper, initial) {
53511                 damper = (typeof damper == 'number') ? damper : 1;
53512                 var start;
53513                 from = +from || 0;
53514                 end = +end || 0;
53515                 start = (initial != null) ? initial : from;
53516                 return {
53517                     from: from,
53518                     delta: (end - start) * damper
53519                 };
53520             },
53521
53522             forcePath: function(path) {
53523                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
53524                     path = Ext.draw.Draw.parsePathString(path);
53525                 }
53526                 return path;
53527             },
53528
53529             get: function(start, end, damper, initialFrom) {
53530                 var endPath = this.forcePath(end),
53531                     out = [],
53532                     startLn = start.length,
53533                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
53534                 for (i = 0; i < startLn; i++) {
53535                     startPath = this.forcePath(start[i][1]);
53536
53537                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
53538                     startPath = deltaPath[0];
53539                     endPath = deltaPath[1];
53540
53541                     startPathLn = startPath.length;
53542                     path = [];
53543                     for (j = 0; j < startPathLn; j++) {
53544                         deltaPath = [startPath[j][0]];
53545                         pointsLn = startPath[j].length;
53546                         for (k = 1; k < pointsLn; k++) {
53547                             initial = initialFrom && initialFrom[0][1][j][k].from;
53548                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
53549                         }
53550                         path.push(deltaPath);
53551                     }
53552                     out.push([start[i][0], path]);
53553                 }
53554                 return out;
53555             },
53556
53557             set: function(values, easing) {
53558                 var ln = values.length,
53559                     out = [],
53560                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
53561                 for (i = 0; i < ln; i++) {
53562                     deltaPath = values[i][1];
53563                     newPath = [];
53564                     deltaPathLn = deltaPath.length;
53565                     for (j = 0; j < deltaPathLn; j++) {
53566                         calcPath = [deltaPath[j][0]];
53567                         pointsLn = deltaPath[j].length;
53568                         for (k = 1; k < pointsLn; k++) {
53569                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
53570                         }
53571                         newPath.push(calcPath.join(','));
53572                     }
53573                     out.push([values[i][0], newPath.join(',')]);
53574                 }
53575                 return out;
53576             }
53577         }
53578         /* End Definitions */
53579     }
53580 }, function() {
53581     Ext.each([
53582         'outlineColor',
53583         'backgroundColor',
53584         'borderColor',
53585         'borderTopColor',
53586         'borderRightColor', 
53587         'borderBottomColor', 
53588         'borderLeftColor',
53589         'fill',
53590         'stroke'
53591     ], function(prop) {
53592         this[prop] = this.color;
53593     }, this);
53594 });
53595 /**
53596  * @class Ext.fx.Anim
53597  *
53598  * This class manages animation for a specific {@link #target}. The animation allows
53599  * animation of various properties on the target, such as size, position, color and others.
53600  *
53601  * ## Starting Conditions
53602  * The starting conditions for the animation are provided by the {@link #from} configuration.
53603  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
53604  * property is not defined, the starting value for that property will be read directly from the target.
53605  *
53606  * ## End Conditions
53607  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
53608  * the final values once the animations has finished. The values in the {@link #from} can mirror
53609  * those in the {@link #to} configuration to provide a starting point.
53610  *
53611  * ## Other Options
53612  *  - {@link #duration}: Specifies the time period of the animation.
53613  *  - {@link #easing}: Specifies the easing of the animation.
53614  *  - {@link #iterations}: Allows the animation to repeat a number of times.
53615  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
53616  *
53617  * ## Example Code
53618  *
53619  *     @example
53620  *     var myComponent = Ext.create('Ext.Component', {
53621  *         renderTo: document.body,
53622  *         width: 200,
53623  *         height: 200,
53624  *         style: 'border: 1px solid red;'
53625  *     });
53626  *
53627  *     Ext.create('Ext.fx.Anim', {
53628  *         target: myComponent,
53629  *         duration: 1000,
53630  *         from: {
53631  *             width: 400 //starting width 400
53632  *         },
53633  *         to: {
53634  *             width: 300, //end width 300
53635  *             height: 300 // end width 300
53636  *         }
53637  *     });
53638  */
53639 Ext.define('Ext.fx.Anim', {
53640
53641     /* Begin Definitions */
53642
53643     mixins: {
53644         observable: 'Ext.util.Observable'
53645     },
53646
53647     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
53648
53649     /* End Definitions */
53650
53651     isAnimation: true,
53652
53653     /**
53654      * @cfg {Function} callback
53655      * A function to be run after the animation has completed.
53656      */
53657
53658     /**
53659      * @cfg {Function} scope
53660      * The scope that the {@link #callback} function will be called with
53661      */
53662
53663     /**
53664      * @cfg {Number} duration
53665      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
53666      * specified, then each animate will take the same duration for each iteration.
53667      */
53668     duration: 250,
53669
53670     /**
53671      * @cfg {Number} delay
53672      * Time to delay before starting the animation. Defaults to 0.
53673      */
53674     delay: 0,
53675
53676     /* private used to track a delayed starting time */
53677     delayStart: 0,
53678
53679     /**
53680      * @cfg {Boolean} dynamic
53681      * Currently only for Component Animation: Only set a component's outer element size bypassing layouts.  Set to true to do full layouts for every frame of the animation.  Defaults to false.
53682      */
53683     dynamic: false,
53684
53685     /**
53686      * @cfg {String} easing
53687 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
53688 speed over its duration.
53689
53690          -backIn
53691          -backOut
53692          -bounceIn
53693          -bounceOut
53694          -ease
53695          -easeIn
53696          -easeOut
53697          -easeInOut
53698          -elasticIn
53699          -elasticOut
53700          -cubic-bezier(x1, y1, x2, y2)
53701
53702 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
53703 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
53704 be in the range [0, 1] or the definition is invalid.
53705
53706 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
53707
53708      * @markdown
53709      */
53710     easing: 'ease',
53711
53712      /**
53713       * @cfg {Object} keyframes
53714       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
53715       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
53716       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
53717       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
53718       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
53719  <pre><code>
53720 keyframes : {
53721     '0%': {
53722         left: 100
53723     },
53724     '40%': {
53725         left: 150
53726     },
53727     '60%': {
53728         left: 75
53729     },
53730     '100%': {
53731         left: 100
53732     }
53733 }
53734  </code></pre>
53735       */
53736
53737     /**
53738      * @private
53739      */
53740     damper: 1,
53741
53742     /**
53743      * @private
53744      */
53745     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
53746
53747     /**
53748      * Run the animation from the end to the beginning
53749      * Defaults to false.
53750      * @cfg {Boolean} reverse
53751      */
53752     reverse: false,
53753
53754     /**
53755      * Flag to determine if the animation has started
53756      * @property running
53757      * @type Boolean
53758      */
53759     running: false,
53760
53761     /**
53762      * Flag to determine if the animation is paused. Only set this to true if you need to
53763      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
53764      * @property paused
53765      * @type Boolean
53766      */
53767     paused: false,
53768
53769     /**
53770      * Number of times to execute the animation. Defaults to 1.
53771      * @cfg {Number} iterations
53772      */
53773     iterations: 1,
53774
53775     /**
53776      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
53777      * @cfg {Boolean} alternate
53778      * Defaults to false.
53779      */
53780     alternate: false,
53781
53782     /**
53783      * Current iteration the animation is running.
53784      * @property currentIteration
53785      * @type Number
53786      */
53787     currentIteration: 0,
53788
53789     /**
53790      * Starting time of the animation.
53791      * @property startTime
53792      * @type Date
53793      */
53794     startTime: 0,
53795
53796     /**
53797      * Contains a cache of the interpolators to be used.
53798      * @private
53799      * @property propHandlers
53800      * @type Object
53801      */
53802
53803     /**
53804      * @cfg {String/Object} target
53805      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
53806      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
53807      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
53808      * automatically.
53809      */
53810
53811     /**
53812      * @cfg {Object} from
53813      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
53814      * Ext.fx.target will be used. For example:
53815 <pre><code>
53816 from : {
53817     opacity: 0,       // Transparent
53818     color: '#ffffff', // White
53819     left: 0
53820 }
53821 </code></pre>
53822      */
53823
53824     /**
53825      * @cfg {Object} to
53826      * An object containing property/value pairs for the end of the animation. For example:
53827  <pre><code>
53828  to : {
53829      opacity: 1,       // Opaque
53830      color: '#00ff00', // Green
53831      left: 500
53832  }
53833  </code></pre>
53834      */
53835
53836     // @private
53837     constructor: function(config) {
53838         var me = this,
53839             curve;
53840             
53841         config = config || {};
53842         // If keyframes are passed, they really want an Animator instead.
53843         if (config.keyframes) {
53844             return Ext.create('Ext.fx.Animator', config);
53845         }
53846         config = Ext.apply(me, config);
53847         if (me.from === undefined) {
53848             me.from = {};
53849         }
53850         me.propHandlers = {};
53851         me.config = config;
53852         me.target = Ext.fx.Manager.createTarget(me.target);
53853         me.easingFn = Ext.fx.Easing[me.easing];
53854         me.target.dynamic = me.dynamic;
53855
53856         // If not a pre-defined curve, try a cubic-bezier
53857         if (!me.easingFn) {
53858             me.easingFn = String(me.easing).match(me.bezierRE);
53859             if (me.easingFn && me.easingFn.length == 5) {
53860                 curve = me.easingFn;
53861                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
53862             }
53863         }
53864         me.id = Ext.id(null, 'ext-anim-');
53865         Ext.fx.Manager.addAnim(me);
53866         me.addEvents(
53867             /**
53868              * @event beforeanimate
53869              * Fires before the animation starts. A handler can return false to cancel the animation.
53870              * @param {Ext.fx.Anim} this
53871              */
53872             'beforeanimate',
53873              /**
53874               * @event afteranimate
53875               * Fires when the animation is complete.
53876               * @param {Ext.fx.Anim} this
53877               * @param {Date} startTime
53878               */
53879             'afteranimate',
53880              /**
53881               * @event lastframe
53882               * Fires when the animation's last frame has been set.
53883               * @param {Ext.fx.Anim} this
53884               * @param {Date} startTime
53885               */
53886             'lastframe'
53887         );
53888         me.mixins.observable.constructor.call(me, config);
53889         if (config.callback) {
53890             me.on('afteranimate', config.callback, config.scope);
53891         }
53892         return me;
53893     },
53894
53895     /**
53896      * @private
53897      * Helper to the target
53898      */
53899     setAttr: function(attr, value) {
53900         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
53901     },
53902
53903     /**
53904      * @private
53905      * Set up the initial currentAttrs hash.
53906      */
53907     initAttrs: function() {
53908         var me = this,
53909             from = me.from,
53910             to = me.to,
53911             initialFrom = me.initialFrom || {},
53912             out = {},
53913             start, end, propHandler, attr;
53914
53915         for (attr in to) {
53916             if (to.hasOwnProperty(attr)) {
53917                 start = me.target.getAttr(attr, from[attr]);
53918                 end = to[attr];
53919                 // Use default (numeric) property handler
53920                 if (!Ext.fx.PropertyHandler[attr]) {
53921                     if (Ext.isObject(end)) {
53922                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
53923                     } else {
53924                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
53925                     }
53926                 }
53927                 // Use custom handler
53928                 else {
53929                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
53930                 }
53931                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
53932             }
53933         }
53934         me.currentAttrs = out;
53935     },
53936
53937     /**
53938      * @private
53939      * Fires beforeanimate and sets the running flag.
53940      */
53941     start: function(startTime) {
53942         var me = this,
53943             delay = me.delay,
53944             delayStart = me.delayStart,
53945             delayDelta;
53946         if (delay) {
53947             if (!delayStart) {
53948                 me.delayStart = startTime;
53949                 return;
53950             }
53951             else {
53952                 delayDelta = startTime - delayStart;
53953                 if (delayDelta < delay) {
53954                     return;
53955                 }
53956                 else {
53957                     // Compensate for frame delay;
53958                     startTime = new Date(delayStart.getTime() + delay);
53959                 }
53960             }
53961         }
53962         if (me.fireEvent('beforeanimate', me) !== false) {
53963             me.startTime = startTime;
53964             if (!me.paused && !me.currentAttrs) {
53965                 me.initAttrs();
53966             }
53967             me.running = true;
53968         }
53969     },
53970
53971     /**
53972      * @private
53973      * Calculate attribute value at the passed timestamp.
53974      * @returns a hash of the new attributes.
53975      */
53976     runAnim: function(elapsedTime) {
53977         var me = this,
53978             attrs = me.currentAttrs,
53979             duration = me.duration,
53980             easingFn = me.easingFn,
53981             propHandlers = me.propHandlers,
53982             ret = {},
53983             easing, values, attr, lastFrame;
53984
53985         if (elapsedTime >= duration) {
53986             elapsedTime = duration;
53987             lastFrame = true;
53988         }
53989         if (me.reverse) {
53990             elapsedTime = duration - elapsedTime;
53991         }
53992
53993         for (attr in attrs) {
53994             if (attrs.hasOwnProperty(attr)) {
53995                 values = attrs[attr];
53996                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
53997                 ret[attr] = propHandlers[attr].set(values, easing);
53998             }
53999         }
54000         return ret;
54001     },
54002
54003     /**
54004      * @private
54005      * Perform lastFrame cleanup and handle iterations
54006      * @returns a hash of the new attributes.
54007      */
54008     lastFrame: function() {
54009         var me = this,
54010             iter = me.iterations,
54011             iterCount = me.currentIteration;
54012
54013         iterCount++;
54014         if (iterCount < iter) {
54015             if (me.alternate) {
54016                 me.reverse = !me.reverse;
54017             }
54018             me.startTime = new Date();
54019             me.currentIteration = iterCount;
54020             // Turn off paused for CSS3 Transitions
54021             me.paused = false;
54022         }
54023         else {
54024             me.currentIteration = 0;
54025             me.end();
54026             me.fireEvent('lastframe', me, me.startTime);
54027         }
54028     },
54029
54030     /**
54031      * Fire afteranimate event and end the animation. Usually called automatically when the
54032      * animation reaches its final frame, but can also be called manually to pre-emptively
54033      * stop and destroy the running animation.
54034      */
54035     end: function() {
54036         var me = this;
54037         me.startTime = 0;
54038         me.paused = false;
54039         me.running = false;
54040         Ext.fx.Manager.removeAnim(me);
54041         me.fireEvent('afteranimate', me, me.startTime);
54042     }
54043 });
54044 // Set flag to indicate that Fx is available. Class might not be available immediately.
54045 Ext.enableFx = true;
54046
54047 /*
54048  * This is a derivative of the similarly named class in the YUI Library.
54049  * The original license:
54050  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
54051  * Code licensed under the BSD License:
54052  * http://developer.yahoo.net/yui/license.txt
54053  */
54054
54055
54056 /**
54057  * Defines the interface and base operation of items that that can be
54058  * dragged or can be drop targets.  It was designed to be extended, overriding
54059  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
54060  * Up to three html elements can be associated with a DragDrop instance:
54061  *
54062  * - linked element: the element that is passed into the constructor.
54063  *   This is the element which defines the boundaries for interaction with
54064  *   other DragDrop objects.
54065  *
54066  * - handle element(s): The drag operation only occurs if the element that
54067  *   was clicked matches a handle element.  By default this is the linked
54068  *   element, but there are times that you will want only a portion of the
54069  *   linked element to initiate the drag operation, and the setHandleElId()
54070  *   method provides a way to define this.
54071  *
54072  * - drag element: this represents the element that would be moved along
54073  *   with the cursor during a drag operation.  By default, this is the linked
54074  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
54075  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
54076  *
54077  * This class should not be instantiated until the onload event to ensure that
54078  * the associated elements are available.
54079  * The following would define a DragDrop obj that would interact with any
54080  * other DragDrop obj in the "group1" group:
54081  *
54082  *     dd = new Ext.dd.DragDrop("div1", "group1");
54083  *
54084  * Since none of the event handlers have been implemented, nothing would
54085  * actually happen if you were to run the code above.  Normally you would
54086  * override this class or one of the default implementations, but you can
54087  * also override the methods you want on an instance of the class...
54088  *
54089  *     dd.onDragDrop = function(e, id) {
54090  *         alert("dd was dropped on " + id);
54091  *     }
54092  *
54093  */
54094 Ext.define('Ext.dd.DragDrop', {
54095     requires: ['Ext.dd.DragDropManager'],
54096
54097     /**
54098      * Creates new DragDrop.
54099      * @param {String} id of the element that is linked to this instance
54100      * @param {String} sGroup the group of related DragDrop objects
54101      * @param {Object} config an object containing configurable attributes.
54102      * Valid properties for DragDrop:
54103      *
54104      * - padding
54105      * - isTarget
54106      * - maintainOffset
54107      * - primaryButtonOnly
54108      */
54109     constructor: function(id, sGroup, config) {
54110         if(id) {
54111             this.init(id, sGroup, config);
54112         }
54113     },
54114
54115     /**
54116      * Set to false to enable a DragDrop object to fire drag events while dragging
54117      * over its own Element. Defaults to true - DragDrop objects do not by default
54118      * fire drag events to themselves.
54119      * @property ignoreSelf
54120      * @type Boolean
54121      */
54122
54123     /**
54124      * The id of the element associated with this object.  This is what we
54125      * refer to as the "linked element" because the size and position of
54126      * this element is used to determine when the drag and drop objects have
54127      * interacted.
54128      * @property id
54129      * @type String
54130      */
54131     id: null,
54132
54133     /**
54134      * Configuration attributes passed into the constructor
54135      * @property config
54136      * @type Object
54137      */
54138     config: null,
54139
54140     /**
54141      * The id of the element that will be dragged.  By default this is same
54142      * as the linked element, but could be changed to another element. Ex:
54143      * Ext.dd.DDProxy
54144      * @property dragElId
54145      * @type String
54146      * @private
54147      */
54148     dragElId: null,
54149
54150     /**
54151      * The ID of the element that initiates the drag operation.  By default
54152      * this is the linked element, but could be changed to be a child of this
54153      * element.  This lets us do things like only starting the drag when the
54154      * header element within the linked html element is clicked.
54155      * @property handleElId
54156      * @type String
54157      * @private
54158      */
54159     handleElId: null,
54160
54161     /**
54162      * An object who's property names identify HTML tags to be considered invalid as drag handles.
54163      * A non-null property value identifies the tag as invalid. Defaults to the
54164      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
54165 {
54166     A: "A"
54167 }</code></pre>
54168      * @property invalidHandleTypes
54169      * @type Object
54170      */
54171     invalidHandleTypes: null,
54172
54173     /**
54174      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
54175      * A non-null property value identifies the ID as invalid. For example, to prevent
54176      * dragging from being initiated on element ID "foo", use:<pre><code>
54177 {
54178     foo: true
54179 }</code></pre>
54180      * @property invalidHandleIds
54181      * @type Object
54182      */
54183     invalidHandleIds: null,
54184
54185     /**
54186      * An Array of CSS class names for elements to be considered in valid as drag handles.
54187      * @property {String[]} invalidHandleClasses
54188      */
54189     invalidHandleClasses: null,
54190
54191     /**
54192      * The linked element's absolute X position at the time the drag was
54193      * started
54194      * @property startPageX
54195      * @type Number
54196      * @private
54197      */
54198     startPageX: 0,
54199
54200     /**
54201      * The linked element's absolute X position at the time the drag was
54202      * started
54203      * @property startPageY
54204      * @type Number
54205      * @private
54206      */
54207     startPageY: 0,
54208
54209     /**
54210      * The group defines a logical collection of DragDrop objects that are
54211      * related.  Instances only get events when interacting with other
54212      * DragDrop object in the same group.  This lets us define multiple
54213      * groups using a single DragDrop subclass if we want.
54214      * @property groups
54215      * @type Object An object in the format {'group1':true, 'group2':true}
54216      */
54217     groups: null,
54218
54219     /**
54220      * Individual drag/drop instances can be locked.  This will prevent
54221      * onmousedown start drag.
54222      * @property locked
54223      * @type Boolean
54224      * @private
54225      */
54226     locked: false,
54227
54228     /**
54229      * Locks this instance
54230      */
54231     lock: function() {
54232         this.locked = true;
54233     },
54234
54235     /**
54236      * When set to true, other DD objects in cooperating DDGroups do not receive
54237      * notification events when this DD object is dragged over them. Defaults to false.
54238      * @property moveOnly
54239      * @type Boolean
54240      */
54241     moveOnly: false,
54242
54243     /**
54244      * Unlocks this instace
54245      */
54246     unlock: function() {
54247         this.locked = false;
54248     },
54249
54250     /**
54251      * By default, all instances can be a drop target.  This can be disabled by
54252      * setting isTarget to false.
54253      * @property isTarget
54254      * @type Boolean
54255      */
54256     isTarget: true,
54257
54258     /**
54259      * The padding configured for this drag and drop object for calculating
54260      * the drop zone intersection with this object.
54261      * An array containing the 4 padding values: [top, right, bottom, left]
54262      * @property {Number[]} padding
54263      */
54264     padding: null,
54265
54266     /**
54267      * Cached reference to the linked element
54268      * @property _domRef
54269      * @private
54270      */
54271     _domRef: null,
54272
54273     /**
54274      * Internal typeof flag
54275      * @property __ygDragDrop
54276      * @private
54277      */
54278     __ygDragDrop: true,
54279
54280     /**
54281      * Set to true when horizontal contraints are applied
54282      * @property constrainX
54283      * @type Boolean
54284      * @private
54285      */
54286     constrainX: false,
54287
54288     /**
54289      * Set to true when vertical contraints are applied
54290      * @property constrainY
54291      * @type Boolean
54292      * @private
54293      */
54294     constrainY: false,
54295
54296     /**
54297      * The left constraint
54298      * @property minX
54299      * @type Number
54300      * @private
54301      */
54302     minX: 0,
54303
54304     /**
54305      * The right constraint
54306      * @property maxX
54307      * @type Number
54308      * @private
54309      */
54310     maxX: 0,
54311
54312     /**
54313      * The up constraint
54314      * @property minY
54315      * @type Number
54316      * @private
54317      */
54318     minY: 0,
54319
54320     /**
54321      * The down constraint
54322      * @property maxY
54323      * @type Number
54324      * @private
54325      */
54326     maxY: 0,
54327
54328     /**
54329      * Maintain offsets when we resetconstraints.  Set to true when you want
54330      * the position of the element relative to its parent to stay the same
54331      * when the page changes
54332      *
54333      * @property maintainOffset
54334      * @type Boolean
54335      */
54336     maintainOffset: false,
54337
54338     /**
54339      * Array of pixel locations the element will snap to if we specified a
54340      * horizontal graduation/interval.  This array is generated automatically
54341      * when you define a tick interval.
54342      * @property {Number[]} xTicks
54343      */
54344     xTicks: null,
54345
54346     /**
54347      * Array of pixel locations the element will snap to if we specified a
54348      * vertical graduation/interval.  This array is generated automatically
54349      * when you define a tick interval.
54350      * @property {Number[]} yTicks
54351      */
54352     yTicks: null,
54353
54354     /**
54355      * By default the drag and drop instance will only respond to the primary
54356      * button click (left button for a right-handed mouse).  Set to true to
54357      * allow drag and drop to start with any mouse click that is propogated
54358      * by the browser
54359      * @property primaryButtonOnly
54360      * @type Boolean
54361      */
54362     primaryButtonOnly: true,
54363
54364     /**
54365      * The available property is false until the linked dom element is accessible.
54366      * @property available
54367      * @type Boolean
54368      */
54369     available: false,
54370
54371     /**
54372      * By default, drags can only be initiated if the mousedown occurs in the
54373      * region the linked element is.  This is done in part to work around a
54374      * bug in some browsers that mis-report the mousedown if the previous
54375      * mouseup happened outside of the window.  This property is set to true
54376      * if outer handles are defined. Defaults to false.
54377      *
54378      * @property hasOuterHandles
54379      * @type Boolean
54380      */
54381     hasOuterHandles: false,
54382
54383     /**
54384      * Code that executes immediately before the startDrag event
54385      * @private
54386      */
54387     b4StartDrag: function(x, y) { },
54388
54389     /**
54390      * Abstract method called after a drag/drop object is clicked
54391      * and the drag or mousedown time thresholds have beeen met.
54392      * @param {Number} X click location
54393      * @param {Number} Y click location
54394      */
54395     startDrag: function(x, y) { /* override this */ },
54396
54397     /**
54398      * Code that executes immediately before the onDrag event
54399      * @private
54400      */
54401     b4Drag: function(e) { },
54402
54403     /**
54404      * Abstract method called during the onMouseMove event while dragging an
54405      * object.
54406      * @param {Event} e the mousemove event
54407      */
54408     onDrag: function(e) { /* override this */ },
54409
54410     /**
54411      * Abstract method called when this element fist begins hovering over
54412      * another DragDrop obj
54413      * @param {Event} e the mousemove event
54414      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54415      * id this is hovering over.  In INTERSECT mode, an array of one or more
54416      * dragdrop items being hovered over.
54417      */
54418     onDragEnter: function(e, id) { /* override this */ },
54419
54420     /**
54421      * Code that executes immediately before the onDragOver event
54422      * @private
54423      */
54424     b4DragOver: function(e) { },
54425
54426     /**
54427      * Abstract method called when this element is hovering over another
54428      * DragDrop obj
54429      * @param {Event} e the mousemove event
54430      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54431      * id this is hovering over.  In INTERSECT mode, an array of dd items
54432      * being hovered over.
54433      */
54434     onDragOver: function(e, id) { /* override this */ },
54435
54436     /**
54437      * Code that executes immediately before the onDragOut event
54438      * @private
54439      */
54440     b4DragOut: function(e) { },
54441
54442     /**
54443      * Abstract method called when we are no longer hovering over an element
54444      * @param {Event} e the mousemove event
54445      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54446      * id this was hovering over.  In INTERSECT mode, an array of dd items
54447      * that the mouse is no longer over.
54448      */
54449     onDragOut: function(e, id) { /* override this */ },
54450
54451     /**
54452      * Code that executes immediately before the onDragDrop event
54453      * @private
54454      */
54455     b4DragDrop: function(e) { },
54456
54457     /**
54458      * Abstract method called when this item is dropped on another DragDrop
54459      * obj
54460      * @param {Event} e the mouseup event
54461      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54462      * id this was dropped on.  In INTERSECT mode, an array of dd items this
54463      * was dropped on.
54464      */
54465     onDragDrop: function(e, id) { /* override this */ },
54466
54467     /**
54468      * Abstract method called when this item is dropped on an area with no
54469      * drop target
54470      * @param {Event} e the mouseup event
54471      */
54472     onInvalidDrop: function(e) { /* override this */ },
54473
54474     /**
54475      * Code that executes immediately before the endDrag event
54476      * @private
54477      */
54478     b4EndDrag: function(e) { },
54479
54480     /**
54481      * Called when we are done dragging the object
54482      * @param {Event} e the mouseup event
54483      */
54484     endDrag: function(e) { /* override this */ },
54485
54486     /**
54487      * Code executed immediately before the onMouseDown event
54488      * @param {Event} e the mousedown event
54489      * @private
54490      */
54491     b4MouseDown: function(e) {  },
54492
54493     /**
54494      * Called when a drag/drop obj gets a mousedown
54495      * @param {Event} e the mousedown event
54496      */
54497     onMouseDown: function(e) { /* override this */ },
54498
54499     /**
54500      * Called when a drag/drop obj gets a mouseup
54501      * @param {Event} e the mouseup event
54502      */
54503     onMouseUp: function(e) { /* override this */ },
54504
54505     /**
54506      * Override the onAvailable method to do what is needed after the initial
54507      * position was determined.
54508      */
54509     onAvailable: function () {
54510     },
54511
54512     /**
54513      * @property {Object} defaultPadding
54514      * Provides default constraint padding to "constrainTo" elements.
54515      */
54516     defaultPadding: {
54517         left: 0,
54518         right: 0,
54519         top: 0,
54520         bottom: 0
54521     },
54522
54523     /**
54524      * Initializes the drag drop object's constraints to restrict movement to a certain element.
54525      *
54526      * Usage:
54527      *
54528      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
54529      *                    { dragElId: "existingProxyDiv" });
54530      *     dd.startDrag = function(){
54531      *         this.constrainTo("parent-id");
54532      *     };
54533      *
54534      * Or you can initalize it using the {@link Ext.Element} object:
54535      *
54536      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
54537      *         startDrag : function(){
54538      *             this.constrainTo("parent-id");
54539      *         }
54540      *     });
54541      *
54542      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
54543      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
54544      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
54545      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
54546      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
54547      */
54548     constrainTo : function(constrainTo, pad, inContent){
54549         if(Ext.isNumber(pad)){
54550             pad = {left: pad, right:pad, top:pad, bottom:pad};
54551         }
54552         pad = pad || this.defaultPadding;
54553         var b = Ext.get(this.getEl()).getBox(),
54554             ce = Ext.get(constrainTo),
54555             s = ce.getScroll(),
54556             c,
54557             cd = ce.dom;
54558         if(cd == document.body){
54559             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
54560         }else{
54561             var xy = ce.getXY();
54562             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
54563         }
54564
54565
54566         var topSpace = b.y - c.y,
54567             leftSpace = b.x - c.x;
54568
54569         this.resetConstraints();
54570         this.setXConstraint(leftSpace - (pad.left||0), // left
54571                 c.width - leftSpace - b.width - (pad.right||0), //right
54572                                 this.xTickSize
54573         );
54574         this.setYConstraint(topSpace - (pad.top||0), //top
54575                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
54576                                 this.yTickSize
54577         );
54578     },
54579
54580     /**
54581      * Returns a reference to the linked element
54582      * @return {HTMLElement} the html element
54583      */
54584     getEl: function() {
54585         if (!this._domRef) {
54586             this._domRef = Ext.getDom(this.id);
54587         }
54588
54589         return this._domRef;
54590     },
54591
54592     /**
54593      * Returns a reference to the actual element to drag.  By default this is
54594      * the same as the html element, but it can be assigned to another
54595      * element. An example of this can be found in Ext.dd.DDProxy
54596      * @return {HTMLElement} the html element
54597      */
54598     getDragEl: function() {
54599         return Ext.getDom(this.dragElId);
54600     },
54601
54602     /**
54603      * Sets up the DragDrop object.  Must be called in the constructor of any
54604      * Ext.dd.DragDrop subclass
54605      * @param {String} id the id of the linked element
54606      * @param {String} sGroup the group of related items
54607      * @param {Object} config configuration attributes
54608      */
54609     init: function(id, sGroup, config) {
54610         this.initTarget(id, sGroup, config);
54611         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
54612         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
54613     },
54614
54615     /**
54616      * Initializes Targeting functionality only... the object does not
54617      * get a mousedown handler.
54618      * @param {String} id the id of the linked element
54619      * @param {String} sGroup the group of related items
54620      * @param {Object} config configuration attributes
54621      */
54622     initTarget: function(id, sGroup, config) {
54623         // configuration attributes
54624         this.config = config || {};
54625
54626         // create a local reference to the drag and drop manager
54627         this.DDMInstance = Ext.dd.DragDropManager;
54628         // initialize the groups array
54629         this.groups = {};
54630
54631         // assume that we have an element reference instead of an id if the
54632         // parameter is not a string
54633         if (typeof id !== "string") {
54634             id = Ext.id(id);
54635         }
54636
54637         // set the id
54638         this.id = id;
54639
54640         // add to an interaction group
54641         this.addToGroup((sGroup) ? sGroup : "default");
54642
54643         // We don't want to register this as the handle with the manager
54644         // so we just set the id rather than calling the setter.
54645         this.handleElId = id;
54646
54647         // the linked element is the element that gets dragged by default
54648         this.setDragElId(id);
54649
54650         // by default, clicked anchors will not start drag operations.
54651         this.invalidHandleTypes = { A: "A" };
54652         this.invalidHandleIds = {};
54653         this.invalidHandleClasses = [];
54654
54655         this.applyConfig();
54656
54657         this.handleOnAvailable();
54658     },
54659
54660     /**
54661      * Applies the configuration parameters that were passed into the constructor.
54662      * This is supposed to happen at each level through the inheritance chain.  So
54663      * a DDProxy implentation will execute apply config on DDProxy, DD, and
54664      * DragDrop in order to get all of the parameters that are available in
54665      * each object.
54666      */
54667     applyConfig: function() {
54668
54669         // configurable properties:
54670         //    padding, isTarget, maintainOffset, primaryButtonOnly
54671         this.padding           = this.config.padding || [0, 0, 0, 0];
54672         this.isTarget          = (this.config.isTarget !== false);
54673         this.maintainOffset    = (this.config.maintainOffset);
54674         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
54675
54676     },
54677
54678     /**
54679      * Executed when the linked element is available
54680      * @private
54681      */
54682     handleOnAvailable: function() {
54683         this.available = true;
54684         this.resetConstraints();
54685         this.onAvailable();
54686     },
54687
54688     /**
54689      * Configures the padding for the target zone in px.  Effectively expands
54690      * (or reduces) the virtual object size for targeting calculations.
54691      * Supports css-style shorthand; if only one parameter is passed, all sides
54692      * will have that padding, and if only two are passed, the top and bottom
54693      * will have the first param, the left and right the second.
54694      * @param {Number} iTop    Top pad
54695      * @param {Number} iRight  Right pad
54696      * @param {Number} iBot    Bot pad
54697      * @param {Number} iLeft   Left pad
54698      */
54699     setPadding: function(iTop, iRight, iBot, iLeft) {
54700         // this.padding = [iLeft, iRight, iTop, iBot];
54701         if (!iRight && 0 !== iRight) {
54702             this.padding = [iTop, iTop, iTop, iTop];
54703         } else if (!iBot && 0 !== iBot) {
54704             this.padding = [iTop, iRight, iTop, iRight];
54705         } else {
54706             this.padding = [iTop, iRight, iBot, iLeft];
54707         }
54708     },
54709
54710     /**
54711      * Stores the initial placement of the linked element.
54712      * @param {Number} diffX   the X offset, default 0
54713      * @param {Number} diffY   the Y offset, default 0
54714      */
54715     setInitPosition: function(diffX, diffY) {
54716         var el = this.getEl();
54717
54718         if (!this.DDMInstance.verifyEl(el)) {
54719             return;
54720         }
54721
54722         var dx = diffX || 0;
54723         var dy = diffY || 0;
54724
54725         var p = Ext.Element.getXY( el );
54726
54727         this.initPageX = p[0] - dx;
54728         this.initPageY = p[1] - dy;
54729
54730         this.lastPageX = p[0];
54731         this.lastPageY = p[1];
54732
54733         this.setStartPosition(p);
54734     },
54735
54736     /**
54737      * Sets the start position of the element.  This is set when the obj
54738      * is initialized, the reset when a drag is started.
54739      * @param pos current position (from previous lookup)
54740      * @private
54741      */
54742     setStartPosition: function(pos) {
54743         var p = pos || Ext.Element.getXY( this.getEl() );
54744         this.deltaSetXY = null;
54745
54746         this.startPageX = p[0];
54747         this.startPageY = p[1];
54748     },
54749
54750     /**
54751      * Adds this instance to a group of related drag/drop objects.  All
54752      * instances belong to at least one group, and can belong to as many
54753      * groups as needed.
54754      * @param {String} sGroup the name of the group
54755      */
54756     addToGroup: function(sGroup) {
54757         this.groups[sGroup] = true;
54758         this.DDMInstance.regDragDrop(this, sGroup);
54759     },
54760
54761     /**
54762      * Removes this instance from the supplied interaction group
54763      * @param {String} sGroup  The group to drop
54764      */
54765     removeFromGroup: function(sGroup) {
54766         if (this.groups[sGroup]) {
54767             delete this.groups[sGroup];
54768         }
54769
54770         this.DDMInstance.removeDDFromGroup(this, sGroup);
54771     },
54772
54773     /**
54774      * Allows you to specify that an element other than the linked element
54775      * will be moved with the cursor during a drag
54776      * @param {String} id the id of the element that will be used to initiate the drag
54777      */
54778     setDragElId: function(id) {
54779         this.dragElId = id;
54780     },
54781
54782     /**
54783      * Allows you to specify a child of the linked element that should be
54784      * used to initiate the drag operation.  An example of this would be if
54785      * you have a content div with text and links.  Clicking anywhere in the
54786      * content area would normally start the drag operation.  Use this method
54787      * to specify that an element inside of the content div is the element
54788      * that starts the drag operation.
54789      * @param {String} id the id of the element that will be used to
54790      * initiate the drag.
54791      */
54792     setHandleElId: function(id) {
54793         if (typeof id !== "string") {
54794             id = Ext.id(id);
54795         }
54796         this.handleElId = id;
54797         this.DDMInstance.regHandle(this.id, id);
54798     },
54799
54800     /**
54801      * Allows you to set an element outside of the linked element as a drag
54802      * handle
54803      * @param {String} id the id of the element that will be used to initiate the drag
54804      */
54805     setOuterHandleElId: function(id) {
54806         if (typeof id !== "string") {
54807             id = Ext.id(id);
54808         }
54809         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
54810         this.setHandleElId(id);
54811
54812         this.hasOuterHandles = true;
54813     },
54814
54815     /**
54816      * Removes all drag and drop hooks for this element
54817      */
54818     unreg: function() {
54819         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
54820         this._domRef = null;
54821         this.DDMInstance._remove(this);
54822     },
54823
54824     destroy : function(){
54825         this.unreg();
54826     },
54827
54828     /**
54829      * Returns true if this instance is locked, or the drag drop mgr is locked
54830      * (meaning that all drag/drop is disabled on the page.)
54831      * @return {Boolean} true if this obj or all drag/drop is locked, else
54832      * false
54833      */
54834     isLocked: function() {
54835         return (this.DDMInstance.isLocked() || this.locked);
54836     },
54837
54838     /**
54839      * Called when this object is clicked
54840      * @param {Event} e
54841      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
54842      * @private
54843      */
54844     handleMouseDown: function(e, oDD){
54845         if (this.primaryButtonOnly && e.button != 0) {
54846             return;
54847         }
54848
54849         if (this.isLocked()) {
54850             return;
54851         }
54852
54853         this.DDMInstance.refreshCache(this.groups);
54854
54855         var pt = e.getPoint();
54856         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
54857         } else {
54858             if (this.clickValidator(e)) {
54859                 // set the initial element position
54860                 this.setStartPosition();
54861                 this.b4MouseDown(e);
54862                 this.onMouseDown(e);
54863
54864                 this.DDMInstance.handleMouseDown(e, this);
54865
54866                 this.DDMInstance.stopEvent(e);
54867             } else {
54868
54869
54870             }
54871         }
54872     },
54873
54874     clickValidator: function(e) {
54875         var target = e.getTarget();
54876         return ( this.isValidHandleChild(target) &&
54877                     (this.id == this.handleElId ||
54878                         this.DDMInstance.handleWasClicked(target, this.id)) );
54879     },
54880
54881     /**
54882      * Allows you to specify a tag name that should not start a drag operation
54883      * when clicked.  This is designed to facilitate embedding links within a
54884      * drag handle that do something other than start the drag.
54885      * @method addInvalidHandleType
54886      * @param {String} tagName the type of element to exclude
54887      */
54888     addInvalidHandleType: function(tagName) {
54889         var type = tagName.toUpperCase();
54890         this.invalidHandleTypes[type] = type;
54891     },
54892
54893     /**
54894      * Lets you to specify an element id for a child of a drag handle
54895      * that should not initiate a drag
54896      * @method addInvalidHandleId
54897      * @param {String} id the element id of the element you wish to ignore
54898      */
54899     addInvalidHandleId: function(id) {
54900         if (typeof id !== "string") {
54901             id = Ext.id(id);
54902         }
54903         this.invalidHandleIds[id] = id;
54904     },
54905
54906     /**
54907      * Lets you specify a css class of elements that will not initiate a drag
54908      * @param {String} cssClass the class of the elements you wish to ignore
54909      */
54910     addInvalidHandleClass: function(cssClass) {
54911         this.invalidHandleClasses.push(cssClass);
54912     },
54913
54914     /**
54915      * Unsets an excluded tag name set by addInvalidHandleType
54916      * @param {String} tagName the type of element to unexclude
54917      */
54918     removeInvalidHandleType: function(tagName) {
54919         var type = tagName.toUpperCase();
54920         // this.invalidHandleTypes[type] = null;
54921         delete this.invalidHandleTypes[type];
54922     },
54923
54924     /**
54925      * Unsets an invalid handle id
54926      * @param {String} id the id of the element to re-enable
54927      */
54928     removeInvalidHandleId: function(id) {
54929         if (typeof id !== "string") {
54930             id = Ext.id(id);
54931         }
54932         delete this.invalidHandleIds[id];
54933     },
54934
54935     /**
54936      * Unsets an invalid css class
54937      * @param {String} cssClass the class of the element(s) you wish to
54938      * re-enable
54939      */
54940     removeInvalidHandleClass: function(cssClass) {
54941         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
54942             if (this.invalidHandleClasses[i] == cssClass) {
54943                 delete this.invalidHandleClasses[i];
54944             }
54945         }
54946     },
54947
54948     /**
54949      * Checks the tag exclusion list to see if this click should be ignored
54950      * @param {HTMLElement} node the HTMLElement to evaluate
54951      * @return {Boolean} true if this is a valid tag type, false if not
54952      */
54953     isValidHandleChild: function(node) {
54954
54955         var valid = true;
54956         // var n = (node.nodeName == "#text") ? node.parentNode : node;
54957         var nodeName;
54958         try {
54959             nodeName = node.nodeName.toUpperCase();
54960         } catch(e) {
54961             nodeName = node.nodeName;
54962         }
54963         valid = valid && !this.invalidHandleTypes[nodeName];
54964         valid = valid && !this.invalidHandleIds[node.id];
54965
54966         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
54967             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
54968         }
54969
54970
54971         return valid;
54972
54973     },
54974
54975     /**
54976      * Creates the array of horizontal tick marks if an interval was specified
54977      * in setXConstraint().
54978      * @private
54979      */
54980     setXTicks: function(iStartX, iTickSize) {
54981         this.xTicks = [];
54982         this.xTickSize = iTickSize;
54983
54984         var tickMap = {};
54985
54986         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
54987             if (!tickMap[i]) {
54988                 this.xTicks[this.xTicks.length] = i;
54989                 tickMap[i] = true;
54990             }
54991         }
54992
54993         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
54994             if (!tickMap[i]) {
54995                 this.xTicks[this.xTicks.length] = i;
54996                 tickMap[i] = true;
54997             }
54998         }
54999
55000         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
55001     },
55002
55003     /**
55004      * Creates the array of vertical tick marks if an interval was specified in
55005      * setYConstraint().
55006      * @private
55007      */
55008     setYTicks: function(iStartY, iTickSize) {
55009         this.yTicks = [];
55010         this.yTickSize = iTickSize;
55011
55012         var tickMap = {};
55013
55014         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
55015             if (!tickMap[i]) {
55016                 this.yTicks[this.yTicks.length] = i;
55017                 tickMap[i] = true;
55018             }
55019         }
55020
55021         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
55022             if (!tickMap[i]) {
55023                 this.yTicks[this.yTicks.length] = i;
55024                 tickMap[i] = true;
55025             }
55026         }
55027
55028         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
55029     },
55030
55031     /**
55032      * By default, the element can be dragged any place on the screen.  Use
55033      * this method to limit the horizontal travel of the element.  Pass in
55034      * 0,0 for the parameters if you want to lock the drag to the y axis.
55035      * @param {Number} iLeft the number of pixels the element can move to the left
55036      * @param {Number} iRight the number of pixels the element can move to the
55037      * right
55038      * @param {Number} iTickSize (optional) parameter for specifying that the
55039      * element should move iTickSize pixels at a time.
55040      */
55041     setXConstraint: function(iLeft, iRight, iTickSize) {
55042         this.leftConstraint = iLeft;
55043         this.rightConstraint = iRight;
55044
55045         this.minX = this.initPageX - iLeft;
55046         this.maxX = this.initPageX + iRight;
55047         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
55048
55049         this.constrainX = true;
55050     },
55051
55052     /**
55053      * Clears any constraints applied to this instance.  Also clears ticks
55054      * since they can't exist independent of a constraint at this time.
55055      */
55056     clearConstraints: function() {
55057         this.constrainX = false;
55058         this.constrainY = false;
55059         this.clearTicks();
55060     },
55061
55062     /**
55063      * Clears any tick interval defined for this instance
55064      */
55065     clearTicks: function() {
55066         this.xTicks = null;
55067         this.yTicks = null;
55068         this.xTickSize = 0;
55069         this.yTickSize = 0;
55070     },
55071
55072     /**
55073      * By default, the element can be dragged any place on the screen.  Set
55074      * this to limit the vertical travel of the element.  Pass in 0,0 for the
55075      * parameters if you want to lock the drag to the x axis.
55076      * @param {Number} iUp the number of pixels the element can move up
55077      * @param {Number} iDown the number of pixels the element can move down
55078      * @param {Number} iTickSize (optional) parameter for specifying that the
55079      * element should move iTickSize pixels at a time.
55080      */
55081     setYConstraint: function(iUp, iDown, iTickSize) {
55082         this.topConstraint = iUp;
55083         this.bottomConstraint = iDown;
55084
55085         this.minY = this.initPageY - iUp;
55086         this.maxY = this.initPageY + iDown;
55087         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
55088
55089         this.constrainY = true;
55090
55091     },
55092
55093     /**
55094      * Must be called if you manually reposition a dd element.
55095      * @param {Boolean} maintainOffset
55096      */
55097     resetConstraints: function() {
55098         // Maintain offsets if necessary
55099         if (this.initPageX || this.initPageX === 0) {
55100             // figure out how much this thing has moved
55101             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
55102             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
55103
55104             this.setInitPosition(dx, dy);
55105
55106         // This is the first time we have detected the element's position
55107         } else {
55108             this.setInitPosition();
55109         }
55110
55111         if (this.constrainX) {
55112             this.setXConstraint( this.leftConstraint,
55113                                  this.rightConstraint,
55114                                  this.xTickSize        );
55115         }
55116
55117         if (this.constrainY) {
55118             this.setYConstraint( this.topConstraint,
55119                                  this.bottomConstraint,
55120                                  this.yTickSize         );
55121         }
55122     },
55123
55124     /**
55125      * Normally the drag element is moved pixel by pixel, but we can specify
55126      * that it move a number of pixels at a time.  This method resolves the
55127      * location when we have it set up like this.
55128      * @param {Number} val where we want to place the object
55129      * @param {Number[]} tickArray sorted array of valid points
55130      * @return {Number} the closest tick
55131      * @private
55132      */
55133     getTick: function(val, tickArray) {
55134         if (!tickArray) {
55135             // If tick interval is not defined, it is effectively 1 pixel,
55136             // so we return the value passed to us.
55137             return val;
55138         } else if (tickArray[0] >= val) {
55139             // The value is lower than the first tick, so we return the first
55140             // tick.
55141             return tickArray[0];
55142         } else {
55143             for (var i=0, len=tickArray.length; i<len; ++i) {
55144                 var next = i + 1;
55145                 if (tickArray[next] && tickArray[next] >= val) {
55146                     var diff1 = val - tickArray[i];
55147                     var diff2 = tickArray[next] - val;
55148                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
55149                 }
55150             }
55151
55152             // The value is larger than the last tick, so we return the last
55153             // tick.
55154             return tickArray[tickArray.length - 1];
55155         }
55156     },
55157
55158     /**
55159      * toString method
55160      * @return {String} string representation of the dd obj
55161      */
55162     toString: function() {
55163         return ("DragDrop " + this.id);
55164     }
55165
55166 });
55167
55168 /*
55169  * This is a derivative of the similarly named class in the YUI Library.
55170  * The original license:
55171  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55172  * Code licensed under the BSD License:
55173  * http://developer.yahoo.net/yui/license.txt
55174  */
55175
55176
55177 /**
55178  * @class Ext.dd.DD
55179  * A DragDrop implementation where the linked element follows the
55180  * mouse cursor during a drag.
55181  * @extends Ext.dd.DragDrop
55182  */
55183 Ext.define('Ext.dd.DD', {
55184     extend: 'Ext.dd.DragDrop',
55185     requires: ['Ext.dd.DragDropManager'],
55186
55187     /**
55188      * Creates new DD instance.
55189      * @param {String} id the id of the linked element
55190      * @param {String} sGroup the group of related DragDrop items
55191      * @param {Object} config an object containing configurable attributes.
55192      * Valid properties for DD: scroll
55193      */
55194     constructor: function(id, sGroup, config) {
55195         if (id) {
55196             this.init(id, sGroup, config);
55197         }
55198     },
55199
55200     /**
55201      * When set to true, the utility automatically tries to scroll the browser
55202      * window when a drag and drop element is dragged near the viewport boundary.
55203      * Defaults to true.
55204      * @property scroll
55205      * @type Boolean
55206      */
55207     scroll: true,
55208
55209     /**
55210      * Sets the pointer offset to the distance between the linked element's top
55211      * left corner and the location the element was clicked
55212      * @method autoOffset
55213      * @param {Number} iPageX the X coordinate of the click
55214      * @param {Number} iPageY the Y coordinate of the click
55215      */
55216     autoOffset: function(iPageX, iPageY) {
55217         var x = iPageX - this.startPageX;
55218         var y = iPageY - this.startPageY;
55219         this.setDelta(x, y);
55220     },
55221
55222     /**
55223      * Sets the pointer offset.  You can call this directly to force the
55224      * offset to be in a particular location (e.g., pass in 0,0 to set it
55225      * to the center of the object)
55226      * @method setDelta
55227      * @param {Number} iDeltaX the distance from the left
55228      * @param {Number} iDeltaY the distance from the top
55229      */
55230     setDelta: function(iDeltaX, iDeltaY) {
55231         this.deltaX = iDeltaX;
55232         this.deltaY = iDeltaY;
55233     },
55234
55235     /**
55236      * Sets the drag element to the location of the mousedown or click event,
55237      * maintaining the cursor location relative to the location on the element
55238      * that was clicked.  Override this if you want to place the element in a
55239      * location other than where the cursor is.
55240      * @method setDragElPos
55241      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55242      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55243      */
55244     setDragElPos: function(iPageX, iPageY) {
55245         // the first time we do this, we are going to check to make sure
55246         // the element has css positioning
55247
55248         var el = this.getDragEl();
55249         this.alignElWithMouse(el, iPageX, iPageY);
55250     },
55251
55252     /**
55253      * Sets the element to the location of the mousedown or click event,
55254      * maintaining the cursor location relative to the location on the element
55255      * that was clicked.  Override this if you want to place the element in a
55256      * location other than where the cursor is.
55257      * @method alignElWithMouse
55258      * @param {HTMLElement} el the element to move
55259      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55260      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55261      */
55262     alignElWithMouse: function(el, iPageX, iPageY) {
55263         var oCoord = this.getTargetCoord(iPageX, iPageY),
55264             fly = el.dom ? el : Ext.fly(el, '_dd'),
55265             elSize = fly.getSize(),
55266             EL = Ext.Element,
55267             vpSize;
55268
55269         if (!this.deltaSetXY) {
55270             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
55271             var aCoord = [
55272                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
55273                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
55274             ];
55275             fly.setXY(aCoord);
55276             var newLeft = fly.getLeft(true);
55277             var newTop  = fly.getTop(true);
55278             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
55279         } else {
55280             vpSize = this.cachedViewportSize;
55281             fly.setLeftTop(
55282                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
55283                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
55284             );
55285         }
55286
55287         this.cachePosition(oCoord.x, oCoord.y);
55288         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
55289         return oCoord;
55290     },
55291
55292     /**
55293      * Saves the most recent position so that we can reset the constraints and
55294      * tick marks on-demand.  We need to know this so that we can calculate the
55295      * number of pixels the element is offset from its original position.
55296      * @method cachePosition
55297      * @param {Number} iPageX (optional) the current x position (this just makes it so we
55298      * don't have to look it up again)
55299      * @param {Number} iPageY (optional) the current y position (this just makes it so we
55300      * don't have to look it up again)
55301      */
55302     cachePosition: function(iPageX, iPageY) {
55303         if (iPageX) {
55304             this.lastPageX = iPageX;
55305             this.lastPageY = iPageY;
55306         } else {
55307             var aCoord = Ext.Element.getXY(this.getEl());
55308             this.lastPageX = aCoord[0];
55309             this.lastPageY = aCoord[1];
55310         }
55311     },
55312
55313     /**
55314      * Auto-scroll the window if the dragged object has been moved beyond the
55315      * visible window boundary.
55316      * @method autoScroll
55317      * @param {Number} x the drag element's x position
55318      * @param {Number} y the drag element's y position
55319      * @param {Number} h the height of the drag element
55320      * @param {Number} w the width of the drag element
55321      * @private
55322      */
55323     autoScroll: function(x, y, h, w) {
55324
55325         if (this.scroll) {
55326             // The client height
55327             var clientH = Ext.Element.getViewHeight();
55328
55329             // The client width
55330             var clientW = Ext.Element.getViewWidth();
55331
55332             // The amt scrolled down
55333             var st = this.DDMInstance.getScrollTop();
55334
55335             // The amt scrolled right
55336             var sl = this.DDMInstance.getScrollLeft();
55337
55338             // Location of the bottom of the element
55339             var bot = h + y;
55340
55341             // Location of the right of the element
55342             var right = w + x;
55343
55344             // The distance from the cursor to the bottom of the visible area,
55345             // adjusted so that we don't scroll if the cursor is beyond the
55346             // element drag constraints
55347             var toBot = (clientH + st - y - this.deltaY);
55348
55349             // The distance from the cursor to the right of the visible area
55350             var toRight = (clientW + sl - x - this.deltaX);
55351
55352
55353             // How close to the edge the cursor must be before we scroll
55354             // var thresh = (document.all) ? 100 : 40;
55355             var thresh = 40;
55356
55357             // How many pixels to scroll per autoscroll op.  This helps to reduce
55358             // clunky scrolling. IE is more sensitive about this ... it needs this
55359             // value to be higher.
55360             var scrAmt = (document.all) ? 80 : 30;
55361
55362             // Scroll down if we are near the bottom of the visible page and the
55363             // obj extends below the crease
55364             if ( bot > clientH && toBot < thresh ) {
55365                 window.scrollTo(sl, st + scrAmt);
55366             }
55367
55368             // Scroll up if the window is scrolled down and the top of the object
55369             // goes above the top border
55370             if ( y < st && st > 0 && y - st < thresh ) {
55371                 window.scrollTo(sl, st - scrAmt);
55372             }
55373
55374             // Scroll right if the obj is beyond the right border and the cursor is
55375             // near the border.
55376             if ( right > clientW && toRight < thresh ) {
55377                 window.scrollTo(sl + scrAmt, st);
55378             }
55379
55380             // Scroll left if the window has been scrolled to the right and the obj
55381             // extends past the left border
55382             if ( x < sl && sl > 0 && x - sl < thresh ) {
55383                 window.scrollTo(sl - scrAmt, st);
55384             }
55385         }
55386     },
55387
55388     /**
55389      * Finds the location the element should be placed if we want to move
55390      * it to where the mouse location less the click offset would place us.
55391      * @method getTargetCoord
55392      * @param {Number} iPageX the X coordinate of the click
55393      * @param {Number} iPageY the Y coordinate of the click
55394      * @return an object that contains the coordinates (Object.x and Object.y)
55395      * @private
55396      */
55397     getTargetCoord: function(iPageX, iPageY) {
55398         var x = iPageX - this.deltaX;
55399         var y = iPageY - this.deltaY;
55400
55401         if (this.constrainX) {
55402             if (x < this.minX) {
55403                 x = this.minX;
55404             }
55405             if (x > this.maxX) {
55406                 x = this.maxX;
55407             }
55408         }
55409
55410         if (this.constrainY) {
55411             if (y < this.minY) {
55412                 y = this.minY;
55413             }
55414             if (y > this.maxY) {
55415                 y = this.maxY;
55416             }
55417         }
55418
55419         x = this.getTick(x, this.xTicks);
55420         y = this.getTick(y, this.yTicks);
55421
55422
55423         return {x: x, y: y};
55424     },
55425
55426     /**
55427      * Sets up config options specific to this class. Overrides
55428      * Ext.dd.DragDrop, but all versions of this method through the
55429      * inheritance chain are called
55430      */
55431     applyConfig: function() {
55432         this.callParent();
55433         this.scroll = (this.config.scroll !== false);
55434     },
55435
55436     /**
55437      * Event that fires prior to the onMouseDown event.  Overrides
55438      * Ext.dd.DragDrop.
55439      */
55440     b4MouseDown: function(e) {
55441         // this.resetConstraints();
55442         this.autoOffset(e.getPageX(), e.getPageY());
55443     },
55444
55445     /**
55446      * Event that fires prior to the onDrag event.  Overrides
55447      * Ext.dd.DragDrop.
55448      */
55449     b4Drag: function(e) {
55450         this.setDragElPos(e.getPageX(), e.getPageY());
55451     },
55452
55453     toString: function() {
55454         return ("DD " + this.id);
55455     }
55456
55457     //////////////////////////////////////////////////////////////////////////
55458     // Debugging ygDragDrop events that can be overridden
55459     //////////////////////////////////////////////////////////////////////////
55460     /*
55461     startDrag: function(x, y) {
55462     },
55463
55464     onDrag: function(e) {
55465     },
55466
55467     onDragEnter: function(e, id) {
55468     },
55469
55470     onDragOver: function(e, id) {
55471     },
55472
55473     onDragOut: function(e, id) {
55474     },
55475
55476     onDragDrop: function(e, id) {
55477     },
55478
55479     endDrag: function(e) {
55480     }
55481
55482     */
55483
55484 });
55485
55486 /*
55487  * This is a derivative of the similarly named class in the YUI Library.
55488  * The original license:
55489  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55490  * Code licensed under the BSD License:
55491  * http://developer.yahoo.net/yui/license.txt
55492  */
55493
55494 /**
55495  * @class Ext.dd.DDProxy
55496  * @extends Ext.dd.DD
55497  * A DragDrop implementation that inserts an empty, bordered div into
55498  * the document that follows the cursor during drag operations.  At the time of
55499  * the click, the frame div is resized to the dimensions of the linked html
55500  * element, and moved to the exact location of the linked element.
55501  *
55502  * References to the "frame" element refer to the single proxy element that
55503  * was created to be dragged in place of all DDProxy elements on the
55504  * page.
55505  */
55506 Ext.define('Ext.dd.DDProxy', {
55507     extend: 'Ext.dd.DD',
55508
55509     statics: {
55510         /**
55511          * The default drag frame div id
55512          * @static
55513          */
55514         dragElId: "ygddfdiv"
55515     },
55516
55517     /**
55518      * Creates new DDProxy.
55519      * @param {String} id the id of the linked html element
55520      * @param {String} sGroup the group of related DragDrop objects
55521      * @param {Object} config an object containing configurable attributes.
55522      * Valid properties for DDProxy in addition to those in DragDrop:
55523      * 
55524      * - resizeFrame
55525      * - centerFrame
55526      * - dragElId
55527      */
55528     constructor: function(id, sGroup, config) {
55529         if (id) {
55530             this.init(id, sGroup, config);
55531             this.initFrame();
55532         }
55533     },
55534
55535     /**
55536      * By default we resize the drag frame to be the same size as the element
55537      * we want to drag (this is to get the frame effect).  We can turn it off
55538      * if we want a different behavior.
55539      * @property resizeFrame
55540      * @type Boolean
55541      */
55542     resizeFrame: true,
55543
55544     /**
55545      * By default the frame is positioned exactly where the drag element is, so
55546      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
55547      * you do not have constraints on the obj is to have the drag frame centered
55548      * around the cursor.  Set centerFrame to true for this effect.
55549      * @property centerFrame
55550      * @type Boolean
55551      */
55552     centerFrame: false,
55553
55554     /**
55555      * Creates the proxy element if it does not yet exist
55556      * @method createFrame
55557      */
55558     createFrame: function() {
55559         var self = this;
55560         var body = document.body;
55561
55562         if (!body || !body.firstChild) {
55563             setTimeout( function() { self.createFrame(); }, 50 );
55564             return;
55565         }
55566
55567         var div = this.getDragEl();
55568
55569         if (!div) {
55570             div    = document.createElement("div");
55571             div.id = this.dragElId;
55572             var s  = div.style;
55573
55574             s.position   = "absolute";
55575             s.visibility = "hidden";
55576             s.cursor     = "move";
55577             s.border     = "2px solid #aaa";
55578             s.zIndex     = 999;
55579
55580             // appendChild can blow up IE if invoked prior to the window load event
55581             // while rendering a table.  It is possible there are other scenarios
55582             // that would cause this to happen as well.
55583             body.insertBefore(div, body.firstChild);
55584         }
55585     },
55586
55587     /**
55588      * Initialization for the drag frame element.  Must be called in the
55589      * constructor of all subclasses
55590      * @method initFrame
55591      */
55592     initFrame: function() {
55593         this.createFrame();
55594     },
55595
55596     applyConfig: function() {
55597         this.callParent();
55598
55599         this.resizeFrame = (this.config.resizeFrame !== false);
55600         this.centerFrame = (this.config.centerFrame);
55601         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
55602     },
55603
55604     /**
55605      * Resizes the drag frame to the dimensions of the clicked object, positions
55606      * it over the object, and finally displays it
55607      * @method showFrame
55608      * @param {Number} iPageX X click position
55609      * @param {Number} iPageY Y click position
55610      * @private
55611      */
55612     showFrame: function(iPageX, iPageY) {
55613         var el = this.getEl();
55614         var dragEl = this.getDragEl();
55615         var s = dragEl.style;
55616
55617         this._resizeProxy();
55618
55619         if (this.centerFrame) {
55620             this.setDelta( Math.round(parseInt(s.width,  10)/2),
55621                            Math.round(parseInt(s.height, 10)/2) );
55622         }
55623
55624         this.setDragElPos(iPageX, iPageY);
55625
55626         Ext.fly(dragEl).show();
55627     },
55628
55629     /**
55630      * The proxy is automatically resized to the dimensions of the linked
55631      * element when a drag is initiated, unless resizeFrame is set to false
55632      * @method _resizeProxy
55633      * @private
55634      */
55635     _resizeProxy: function() {
55636         if (this.resizeFrame) {
55637             var el = this.getEl();
55638             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
55639         }
55640     },
55641
55642     // overrides Ext.dd.DragDrop
55643     b4MouseDown: function(e) {
55644         var x = e.getPageX();
55645         var y = e.getPageY();
55646         this.autoOffset(x, y);
55647         this.setDragElPos(x, y);
55648     },
55649
55650     // overrides Ext.dd.DragDrop
55651     b4StartDrag: function(x, y) {
55652         // show the drag frame
55653         this.showFrame(x, y);
55654     },
55655
55656     // overrides Ext.dd.DragDrop
55657     b4EndDrag: function(e) {
55658         Ext.fly(this.getDragEl()).hide();
55659     },
55660
55661     // overrides Ext.dd.DragDrop
55662     // By default we try to move the element to the last location of the frame.
55663     // This is so that the default behavior mirrors that of Ext.dd.DD.
55664     endDrag: function(e) {
55665
55666         var lel = this.getEl();
55667         var del = this.getDragEl();
55668
55669         // Show the drag frame briefly so we can get its position
55670         del.style.visibility = "";
55671
55672         this.beforeMove();
55673         // Hide the linked element before the move to get around a Safari
55674         // rendering bug.
55675         lel.style.visibility = "hidden";
55676         Ext.dd.DDM.moveToEl(lel, del);
55677         del.style.visibility = "hidden";
55678         lel.style.visibility = "";
55679
55680         this.afterDrag();
55681     },
55682
55683     beforeMove : function(){
55684
55685     },
55686
55687     afterDrag : function(){
55688
55689     },
55690
55691     toString: function() {
55692         return ("DDProxy " + this.id);
55693     }
55694
55695 });
55696
55697 /**
55698  * @class Ext.dd.DragSource
55699  * @extends Ext.dd.DDProxy
55700  * A simple class that provides the basic implementation needed to make any element draggable.
55701  */
55702 Ext.define('Ext.dd.DragSource', {
55703     extend: 'Ext.dd.DDProxy',
55704     requires: [
55705         'Ext.dd.StatusProxy',
55706         'Ext.dd.DragDropManager'
55707     ],
55708
55709     /**
55710      * @cfg {String} ddGroup
55711      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
55712      * interact with other drag drop objects in the same group.
55713      */
55714
55715     /**
55716      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
55717      * The CSS class returned to the drag source when drop is allowed.
55718      */
55719     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
55720     /**
55721      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
55722      * The CSS class returned to the drag source when drop is not allowed.
55723      */
55724     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
55725
55726     /**
55727      * @cfg {Boolean} animRepair
55728      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
55729      */
55730     animRepair: true,
55731
55732     /**
55733      * @cfg {String} repairHighlightColor
55734      * The color to use when visually highlighting the drag source in the afterRepair
55735      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
55736      * a preceding '#'.
55737      */
55738     repairHighlightColor: 'c3daf9',
55739
55740     /**
55741      * Creates new drag-source.
55742      * @constructor
55743      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
55744      * @param {Object} config (optional) Config object.
55745      */
55746     constructor: function(el, config) {
55747         this.el = Ext.get(el);
55748         if(!this.dragData){
55749             this.dragData = {};
55750         }
55751
55752         Ext.apply(this, config);
55753
55754         if(!this.proxy){
55755             this.proxy = Ext.create('Ext.dd.StatusProxy', {
55756                 animRepair: this.animRepair
55757             });
55758         }
55759         this.callParent([this.el.dom, this.ddGroup || this.group,
55760               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
55761
55762         this.dragging = false;
55763     },
55764
55765     /**
55766      * Returns the data object associated with this drag source
55767      * @return {Object} data An object containing arbitrary data
55768      */
55769     getDragData : function(e){
55770         return this.dragData;
55771     },
55772
55773     // private
55774     onDragEnter : function(e, id){
55775         var target = Ext.dd.DragDropManager.getDDById(id);
55776         this.cachedTarget = target;
55777         if (this.beforeDragEnter(target, e, id) !== false) {
55778             if (target.isNotifyTarget) {
55779                 var status = target.notifyEnter(this, e, this.dragData);
55780                 this.proxy.setStatus(status);
55781             } else {
55782                 this.proxy.setStatus(this.dropAllowed);
55783             }
55784
55785             if (this.afterDragEnter) {
55786                 /**
55787                  * An empty function by default, but provided so that you can perform a custom action
55788                  * when the dragged item enters the drop target by providing an implementation.
55789                  * @param {Ext.dd.DragDrop} target The drop target
55790                  * @param {Event} e The event object
55791                  * @param {String} id The id of the dragged element
55792                  * @method afterDragEnter
55793                  */
55794                 this.afterDragEnter(target, e, id);
55795             }
55796         }
55797     },
55798
55799     /**
55800      * An empty function by default, but provided so that you can perform a custom action
55801      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
55802      * @param {Ext.dd.DragDrop} target The drop target
55803      * @param {Event} e The event object
55804      * @param {String} id The id of the dragged element
55805      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55806      */
55807     beforeDragEnter: function(target, e, id) {
55808         return true;
55809     },
55810
55811     // private
55812     alignElWithMouse: function() {
55813         this.callParent(arguments);
55814         this.proxy.sync();
55815     },
55816
55817     // private
55818     onDragOver: function(e, id) {
55819         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55820         if (this.beforeDragOver(target, e, id) !== false) {
55821             if(target.isNotifyTarget){
55822                 var status = target.notifyOver(this, e, this.dragData);
55823                 this.proxy.setStatus(status);
55824             }
55825
55826             if (this.afterDragOver) {
55827                 /**
55828                  * An empty function by default, but provided so that you can perform a custom action
55829                  * while the dragged item is over the drop target by providing an implementation.
55830                  * @param {Ext.dd.DragDrop} target The drop target
55831                  * @param {Event} e The event object
55832                  * @param {String} id The id of the dragged element
55833                  * @method afterDragOver
55834                  */
55835                 this.afterDragOver(target, e, id);
55836             }
55837         }
55838     },
55839
55840     /**
55841      * An empty function by default, but provided so that you can perform a custom action
55842      * while the dragged item is over the drop target and optionally cancel the onDragOver.
55843      * @param {Ext.dd.DragDrop} target The drop target
55844      * @param {Event} e The event object
55845      * @param {String} id The id of the dragged element
55846      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55847      */
55848     beforeDragOver: function(target, e, id) {
55849         return true;
55850     },
55851
55852     // private
55853     onDragOut: function(e, id) {
55854         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55855         if (this.beforeDragOut(target, e, id) !== false) {
55856             if (target.isNotifyTarget) {
55857                 target.notifyOut(this, e, this.dragData);
55858             }
55859             this.proxy.reset();
55860             if (this.afterDragOut) {
55861                 /**
55862                  * An empty function by default, but provided so that you can perform a custom action
55863                  * after the dragged item is dragged out of the target without dropping.
55864                  * @param {Ext.dd.DragDrop} target The drop target
55865                  * @param {Event} e The event object
55866                  * @param {String} id The id of the dragged element
55867                  * @method afterDragOut
55868                  */
55869                 this.afterDragOut(target, e, id);
55870             }
55871         }
55872         this.cachedTarget = null;
55873     },
55874
55875     /**
55876      * An empty function by default, but provided so that you can perform a custom action before the dragged
55877      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
55878      * @param {Ext.dd.DragDrop} target The drop target
55879      * @param {Event} e The event object
55880      * @param {String} id The id of the dragged element
55881      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55882      */
55883     beforeDragOut: function(target, e, id){
55884         return true;
55885     },
55886
55887     // private
55888     onDragDrop: function(e, id){
55889         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55890         if (this.beforeDragDrop(target, e, id) !== false) {
55891             if (target.isNotifyTarget) {
55892                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
55893                     this.onValidDrop(target, e, id);
55894                 } else {
55895                     this.onInvalidDrop(target, e, id);
55896                 }
55897             } else {
55898                 this.onValidDrop(target, e, id);
55899             }
55900
55901             if (this.afterDragDrop) {
55902                 /**
55903                  * An empty function by default, but provided so that you can perform a custom action
55904                  * after a valid drag drop has occurred by providing an implementation.
55905                  * @param {Ext.dd.DragDrop} target The drop target
55906                  * @param {Event} e The event object
55907                  * @param {String} id The id of the dropped element
55908                  * @method afterDragDrop
55909                  */
55910                 this.afterDragDrop(target, e, id);
55911             }
55912         }
55913         delete this.cachedTarget;
55914     },
55915
55916     /**
55917      * An empty function by default, but provided so that you can perform a custom action before the dragged
55918      * item is dropped onto the target and optionally cancel the onDragDrop.
55919      * @param {Ext.dd.DragDrop} target The drop target
55920      * @param {Event} e The event object
55921      * @param {String} id The id of the dragged element
55922      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
55923      */
55924     beforeDragDrop: function(target, e, id){
55925         return true;
55926     },
55927
55928     // private
55929     onValidDrop: function(target, e, id){
55930         this.hideProxy();
55931         if(this.afterValidDrop){
55932             /**
55933              * An empty function by default, but provided so that you can perform a custom action
55934              * after a valid drop has occurred by providing an implementation.
55935              * @param {Object} target The target DD
55936              * @param {Event} e The event object
55937              * @param {String} id The id of the dropped element
55938              * @method afterValidDrop
55939              */
55940             this.afterValidDrop(target, e, id);
55941         }
55942     },
55943
55944     // private
55945     getRepairXY: function(e, data){
55946         return this.el.getXY();
55947     },
55948
55949     // private
55950     onInvalidDrop: function(target, e, id) {
55951         this.beforeInvalidDrop(target, e, id);
55952         if (this.cachedTarget) {
55953             if(this.cachedTarget.isNotifyTarget){
55954                 this.cachedTarget.notifyOut(this, e, this.dragData);
55955             }
55956             this.cacheTarget = null;
55957         }
55958         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
55959
55960         if (this.afterInvalidDrop) {
55961             /**
55962              * An empty function by default, but provided so that you can perform a custom action
55963              * after an invalid drop has occurred by providing an implementation.
55964              * @param {Event} e The event object
55965              * @param {String} id The id of the dropped element
55966              * @method afterInvalidDrop
55967              */
55968             this.afterInvalidDrop(e, id);
55969         }
55970     },
55971
55972     // private
55973     afterRepair: function() {
55974         var me = this;
55975         if (Ext.enableFx) {
55976             me.el.highlight(me.repairHighlightColor);
55977         }
55978         me.dragging = false;
55979     },
55980
55981     /**
55982      * An empty function by default, but provided so that you can perform a custom action after an invalid
55983      * drop has occurred.
55984      * @param {Ext.dd.DragDrop} target The drop target
55985      * @param {Event} e The event object
55986      * @param {String} id The id of the dragged element
55987      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
55988      */
55989     beforeInvalidDrop: function(target, e, id) {
55990         return true;
55991     },
55992
55993     // private
55994     handleMouseDown: function(e) {
55995         if (this.dragging) {
55996             return;
55997         }
55998         var data = this.getDragData(e);
55999         if (data && this.onBeforeDrag(data, e) !== false) {
56000             this.dragData = data;
56001             this.proxy.stop();
56002             this.callParent(arguments);
56003         }
56004     },
56005
56006     /**
56007      * An empty function by default, but provided so that you can perform a custom action before the initial
56008      * drag event begins and optionally cancel it.
56009      * @param {Object} data An object containing arbitrary data to be shared with drop targets
56010      * @param {Event} e The event object
56011      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56012      */
56013     onBeforeDrag: function(data, e){
56014         return true;
56015     },
56016
56017     /**
56018      * An empty function by default, but provided so that you can perform a custom action once the initial
56019      * drag event has begun.  The drag cannot be canceled from this function.
56020      * @param {Number} x The x position of the click on the dragged object
56021      * @param {Number} y The y position of the click on the dragged object
56022      * @method
56023      */
56024     onStartDrag: Ext.emptyFn,
56025
56026     // private override
56027     startDrag: function(x, y) {
56028         this.proxy.reset();
56029         this.dragging = true;
56030         this.proxy.update("");
56031         this.onInitDrag(x, y);
56032         this.proxy.show();
56033     },
56034
56035     // private
56036     onInitDrag: function(x, y) {
56037         var clone = this.el.dom.cloneNode(true);
56038         clone.id = Ext.id(); // prevent duplicate ids
56039         this.proxy.update(clone);
56040         this.onStartDrag(x, y);
56041         return true;
56042     },
56043
56044     /**
56045      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
56046      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
56047      */
56048     getProxy: function() {
56049         return this.proxy;
56050     },
56051
56052     /**
56053      * Hides the drag source's {@link Ext.dd.StatusProxy}
56054      */
56055     hideProxy: function() {
56056         this.proxy.hide();
56057         this.proxy.reset(true);
56058         this.dragging = false;
56059     },
56060
56061     // private
56062     triggerCacheRefresh: function() {
56063         Ext.dd.DDM.refreshCache(this.groups);
56064     },
56065
56066     // private - override to prevent hiding
56067     b4EndDrag: function(e) {
56068     },
56069
56070     // private - override to prevent moving
56071     endDrag : function(e){
56072         this.onEndDrag(this.dragData, e);
56073     },
56074
56075     // private
56076     onEndDrag : function(data, e){
56077     },
56078
56079     // private - pin to cursor
56080     autoOffset : function(x, y) {
56081         this.setDelta(-12, -20);
56082     },
56083
56084     destroy: function(){
56085         this.callParent();
56086         Ext.destroy(this.proxy);
56087     }
56088 });
56089
56090 // private - DD implementation for Panels
56091 Ext.define('Ext.panel.DD', {
56092     extend: 'Ext.dd.DragSource',
56093     requires: ['Ext.panel.Proxy'],
56094
56095     constructor : function(panel, cfg){
56096         this.panel = panel;
56097         this.dragData = {panel: panel};
56098         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
56099
56100         this.callParent([panel.el, cfg]);
56101
56102         Ext.defer(function() {
56103             var header = panel.header,
56104                 el = panel.body;
56105
56106             if(header){
56107                 this.setHandleElId(header.id);
56108                 el = header.el;
56109             }
56110             el.setStyle('cursor', 'move');
56111             this.scroll = false;
56112         }, 200, this);
56113     },
56114
56115     showFrame: Ext.emptyFn,
56116     startDrag: Ext.emptyFn,
56117     b4StartDrag: function(x, y) {
56118         this.proxy.show();
56119     },
56120     b4MouseDown: function(e) {
56121         var x = e.getPageX(),
56122             y = e.getPageY();
56123         this.autoOffset(x, y);
56124     },
56125     onInitDrag : function(x, y){
56126         this.onStartDrag(x, y);
56127         return true;
56128     },
56129     createFrame : Ext.emptyFn,
56130     getDragEl : function(e){
56131         return this.proxy.ghost.el.dom;
56132     },
56133     endDrag : function(e){
56134         this.proxy.hide();
56135         this.panel.saveState();
56136     },
56137
56138     autoOffset : function(x, y) {
56139         x -= this.startPageX;
56140         y -= this.startPageY;
56141         this.setDelta(x, y);
56142     }
56143 });
56144
56145 /**
56146  * @class Ext.layout.component.Dock
56147  * @extends Ext.layout.component.AbstractDock
56148  * @private
56149  */
56150 Ext.define('Ext.layout.component.Dock', {
56151
56152     /* Begin Definitions */
56153
56154     alias: ['layout.dock'],
56155
56156     extend: 'Ext.layout.component.AbstractDock'
56157
56158     /* End Definitions */
56159
56160 });
56161 /**
56162  * Panel is a container that has specific functionality and structural components that make it the perfect building
56163  * block for application-oriented user interfaces.
56164  *
56165  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
56166  * {@link Ext.container.Container#layout layout}, and containing child Components.
56167  *
56168  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
56169  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
56170  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
56171  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
56172  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
56173  *
56174  * {@img Ext.panel.Panel/panel.png Panel components}
56175  *
56176  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
56177  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
56178  *
56179  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
56180  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
56181  * is {@link Ext.container.Container#add completely managed by the framework}.
56182  *
56183  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
56184  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
56185  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
56186  * `{@link #closeAction closeAction}: 'hide'`.
56187  *
56188  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
56189  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
56190  * Panel into the document, here's how to do it:
56191  *
56192  *     @example
56193  *     Ext.create('Ext.panel.Panel', {
56194  *         title: 'Hello',
56195  *         width: 200,
56196  *         html: '<p>World!</p>',
56197  *         renderTo: Ext.getBody()
56198  *     });
56199  *
56200  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
56201  * constituent part of a Container:
56202  *
56203  *     @example
56204  *     var filterPanel = Ext.create('Ext.panel.Panel', {
56205  *         bodyPadding: 5,  // Don't want content to crunch against the borders
56206  *         width: 300,
56207  *         title: 'Filters',
56208  *         items: [{
56209  *             xtype: 'datefield',
56210  *             fieldLabel: 'Start date'
56211  *         }, {
56212  *             xtype: 'datefield',
56213  *             fieldLabel: 'End date'
56214  *         }],
56215  *         renderTo: Ext.getBody()
56216  *     });
56217  *
56218  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
56219  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
56220  * size and position its child Components.
56221  *
56222  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
56223  * arranging child Components:
56224  *
56225  *     @example
56226  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
56227  *         title: 'Results',
56228  *         width: 600,
56229  *         height: 400,
56230  *         renderTo: Ext.getBody(),
56231  *         layout: {
56232  *             type: 'vbox',       // Arrange child items vertically
56233  *             align: 'stretch',    // Each takes up full width
56234  *             padding: 5
56235  *         },
56236  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
56237  *             xtype: 'grid',
56238  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
56239  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
56240  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
56241  *         }, {
56242  *             xtype: 'splitter'   // A splitter between the two child items
56243  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
56244  *             title: 'Details',
56245  *             bodyPadding: 5,
56246  *             items: [{
56247  *                 fieldLabel: 'Data item',
56248  *                 xtype: 'textfield'
56249  *             }], // An array of form fields
56250  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
56251  *         }]
56252  *     });
56253  *
56254  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
56255  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
56256  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
56257  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
56258  * distribute available space proportionately.
56259  *
56260  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
56261  * within its content area.
56262  *
56263  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
56264  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
56265  * browser size.
56266  */
56267 Ext.define('Ext.panel.Panel', {
56268     extend: 'Ext.panel.AbstractPanel',
56269     requires: [
56270         'Ext.panel.Header',
56271         'Ext.fx.Anim',
56272         'Ext.util.KeyMap',
56273         'Ext.panel.DD',
56274         'Ext.XTemplate',
56275         'Ext.layout.component.Dock',
56276         'Ext.util.Memento'
56277     ],
56278     alias: 'widget.panel',
56279     alternateClassName: 'Ext.Panel',
56280
56281     /**
56282      * @cfg {String} collapsedCls
56283      * A CSS class to add to the panel's element after it has been collapsed.
56284      */
56285     collapsedCls: 'collapsed',
56286
56287     /**
56288      * @cfg {Boolean} animCollapse
56289      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
56290      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
56291      * duration in milliseconds.
56292      */
56293     animCollapse: Ext.enableFx,
56294
56295     /**
56296      * @cfg {Number} minButtonWidth
56297      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
56298      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
56299      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
56300      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
56301      * their parent container.
56302      */
56303     minButtonWidth: 75,
56304
56305     /**
56306      * @cfg {Boolean} collapsed
56307      * `true` to render the panel collapsed, `false` to render it expanded.
56308      */
56309     collapsed: false,
56310
56311     /**
56312      * @cfg {Boolean} collapseFirst
56313      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
56314      * the panel's title bar, `false` to render it last.
56315      */
56316     collapseFirst: true,
56317
56318     /**
56319      * @cfg {Boolean} hideCollapseTool
56320      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
56321      */
56322     hideCollapseTool: false,
56323
56324     /**
56325      * @cfg {Boolean} titleCollapse
56326      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
56327      * the header bar, `false`) to allow it only by clicking to tool butto).
56328      */
56329     titleCollapse: false,
56330
56331     /**
56332      * @cfg {String} collapseMode
56333      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
56334      * {@link Ext.layout.container.Border border layout}.**
56335      *
56336      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
56337      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
56338      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
56339      * expand tool.
56340      *
56341      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
56342      *
56343      * - **`undefined/omitted`**
56344      *
56345      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
56346      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
56347      *
56348      * - **`header`** :
56349      *
56350      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
56351      *   layout}.
56352      */
56353
56354     /**
56355      * @cfg {Ext.Component/Object} placeholder
56356      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56357      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
56358      *
56359      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
56360      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
56361      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
56362      */
56363
56364     /**
56365      * @cfg {Boolean} floatable
56366      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56367      * {@link Ext.layout.container.Border border layout}.**
56368      *
56369      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
56370      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
56371      */
56372     floatable: true,
56373
56374     /**
56375      * @cfg {Boolean} overlapHeader
56376      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
56377      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
56378      * which does not have frame:true, this will need to be set to true.
56379      */
56380
56381     /**
56382      * @cfg {Boolean} collapsible
56383      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
56384      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
56385      *
56386      * See {@link #collapseMode} and {@link #collapseDirection}
56387      */
56388     collapsible: false,
56389
56390     /**
56391      * @cfg {Boolean} collapseDirection
56392      * The direction to collapse the Panel when the toggle button is clicked.
56393      *
56394      * Defaults to the {@link #headerPosition}
56395      *
56396      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
56397      * Ext.layout.container.Border border layout}.**
56398      *
56399      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56400      */
56401
56402     /**
56403      * @cfg {Boolean} closable
56404      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
56405      * disallow closing the window.
56406      *
56407      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
56408      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
56409      * reused.
56410      *
56411      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
56412      */
56413     closable: false,
56414
56415     /**
56416      * @cfg {String} closeAction
56417      * The action to take when the close header tool is clicked:
56418      *
56419      * - **`'{@link #destroy}'`** :
56420      *
56421      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
56422      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
56423      *
56424      * - **`'{@link #hide}'`** :
56425      *
56426      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
56427      *   available to be redisplayed via the {@link #show} method.
56428      *
56429      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
56430      * approriate closeAction.
56431      */
56432     closeAction: 'destroy',
56433
56434     /**
56435      * @cfg {Object/Object[]} dockedItems
56436      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
56437      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
56438      *
56439      *     var panel = new Ext.panel.Panel({
56440      *         dockedItems: [{
56441      *             xtype: 'toolbar',
56442      *             dock: 'top',
56443      *             items: [{
56444      *                 text: 'Docked to the top'
56445      *             }]
56446      *         }]
56447      *     });
56448      */
56449
56450     /**
56451       * @cfg {Boolean} preventHeader
56452       * Prevent a Header from being created and shown.
56453       */
56454     preventHeader: false,
56455
56456      /**
56457       * @cfg {String} headerPosition
56458       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56459       */
56460     headerPosition: 'top',
56461
56462      /**
56463      * @cfg {Boolean} frame
56464      * True to apply a frame to the panel.
56465      */
56466     frame: false,
56467
56468     /**
56469      * @cfg {Boolean} frameHeader
56470      * True to apply a frame to the panel panels header (if 'frame' is true).
56471      */
56472     frameHeader: true,
56473
56474     /**
56475      * @cfg {Object[]/Ext.panel.Tool[]} tools
56476      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
56477      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
56478      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
56479      *
56480      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
56481      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
56482      * behavior.
56483      *
56484      * Example usage:
56485      *
56486      *     tools:[{
56487      *         type:'refresh',
56488      *         tooltip: 'Refresh form Data',
56489      *         // hidden:true,
56490      *         handler: function(event, toolEl, panel){
56491      *             // refresh logic
56492      *         }
56493      *     },
56494      *     {
56495      *         type:'help',
56496      *         tooltip: 'Get Help',
56497      *         handler: function(event, toolEl, panel){
56498      *             // show help here
56499      *         }
56500      *     }]
56501      */
56502
56503     /**
56504      * @cfg {String} [title='']
56505      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
56506      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
56507      * {@link #preventHeader} is set to `true`.
56508      */
56509
56510     /**
56511      * @cfg {String} iconCls
56512      * CSS class for icon in header. Used for displaying an icon to the left of a title.
56513      */
56514
56515     initComponent: function() {
56516         var me = this,
56517             cls;
56518
56519         me.addEvents(
56520
56521             /**
56522              * @event beforeclose
56523              * Fires before the user closes the panel. Return false from any listener to stop the close event being
56524              * fired
56525              * @param {Ext.panel.Panel} panel The Panel object
56526              */
56527             'beforeclose',
56528
56529             /**
56530              * @event beforeexpand
56531              * Fires before this panel is expanded. Return false to prevent the expand.
56532              * @param {Ext.panel.Panel} p The Panel being expanded.
56533              * @param {Boolean} animate True if the expand is animated, else false.
56534              */
56535             "beforeexpand",
56536
56537             /**
56538              * @event beforecollapse
56539              * Fires before this panel is collapsed. Return false to prevent the collapse.
56540              * @param {Ext.panel.Panel} p The Panel being collapsed.
56541              * @param {String} direction . The direction of the collapse. One of
56542              *
56543              *   - Ext.Component.DIRECTION_TOP
56544              *   - Ext.Component.DIRECTION_RIGHT
56545              *   - Ext.Component.DIRECTION_BOTTOM
56546              *   - Ext.Component.DIRECTION_LEFT
56547              *
56548              * @param {Boolean} animate True if the collapse is animated, else false.
56549              */
56550             "beforecollapse",
56551
56552             /**
56553              * @event expand
56554              * Fires after this Panel has expanded.
56555              * @param {Ext.panel.Panel} p The Panel that has been expanded.
56556              */
56557             "expand",
56558
56559             /**
56560              * @event collapse
56561              * Fires after this Panel hass collapsed.
56562              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
56563              */
56564             "collapse",
56565
56566             /**
56567              * @event titlechange
56568              * Fires after the Panel title has been set or changed.
56569              * @param {Ext.panel.Panel} p the Panel which has been resized.
56570              * @param {String} newTitle The new title.
56571              * @param {String} oldTitle The previous panel title.
56572              */
56573             'titlechange',
56574
56575             /**
56576              * @event iconchange
56577              * Fires after the Panel iconCls has been set or changed.
56578              * @param {Ext.panel.Panel} p the Panel which has been resized.
56579              * @param {String} newIconCls The new iconCls.
56580              * @param {String} oldIconCls The previous panel iconCls.
56581              */
56582             'iconchange'
56583         );
56584
56585         // Save state on these two events.
56586         this.addStateEvents('expand', 'collapse');
56587
56588         if (me.unstyled) {
56589             me.setUI('plain');
56590         }
56591
56592         if (me.frame) {
56593             me.setUI(me.ui + '-framed');
56594         }
56595
56596         // Backwards compatibility
56597         me.bridgeToolbars();
56598
56599         me.callParent();
56600         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
56601     },
56602
56603     setBorder: function(border) {
56604         // var me     = this,
56605         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
56606         //
56607         // me.callParent(arguments);
56608         //
56609         // if (me.collapsed) {
56610         //     me[method](me.collapsedCls + '-noborder');
56611         // }
56612         //
56613         // if (me.header) {
56614         //     me.header.setBorder(border);
56615         //     if (me.collapsed) {
56616         //         me.header[method](me.collapsedCls + '-noborder');
56617         //     }
56618         // }
56619
56620         this.callParent(arguments);
56621     },
56622
56623     beforeDestroy: function() {
56624         Ext.destroy(
56625             this.ghostPanel,
56626             this.dd
56627         );
56628         this.callParent();
56629     },
56630
56631     initAria: function() {
56632         this.callParent();
56633         this.initHeaderAria();
56634     },
56635
56636     initHeaderAria: function() {
56637         var me = this,
56638             el = me.el,
56639             header = me.header;
56640         if (el && header) {
56641             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
56642         }
56643     },
56644
56645     getHeader: function() {
56646         return this.header;
56647     },
56648
56649     /**
56650      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
56651      * @param {String} newTitle
56652      */
56653     setTitle: function(newTitle) {
56654         var me = this,
56655         oldTitle = this.title;
56656
56657         me.title = newTitle;
56658         if (me.header) {
56659             me.header.setTitle(newTitle);
56660         } else {
56661             me.updateHeader();
56662         }
56663
56664         if (me.reExpander) {
56665             me.reExpander.setTitle(newTitle);
56666         }
56667         me.fireEvent('titlechange', me, newTitle, oldTitle);
56668     },
56669
56670     /**
56671      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
56672      * {@link #iconchange} event after completion.
56673      * @param {String} newIconCls The new CSS class name
56674      */
56675     setIconCls: function(newIconCls) {
56676         var me = this,
56677             oldIconCls = me.iconCls;
56678
56679         me.iconCls = newIconCls;
56680         var header = me.header;
56681         if (header) {
56682             header.setIconCls(newIconCls);
56683         }
56684         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
56685     },
56686
56687     bridgeToolbars: function() {
56688         var me = this,
56689             docked = [],
56690             fbar,
56691             fbarDefaults,
56692             minButtonWidth = me.minButtonWidth;
56693
56694         function initToolbar (toolbar, pos, useButtonAlign) {
56695             if (Ext.isArray(toolbar)) {
56696                 toolbar = {
56697                     xtype: 'toolbar',
56698                     items: toolbar
56699                 };
56700             }
56701             else if (!toolbar.xtype) {
56702                 toolbar.xtype = 'toolbar';
56703             }
56704             toolbar.dock = pos;
56705             if (pos == 'left' || pos == 'right') {
56706                 toolbar.vertical = true;
56707             }
56708
56709             // Legacy support for buttonAlign (only used by buttons/fbar)
56710             if (useButtonAlign) {
56711                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
56712                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
56713                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
56714                 });
56715             }
56716             return toolbar;
56717         }
56718
56719         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
56720
56721         /**
56722          * @cfg {String} buttonAlign
56723          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
56724          * 'right' for buttons/fbar, 'left' for other toolbar types).
56725          *
56726          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
56727          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
56728          */
56729
56730         /**
56731          * @cfg {Object/Object[]} tbar
56732          * Convenience config. Short for 'Top Bar'.
56733          *
56734          *     tbar: [
56735          *       { xtype: 'button', text: 'Button 1' }
56736          *     ]
56737          *
56738          * is equivalent to
56739          *
56740          *     dockedItems: [{
56741          *         xtype: 'toolbar',
56742          *         dock: 'top',
56743          *         items: [
56744          *             { xtype: 'button', text: 'Button 1' }
56745          *         ]
56746          *     }]
56747          */
56748         if (me.tbar) {
56749             docked.push(initToolbar(me.tbar, 'top'));
56750             me.tbar = null;
56751         }
56752
56753         /**
56754          * @cfg {Object/Object[]} bbar
56755          * Convenience config. Short for 'Bottom Bar'.
56756          *
56757          *     bbar: [
56758          *       { xtype: 'button', text: 'Button 1' }
56759          *     ]
56760          *
56761          * is equivalent to
56762          *
56763          *     dockedItems: [{
56764          *         xtype: 'toolbar',
56765          *         dock: 'bottom',
56766          *         items: [
56767          *             { xtype: 'button', text: 'Button 1' }
56768          *         ]
56769          *     }]
56770          */
56771         if (me.bbar) {
56772             docked.push(initToolbar(me.bbar, 'bottom'));
56773             me.bbar = null;
56774         }
56775
56776         /**
56777          * @cfg {Object/Object[]} buttons
56778          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
56779          * synonym for the {@link #fbar} config.
56780          *
56781          *     buttons: [
56782          *       { text: 'Button 1' }
56783          *     ]
56784          *
56785          * is equivalent to
56786          *
56787          *     dockedItems: [{
56788          *         xtype: 'toolbar',
56789          *         dock: 'bottom',
56790          *         ui: 'footer',
56791          *         defaults: {minWidth: {@link #minButtonWidth}},
56792          *         items: [
56793          *             { xtype: 'component', flex: 1 },
56794          *             { xtype: 'button', text: 'Button 1' }
56795          *         ]
56796          *     }]
56797          *
56798          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56799          * each of the buttons in the buttons toolbar.
56800          */
56801         if (me.buttons) {
56802             me.fbar = me.buttons;
56803             me.buttons = null;
56804         }
56805
56806         /**
56807          * @cfg {Object/Object[]} fbar
56808          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
56809          *
56810          *     fbar: [
56811          *       { type: 'button', text: 'Button 1' }
56812          *     ]
56813          *
56814          * is equivalent to
56815          *
56816          *     dockedItems: [{
56817          *         xtype: 'toolbar',
56818          *         dock: 'bottom',
56819          *         ui: 'footer',
56820          *         defaults: {minWidth: {@link #minButtonWidth}},
56821          *         items: [
56822          *             { xtype: 'component', flex: 1 },
56823          *             { xtype: 'button', text: 'Button 1' }
56824          *         ]
56825          *     }]
56826          *
56827          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56828          * each of the buttons in the fbar.
56829          */
56830         if (me.fbar) {
56831             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
56832             fbar.ui = 'footer';
56833
56834             // Apply the minButtonWidth config to buttons in the toolbar
56835             if (minButtonWidth) {
56836                 fbarDefaults = fbar.defaults;
56837                 fbar.defaults = function(config) {
56838                     var defaults = fbarDefaults || {};
56839                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
56840                             !('minWidth' in defaults)) {
56841                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
56842                     }
56843                     return defaults;
56844                 };
56845             }
56846
56847             docked.push(fbar);
56848             me.fbar = null;
56849         }
56850
56851         /**
56852          * @cfg {Object/Object[]} lbar
56853          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
56854          *
56855          *     lbar: [
56856          *       { xtype: 'button', text: 'Button 1' }
56857          *     ]
56858          *
56859          * is equivalent to
56860          *
56861          *     dockedItems: [{
56862          *         xtype: 'toolbar',
56863          *         dock: 'left',
56864          *         items: [
56865          *             { xtype: 'button', text: 'Button 1' }
56866          *         ]
56867          *     }]
56868          */
56869         if (me.lbar) {
56870             docked.push(initToolbar(me.lbar, 'left'));
56871             me.lbar = null;
56872         }
56873
56874         /**
56875          * @cfg {Object/Object[]} rbar
56876          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
56877          *
56878          *     rbar: [
56879          *       { xtype: 'button', text: 'Button 1' }
56880          *     ]
56881          *
56882          * is equivalent to
56883          *
56884          *     dockedItems: [{
56885          *         xtype: 'toolbar',
56886          *         dock: 'right',
56887          *         items: [
56888          *             { xtype: 'button', text: 'Button 1' }
56889          *         ]
56890          *     }]
56891          */
56892         if (me.rbar) {
56893             docked.push(initToolbar(me.rbar, 'right'));
56894             me.rbar = null;
56895         }
56896
56897         if (me.dockedItems) {
56898             if (!Ext.isArray(me.dockedItems)) {
56899                 me.dockedItems = [me.dockedItems];
56900             }
56901             me.dockedItems = me.dockedItems.concat(docked);
56902         } else {
56903             me.dockedItems = docked;
56904         }
56905     },
56906
56907     /**
56908      * @private
56909      * Tools are a Panel-specific capabilty.
56910      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
56911      */
56912     initTools: function() {
56913         var me = this;
56914
56915         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
56916
56917         // Add a collapse tool unless configured to not show a collapse tool
56918         // or to not even show a header.
56919         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
56920             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
56921             me.collapseTool = me.expandTool = me.createComponent({
56922                 xtype: 'tool',
56923                 type: 'collapse-' + me.collapseDirection,
56924                 expandType: me.getOppositeDirection(me.collapseDirection),
56925                 handler: me.toggleCollapse,
56926                 scope: me
56927             });
56928
56929             // Prepend collapse tool is configured to do so.
56930             if (me.collapseFirst) {
56931                 me.tools.unshift(me.collapseTool);
56932             }
56933         }
56934
56935         // Add subclass-specific tools.
56936         me.addTools();
56937
56938         // Make Panel closable.
56939         if (me.closable) {
56940             me.addClsWithUI('closable');
56941             me.addTool({
56942                 type: 'close',
56943                 handler: Ext.Function.bind(me.close, this, [])
56944             });
56945         }
56946
56947         // Append collapse tool if needed.
56948         if (me.collapseTool && !me.collapseFirst) {
56949             me.tools.push(me.collapseTool);
56950         }
56951     },
56952
56953     /**
56954      * @private
56955      * @template
56956      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
56957      */
56958     addTools: Ext.emptyFn,
56959
56960     /**
56961      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
56962      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
56963      * close happens and will cancel the close action if it returns false.
56964      *
56965      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
56966      * {@link #destroy} and {@link #hide} methods.
56967      */
56968     close: function() {
56969         if (this.fireEvent('beforeclose', this) !== false) {
56970             this.doClose();
56971         }
56972     },
56973
56974     // private
56975     doClose: function() {
56976         this.fireEvent('close', this);
56977         this[this.closeAction]();
56978     },
56979
56980     onRender: function(ct, position) {
56981         var me = this,
56982             topContainer;
56983
56984         // Add class-specific header tools.
56985         // Panel adds collapsible and closable.
56986         me.initTools();
56987
56988         // Dock the header/title
56989         me.updateHeader();
56990
56991         // Call to super after adding the header, to prevent an unnecessary re-layout
56992         me.callParent(arguments);
56993     },
56994
56995     afterRender: function() {
56996         var me = this;
56997
56998         me.callParent(arguments);
56999
57000         // Instate the collapsed state after render. We need to wait for
57001         // this moment so that we have established at least some of our size (from our
57002         // configured dimensions or from content via the component layout)
57003         if (me.collapsed) {
57004             me.collapsed = false;
57005             me.collapse(null, false, true);
57006         }
57007     },
57008
57009     /**
57010      * Create, hide, or show the header component as appropriate based on the current config.
57011      * @private
57012      * @param {Boolean} force True to force the header to be created
57013      */
57014     updateHeader: function(force) {
57015         var me = this,
57016             header = me.header,
57017             title = me.title,
57018             tools = me.tools;
57019
57020         if (!me.preventHeader && (force || title || (tools && tools.length))) {
57021             if (!header) {
57022                 header = me.header = Ext.create('Ext.panel.Header', {
57023                     title       : title,
57024                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
57025                     dock        : me.headerPosition || 'top',
57026                     textCls     : me.headerTextCls,
57027                     iconCls     : me.iconCls,
57028                     baseCls     : me.baseCls + '-header',
57029                     tools       : tools,
57030                     ui          : me.ui,
57031                     indicateDrag: me.draggable,
57032                     border      : me.border,
57033                     frame       : me.frame && me.frameHeader,
57034                     ignoreParentFrame : me.frame || me.overlapHeader,
57035                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
57036                     listeners   : me.collapsible && me.titleCollapse ? {
57037                         click: me.toggleCollapse,
57038                         scope: me
57039                     } : null
57040                 });
57041                 me.addDocked(header, 0);
57042
57043                 // Reference the Header's tool array.
57044                 // Header injects named references.
57045                 me.tools = header.tools;
57046             }
57047             header.show();
57048             me.initHeaderAria();
57049         } else if (header) {
57050             header.hide();
57051         }
57052     },
57053
57054     // inherit docs
57055     setUI: function(ui) {
57056         var me = this;
57057
57058         me.callParent(arguments);
57059
57060         if (me.header) {
57061             me.header.setUI(ui);
57062         }
57063     },
57064
57065     // private
57066     getContentTarget: function() {
57067         return this.body;
57068     },
57069
57070     getTargetEl: function() {
57071         return this.body || this.frameBody || this.el;
57072     },
57073
57074     // the overrides below allow for collapsed regions inside the border layout to be hidden
57075
57076     // inherit docs
57077     isVisible: function(deep){
57078         var me = this;
57079         if (me.collapsed && me.placeholder) {
57080             return me.placeholder.isVisible(deep);
57081         }
57082         return me.callParent(arguments);
57083     },
57084
57085     // inherit docs
57086     onHide: function(){
57087         var me = this;
57088         if (me.collapsed && me.placeholder) {
57089             me.placeholder.hide();
57090         } else {
57091             me.callParent(arguments);
57092         }
57093     },
57094
57095     // inherit docs
57096     onShow: function(){
57097         var me = this;
57098         if (me.collapsed && me.placeholder) {
57099             // force hidden back to true, since this gets set by the layout
57100             me.hidden = true;
57101             me.placeholder.show();
57102         } else {
57103             me.callParent(arguments);
57104         }
57105     },
57106
57107     addTool: function(tool) {
57108         var me = this,
57109             header = me.header;
57110
57111         if (Ext.isArray(tool)) {
57112             Ext.each(tool, me.addTool, me);
57113             return;
57114         }
57115         me.tools.push(tool);
57116         if (header) {
57117             header.addTool(tool);
57118         }
57119         me.updateHeader();
57120     },
57121
57122     getOppositeDirection: function(d) {
57123         var c = Ext.Component;
57124         switch (d) {
57125             case c.DIRECTION_TOP:
57126                 return c.DIRECTION_BOTTOM;
57127             case c.DIRECTION_RIGHT:
57128                 return c.DIRECTION_LEFT;
57129             case c.DIRECTION_BOTTOM:
57130                 return c.DIRECTION_TOP;
57131             case c.DIRECTION_LEFT:
57132                 return c.DIRECTION_RIGHT;
57133         }
57134     },
57135
57136     /**
57137      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
57138      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
57139      * collapse action if it returns false.
57140      *
57141      * @param {String} direction . The direction to collapse towards. Must be one of
57142      *
57143      *   - Ext.Component.DIRECTION_TOP
57144      *   - Ext.Component.DIRECTION_RIGHT
57145      *   - Ext.Component.DIRECTION_BOTTOM
57146      *   - Ext.Component.DIRECTION_LEFT
57147      *
57148      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57149      * {@link #animCollapse} panel config)
57150      * @return {Ext.panel.Panel} this
57151      */
57152     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
57153         var me = this,
57154             c = Ext.Component,
57155             height = me.getHeight(),
57156             width = me.getWidth(),
57157             frameInfo,
57158             newSize = 0,
57159             dockedItems = me.dockedItems.items,
57160             dockedItemCount = dockedItems.length,
57161             i = 0,
57162             comp,
57163             pos,
57164             anim = {
57165                 from: {
57166                     height: height,
57167                     width: width
57168                 },
57169                 to: {
57170                     height: height,
57171                     width: width
57172                 },
57173                 listeners: {
57174                     afteranimate: me.afterCollapse,
57175                     scope: me
57176                 },
57177                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
57178             },
57179             reExpander,
57180             reExpanderOrientation,
57181             reExpanderDock,
57182             getDimension,
57183             collapseDimension;
57184
57185         if (!direction) {
57186             direction = me.collapseDirection;
57187         }
57188
57189         // If internal (Called because of initial collapsed state), then no animation, and no events.
57190         if (internal) {
57191             animate = false;
57192         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
57193             return false;
57194         }
57195
57196         reExpanderDock = direction;
57197         me.expandDirection = me.getOppositeDirection(direction);
57198
57199         // Track docked items which we hide during collapsed state
57200         me.hiddenDocked = [];
57201
57202         switch (direction) {
57203             case c.DIRECTION_TOP:
57204             case c.DIRECTION_BOTTOM:
57205                 reExpanderOrientation = 'horizontal';
57206                 collapseDimension = 'height';
57207                 getDimension = 'getHeight';
57208
57209                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
57210                 // Also, collect all other docked items which we must hide after collapse.
57211                 for (; i < dockedItemCount; i++) {
57212                     comp = dockedItems[i];
57213                     if (comp.isVisible()) {
57214                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
57215                             reExpander = comp;
57216                         } else {
57217                             me.hiddenDocked.push(comp);
57218                         }
57219                     } else if (comp === me.reExpander) {
57220                         reExpander = comp;
57221                     }
57222                 }
57223
57224                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
57225                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57226                     anim.from.top = pos;
57227                 }
57228                 break;
57229
57230             case c.DIRECTION_LEFT:
57231             case c.DIRECTION_RIGHT:
57232                 reExpanderOrientation = 'vertical';
57233                 collapseDimension = 'width';
57234                 getDimension = 'getWidth';
57235
57236                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
57237                 // Also, collect all other docked items which we must hide after collapse.
57238                 for (; i < dockedItemCount; i++) {
57239                     comp = dockedItems[i];
57240                     if (comp.isVisible()) {
57241                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
57242                             reExpander = comp;
57243                         } else {
57244                             me.hiddenDocked.push(comp);
57245                         }
57246                     } else if (comp === me.reExpander) {
57247                         reExpander = comp;
57248                     }
57249                 }
57250
57251                 if (direction == Ext.Component.DIRECTION_RIGHT) {
57252                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57253                     anim.from.left = pos;
57254                 }
57255                 break;
57256
57257             default:
57258                 throw('Panel collapse must be passed a valid Component collapse direction');
57259         }
57260
57261         // Disable toggle tool during animated collapse
57262         if (animate && me.collapseTool) {
57263             me.collapseTool.disable();
57264         }
57265
57266         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
57267         me.addClsWithUI(me.collapsedCls);
57268         // if (me.border === false) {
57269         //     me.addClsWithUI(me.collapsedCls + '-noborder');
57270         // }
57271
57272         // We found a header: Measure it to find the collapse-to size.
57273         if (reExpander && reExpander.rendered) {
57274
57275             //we must add the collapsed cls to the header and then remove to get the proper height
57276             reExpander.addClsWithUI(me.collapsedCls);
57277             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57278             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57279                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57280             }
57281
57282             frameInfo = reExpander.getFrameInfo();
57283
57284             //get the size
57285             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
57286
57287             //and remove
57288             reExpander.removeClsWithUI(me.collapsedCls);
57289             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57290             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57291                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57292             }
57293         }
57294         // No header: Render and insert a temporary one, and then measure it.
57295         else {
57296             reExpander = {
57297                 hideMode: 'offsets',
57298                 temporary: true,
57299                 title: me.title,
57300                 orientation: reExpanderOrientation,
57301                 dock: reExpanderDock,
57302                 textCls: me.headerTextCls,
57303                 iconCls: me.iconCls,
57304                 baseCls: me.baseCls + '-header',
57305                 ui: me.ui,
57306                 frame: me.frame && me.frameHeader,
57307                 ignoreParentFrame: me.frame || me.overlapHeader,
57308                 indicateDrag: me.draggable,
57309                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
57310                 renderTo: me.el
57311             };
57312             if (!me.hideCollapseTool) {
57313                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
57314                     xtype: 'tool',
57315                     type: 'expand-' + me.expandDirection,
57316                     handler: me.toggleCollapse,
57317                     scope: me
57318                 }];
57319             }
57320
57321             // Capture the size of the re-expander.
57322             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
57323             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
57324             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
57325             reExpander.hide();
57326
57327             // Insert the new docked item
57328             me.insertDocked(0, reExpander);
57329         }
57330
57331         me.reExpander = reExpander;
57332         me.reExpander.addClsWithUI(me.collapsedCls);
57333         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57334         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57335             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57336         }
57337
57338         // If collapsing right or down, we'll be also animating the left or top.
57339         if (direction == Ext.Component.DIRECTION_RIGHT) {
57340             anim.to.left = pos + (width - newSize);
57341         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
57342             anim.to.top = pos + (height - newSize);
57343         }
57344
57345         // Animate to the new size
57346         anim.to[collapseDimension] = newSize;
57347
57348         // When we collapse a panel, the panel is in control of one dimension (depending on
57349         // collapse direction) and sets that on the component. We must restore the user's
57350         // original value (including non-existance) when we expand. Using this technique, we
57351         // mimic setCalculatedSize for the dimension we do not control and setSize for the
57352         // one we do (only while collapsed).
57353         if (!me.collapseMemento) {
57354             me.collapseMemento = new Ext.util.Memento(me);
57355         }
57356         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
57357
57358         // Remove any flex config before we attempt to collapse.
57359         me.savedFlex = me.flex;
57360         me.minWidth = 0;
57361         me.minHeight = 0;
57362         delete me.flex;
57363         me.suspendLayout = true;
57364
57365         if (animate) {
57366             me.animate(anim);
57367         } else {
57368             me.setSize(anim.to.width, anim.to.height);
57369             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
57370                 me.setPosition(anim.to.left, anim.to.top);
57371             }
57372             me.afterCollapse(false, internal);
57373         }
57374         return me;
57375     },
57376
57377     afterCollapse: function(animated, internal) {
57378         var me = this,
57379             i = 0,
57380             l = me.hiddenDocked.length;
57381
57382         me.collapseMemento.restore(['minWidth', 'minHeight']);
57383
57384         // Now we can restore the dimension we don't control to its original state
57385         // Leave the value in the memento so that it can be correctly restored
57386         // if it is set by animation.
57387         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
57388             me.layoutManagedHeight = 2;
57389             me.collapseMemento.restore('width', false);
57390         } else {
57391             me.layoutManagedWidth = 2;
57392             me.collapseMemento.restore('height', false);
57393         }
57394
57395         // We must hide the body, otherwise it overlays docked items which come before
57396         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
57397         me.saveScrollTop = me.body.dom.scrollTop;
57398         me.body.setStyle('display', 'none');
57399
57400         for (; i < l; i++) {
57401             me.hiddenDocked[i].hide();
57402         }
57403         if (me.reExpander) {
57404             me.reExpander.updateFrame();
57405             me.reExpander.show();
57406         }
57407         me.collapsed = true;
57408         me.suspendLayout = false;
57409
57410         if (!internal) {
57411             if (me.ownerCt) {
57412                 // Because Component layouts only inform upstream containers if they have changed size,
57413                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
57414                 if (animated) {
57415                     me.ownerCt.layout.layout();
57416                 }
57417             } else if (me.reExpander.temporary) {
57418                 me.doComponentLayout();
57419             }
57420         }
57421
57422         if (me.resizer) {
57423             me.resizer.disable();
57424         }
57425
57426         // If me Panel was configured with a collapse tool in its header, flip it's type
57427         if (me.collapseTool) {
57428             me.collapseTool.setType('expand-' + me.expandDirection);
57429         }
57430         if (!internal) {
57431             me.fireEvent('collapse', me);
57432         }
57433
57434         // Re-enable the toggle tool after an animated collapse
57435         if (animated && me.collapseTool) {
57436             me.collapseTool.enable();
57437         }
57438     },
57439
57440     /**
57441      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
57442      * expand action if it returns false.
57443      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57444      * {@link #animCollapse} panel config)
57445      * @return {Ext.panel.Panel} this
57446      */
57447     expand: function(animate) {
57448         var me = this;
57449         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
57450             return false;
57451         }
57452
57453         var i = 0,
57454             l = me.hiddenDocked.length,
57455             direction = me.expandDirection,
57456             height = me.getHeight(),
57457             width = me.getWidth(),
57458             pos, anim;
57459
57460         // Disable toggle tool during animated expand
57461         if (animate && me.collapseTool) {
57462             me.collapseTool.disable();
57463         }
57464
57465         // Show any docked items that we hid on collapse
57466         // And hide the injected reExpander Header
57467         for (; i < l; i++) {
57468             me.hiddenDocked[i].hidden = false;
57469             me.hiddenDocked[i].el.show();
57470         }
57471         if (me.reExpander) {
57472             if (me.reExpander.temporary) {
57473                 me.reExpander.hide();
57474             } else {
57475                 me.reExpander.removeClsWithUI(me.collapsedCls);
57476                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
57477                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57478                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57479                 }
57480                 me.reExpander.updateFrame();
57481             }
57482         }
57483
57484         // If me Panel was configured with a collapse tool in its header, flip it's type
57485         if (me.collapseTool) {
57486             me.collapseTool.setType('collapse-' + me.collapseDirection);
57487         }
57488
57489         // Restore body display and scroll position
57490         me.body.setStyle('display', '');
57491         me.body.dom.scrollTop = me.saveScrollTop;
57492
57493         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
57494         me.collapsed = false;
57495
57496         // Remove any collapsed styling before any animation begins
57497         me.removeClsWithUI(me.collapsedCls);
57498         // if (me.border === false) {
57499         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
57500         // }
57501
57502         anim = {
57503             to: {
57504             },
57505             from: {
57506                 height: height,
57507                 width: width
57508             },
57509             listeners: {
57510                 afteranimate: me.afterExpand,
57511                 scope: me
57512             }
57513         };
57514
57515         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
57516
57517             // Restore the collapsed dimension.
57518             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57519             me.collapseMemento.restore('height', false);
57520
57521             // If autoHeight, measure the height now we have shown the body element.
57522             if (me.height === undefined) {
57523                 me.setCalculatedSize(me.width, null);
57524                 anim.to.height = me.getHeight();
57525
57526                 // Must size back down to collapsed for the animation.
57527                 me.setCalculatedSize(me.width, anim.from.height);
57528             }
57529             // If we were flexed, then we can't just restore to the saved size.
57530             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57531             else if (me.savedFlex) {
57532                 me.flex = me.savedFlex;
57533                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
57534                 delete me.flex;
57535             }
57536             // Else, restore to saved height
57537             else {
57538                 anim.to.height = me.height;
57539             }
57540
57541             // top needs animating upwards
57542             if (direction == Ext.Component.DIRECTION_TOP) {
57543                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57544                 anim.from.top = pos;
57545                 anim.to.top = pos - (anim.to.height - height);
57546             }
57547         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
57548
57549             // Restore the collapsed dimension.
57550             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57551             me.collapseMemento.restore('width', false);
57552
57553             // If autoWidth, measure the width now we have shown the body element.
57554             if (me.width === undefined) {
57555                 me.setCalculatedSize(null, me.height);
57556                 anim.to.width = me.getWidth();
57557
57558                 // Must size back down to collapsed for the animation.
57559                 me.setCalculatedSize(anim.from.width, me.height);
57560             }
57561             // If we were flexed, then we can't just restore to the saved size.
57562             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57563             else if (me.savedFlex) {
57564                 me.flex = me.savedFlex;
57565                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
57566                 delete me.flex;
57567             }
57568             // Else, restore to saved width
57569             else {
57570                 anim.to.width = me.width;
57571             }
57572
57573             // left needs animating leftwards
57574             if (direction == Ext.Component.DIRECTION_LEFT) {
57575                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57576                 anim.from.left = pos;
57577                 anim.to.left = pos - (anim.to.width - width);
57578             }
57579         }
57580
57581         if (animate) {
57582             me.animate(anim);
57583         } else {
57584             me.setCalculatedSize(anim.to.width, anim.to.height);
57585             if (anim.to.x) {
57586                 me.setLeft(anim.to.x);
57587             }
57588             if (anim.to.y) {
57589                 me.setTop(anim.to.y);
57590             }
57591             me.afterExpand(false);
57592         }
57593
57594         return me;
57595     },
57596
57597     afterExpand: function(animated) {
57598         var me = this;
57599
57600         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
57601         if (me.savedFlex) {
57602             me.flex = me.savedFlex;
57603             delete me.savedFlex;
57604             delete me.width;
57605             delete me.height;
57606         }
57607
57608         // Restore width/height and dimension management flags to original values
57609         if (me.collapseMemento) {
57610             me.collapseMemento.restoreAll();
57611         }
57612
57613         if (animated && me.ownerCt) {
57614             // IE 6 has an intermittent repaint issue in this case so give
57615             // it a little extra time to catch up before laying out.
57616             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
57617         }
57618
57619         if (me.resizer) {
57620             me.resizer.enable();
57621         }
57622
57623         me.fireEvent('expand', me);
57624
57625         // Re-enable the toggle tool after an animated expand
57626         if (animated && me.collapseTool) {
57627             me.collapseTool.enable();
57628         }
57629     },
57630
57631     /**
57632      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
57633      * @return {Ext.panel.Panel} this
57634      */
57635     toggleCollapse: function() {
57636         if (this.collapsed) {
57637             this.expand(this.animCollapse);
57638         } else {
57639             this.collapse(this.collapseDirection, this.animCollapse);
57640         }
57641         return this;
57642     },
57643
57644     // private
57645     getKeyMap : function(){
57646         if(!this.keyMap){
57647             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
57648         }
57649         return this.keyMap;
57650     },
57651
57652     // private
57653     initDraggable : function(){
57654         /**
57655          * @property {Ext.dd.DragSource} dd
57656          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
57657          * Ext.dd.DragSource} which handles dragging the Panel.
57658          *
57659          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
57660          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
57661          */
57662         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
57663     },
57664
57665     // private - helper function for ghost
57666     ghostTools : function() {
57667         var tools = [],
57668             headerTools = this.header.query('tool[hidden=false]');
57669
57670         if (headerTools.length) {
57671             Ext.each(headerTools, function(tool) {
57672                 // Some tools can be full components, and copying them into the ghost
57673                 // actually removes them from the owning panel. You could also potentially
57674                 // end up with duplicate DOM ids as well. To avoid any issues we just make
57675                 // a simple bare-minimum clone of each tool for ghosting purposes.
57676                 tools.push({
57677                     type: tool.type
57678                 });
57679             });
57680         } else {
57681             tools = [{
57682                 type: 'placeholder'
57683             }];
57684         }
57685         return tools;
57686     },
57687
57688     // private - used for dragging
57689     ghost: function(cls) {
57690         var me = this,
57691             ghostPanel = me.ghostPanel,
57692             box = me.getBox(),
57693             header;
57694
57695         if (!ghostPanel) {
57696             ghostPanel = Ext.create('Ext.panel.Panel', {
57697                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
57698                 floating: {
57699                     shadow: false
57700                 },
57701                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
57702                 overlapHeader: me.overlapHeader,
57703                 headerPosition: me.headerPosition,
57704                 baseCls: me.baseCls,
57705                 cls: me.baseCls + '-ghost ' + (cls ||'')
57706             });
57707             me.ghostPanel = ghostPanel;
57708         }
57709         ghostPanel.floatParent = me.floatParent;
57710         if (me.floating) {
57711             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
57712         } else {
57713             ghostPanel.toFront();
57714         }
57715         header = ghostPanel.header;
57716         // restore options
57717         if (header) {
57718             header.suspendLayout = true;
57719             Ext.Array.forEach(header.query('tool'), function(tool){
57720                 header.remove(tool);
57721             });
57722             header.suspendLayout = false;
57723         }
57724         ghostPanel.addTool(me.ghostTools());
57725         ghostPanel.setTitle(me.title);
57726         ghostPanel.setIconCls(me.iconCls);
57727
57728         ghostPanel.el.show();
57729         ghostPanel.setPosition(box.x, box.y);
57730         ghostPanel.setSize(box.width, box.height);
57731         me.el.hide();
57732         if (me.floatingItems) {
57733             me.floatingItems.hide();
57734         }
57735         return ghostPanel;
57736     },
57737
57738     // private
57739     unghost: function(show, matchPosition) {
57740         var me = this;
57741         if (!me.ghostPanel) {
57742             return;
57743         }
57744         if (show !== false) {
57745             me.el.show();
57746             if (matchPosition !== false) {
57747                 me.setPosition(me.ghostPanel.getPosition());
57748             }
57749             if (me.floatingItems) {
57750                 me.floatingItems.show();
57751             }
57752             Ext.defer(me.focus, 10, me);
57753         }
57754         me.ghostPanel.el.hide();
57755     },
57756
57757     initResizable: function(resizable) {
57758         if (this.collapsed) {
57759             resizable.disabled = true;
57760         }
57761         this.callParent([resizable]);
57762     }
57763 }, function(){
57764     this.prototype.animCollapse = Ext.enableFx;
57765 });
57766
57767 /**
57768  * Component layout for Tip/ToolTip/etc. components
57769  * @class Ext.layout.component.Tip
57770  * @extends Ext.layout.component.Dock
57771  * @private
57772  */
57773
57774 Ext.define('Ext.layout.component.Tip', {
57775
57776     /* Begin Definitions */
57777
57778     alias: ['layout.tip'],
57779
57780     extend: 'Ext.layout.component.Dock',
57781
57782     /* End Definitions */
57783
57784     type: 'tip',
57785     
57786     onLayout: function(width, height) {
57787         var me = this,
57788             owner = me.owner,
57789             el = owner.el,
57790             minWidth,
57791             maxWidth,
57792             naturalWidth,
57793             constrainedWidth,
57794             xy = el.getXY();
57795
57796         // Position offscreen so the natural width is not affected by the viewport's right edge
57797         el.setXY([-9999,-9999]);
57798
57799         // Calculate initial layout
57800         this.callParent(arguments);
57801
57802         // Handle min/maxWidth for auto-width tips
57803         if (!Ext.isNumber(width)) {
57804             minWidth = owner.minWidth;
57805             maxWidth = owner.maxWidth;
57806             // IE6/7 in strict mode have a problem doing an autoWidth
57807             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
57808                 constrainedWidth = me.doAutoWidth();
57809             } else {
57810                 naturalWidth = el.getWidth();
57811             }
57812             if (naturalWidth < minWidth) {
57813                 constrainedWidth = minWidth;
57814             }
57815             else if (naturalWidth > maxWidth) {
57816                 constrainedWidth = maxWidth;
57817             }
57818             if (constrainedWidth) {
57819                 this.callParent([constrainedWidth, height]);
57820             }
57821         }
57822
57823         // Restore position
57824         el.setXY(xy);
57825     },
57826     
57827     doAutoWidth: function(){
57828         var me = this,
57829             owner = me.owner,
57830             body = owner.body,
57831             width = body.getTextWidth();
57832             
57833         if (owner.header) {
57834             width = Math.max(width, owner.header.getWidth());
57835         }
57836         if (!Ext.isDefined(me.frameWidth)) {
57837             me.frameWidth = owner.el.getWidth() - body.getWidth();
57838         }
57839         width += me.frameWidth + body.getPadding('lr');
57840         return width;
57841     }
57842 });
57843
57844 /**
57845  * @class Ext.tip.Tip
57846  * @extends Ext.panel.Panel
57847  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
57848  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
57849  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
57850  * @xtype tip
57851  */
57852 Ext.define('Ext.tip.Tip', {
57853     extend: 'Ext.panel.Panel',
57854     requires: [ 'Ext.layout.component.Tip' ],
57855     alternateClassName: 'Ext.Tip',
57856     /**
57857      * @cfg {Boolean} [closable=false]
57858      * True to render a close tool button into the tooltip header.
57859      */
57860     /**
57861      * @cfg {Number} width
57862      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
57863      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
57864      */
57865     /**
57866      * @cfg {Number} minWidth The minimum width of the tip in pixels.
57867      */
57868     minWidth : 40,
57869     /**
57870      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
57871      */
57872     maxWidth : 300,
57873     /**
57874      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
57875      * for bottom-right shadow.
57876      */
57877     shadow : "sides",
57878
57879     /**
57880      * @cfg {String} defaultAlign
57881      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
57882      * to its element of origin.
57883      */
57884     defaultAlign : "tl-bl?",
57885     /**
57886      * @cfg {Boolean} constrainPosition
57887      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
57888      */
57889     constrainPosition : true,
57890
57891     // @inherited
57892     frame: false,
57893
57894     // private panel overrides
57895     autoRender: true,
57896     hidden: true,
57897     baseCls: Ext.baseCSSPrefix + 'tip',
57898     floating: {
57899         shadow: true,
57900         shim: true,
57901         constrain: true
57902     },
57903     focusOnToFront: false,
57904     componentLayout: 'tip',
57905
57906     /**
57907      * @cfg {String} closeAction
57908      * <p>The action to take when the close header tool is clicked:
57909      * <div class="mdetail-params"><ul>
57910      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
57911      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
57912      * it and all descendant Components. The window will <b>not</b> be available to be
57913      * redisplayed via the {@link #show} method.
57914      * </div></li>
57915      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
57916      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
57917      * The window will be available to be redisplayed via the {@link #show} method.
57918      * </div></li>
57919      * </ul></div>
57920      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
57921      * which will invoke the approriate closeAction.
57922      */
57923     closeAction: 'hide',
57924
57925     ariaRole: 'tooltip',
57926
57927     initComponent: function() {
57928         var me = this;
57929
57930         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
57931         me.callParent(arguments);
57932
57933         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
57934         me.constrain = me.constrain || me.constrainPosition;
57935     },
57936
57937     /**
57938      * Shows this tip at the specified XY position.  Example usage:
57939      * <pre><code>
57940 // Show the tip at x:50 and y:100
57941 tip.showAt([50,100]);
57942 </code></pre>
57943      * @param {Number[]} xy An array containing the x and y coordinates
57944      */
57945     showAt : function(xy){
57946         var me = this;
57947         this.callParent(arguments);
57948         // Show may have been vetoed.
57949         if (me.isVisible()) {
57950             me.setPagePosition(xy[0], xy[1]);
57951             if (me.constrainPosition || me.constrain) {
57952                 me.doConstrain();
57953             }
57954             me.toFront(true);
57955         }
57956     },
57957
57958     /**
57959      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
57960      * anchor position value.  Example usage:
57961      * <pre><code>
57962 // Show the tip at the default position ('tl-br?')
57963 tip.showBy('my-el');
57964
57965 // Show the tip's top-left corner anchored to the element's top-right corner
57966 tip.showBy('my-el', 'tl-tr');
57967 </code></pre>
57968      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
57969      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
57970      * {@link #defaultAlign} if specified).
57971      */
57972     showBy : function(el, pos) {
57973         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
57974     },
57975
57976     /**
57977      * @private
57978      * @override
57979      * Set Tip draggable using base Component's draggability
57980      */
57981     initDraggable : function(){
57982         var me = this;
57983         me.draggable = {
57984             el: me.getDragEl(),
57985             delegate: me.header.el,
57986             constrain: me,
57987             constrainTo: me.el.getScopeParent()
57988         };
57989         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
57990         Ext.Component.prototype.initDraggable.call(me);
57991     },
57992
57993     // Tip does not ghost. Drag is "live"
57994     ghost: undefined,
57995     unghost: undefined
57996 });
57997
57998 /**
57999  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
58000  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
58001  * control over the tooltip's alignment relative to the target element or mouse, and the timing
58002  * of when it is automatically shown and hidden.
58003  *
58004  * This implementation does **not** have a built-in method of automatically populating the tooltip's
58005  * text based on the target element; you must either configure a fixed {@link #html} value for each
58006  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
58007  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
58008  * convenient way of automatically populating and configuring a tooltip based on specific DOM
58009  * attributes of each target element.
58010  *
58011  * # Basic Example
58012  *
58013  *     var tip = Ext.create('Ext.tip.ToolTip', {
58014  *         target: 'clearButton',
58015  *         html: 'Press this button to clear the form'
58016  *     });
58017  *
58018  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
58019  *
58020  * # Delegation
58021  *
58022  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
58023  * one ToolTip to many elements under a common parent. This is more efficient than creating many
58024  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
58025  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
58026  * appropriate sub-elements.
58027  *
58028  * When using delegation, it is likely that you will want to programmatically change the content
58029  * of the ToolTip based on each delegate element; you can do this by implementing a custom
58030  * listener for the {@link #beforeshow} event. Example:
58031  *
58032  *     var store = Ext.create('Ext.data.ArrayStore', {
58033  *         fields: ['company', 'price', 'change'],
58034  *         data: [
58035  *             ['3m Co',                               71.72, 0.02],
58036  *             ['Alcoa Inc',                           29.01, 0.42],
58037  *             ['Altria Group Inc',                    83.81, 0.28],
58038  *             ['American Express Company',            52.55, 0.01],
58039  *             ['American International Group, Inc.',  64.13, 0.31],
58040  *             ['AT&T Inc.',                           31.61, -0.48]
58041  *         ]
58042  *     });
58043  *
58044  *     var grid = Ext.create('Ext.grid.Panel', {
58045  *         title: 'Array Grid',
58046  *         store: store,
58047  *         columns: [
58048  *             {text: 'Company', flex: 1, dataIndex: 'company'},
58049  *             {text: 'Price', width: 75, dataIndex: 'price'},
58050  *             {text: 'Change', width: 75, dataIndex: 'change'}
58051  *         ],
58052  *         height: 200,
58053  *         width: 400,
58054  *         renderTo: Ext.getBody()
58055  *     });
58056  *
58057  *     grid.getView().on('render', function(view) {
58058  *         view.tip = Ext.create('Ext.tip.ToolTip', {
58059  *             // The overall target element.
58060  *             target: view.el,
58061  *             // Each grid row causes its own seperate show and hide.
58062  *             delegate: view.itemSelector,
58063  *             // Moving within the row should not hide the tip.
58064  *             trackMouse: true,
58065  *             // Render immediately so that tip.body can be referenced prior to the first show.
58066  *             renderTo: Ext.getBody(),
58067  *             listeners: {
58068  *                 // Change content dynamically depending on which element triggered the show.
58069  *                 beforeshow: function updateTipBody(tip) {
58070  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
58071  *                 }
58072  *             }
58073  *         });
58074  *     });
58075  *
58076  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
58077  *
58078  * # Alignment
58079  *
58080  * The following configuration properties allow control over how the ToolTip is aligned relative to
58081  * the target element and/or mouse pointer:
58082  *
58083  * - {@link #anchor}
58084  * - {@link #anchorToTarget}
58085  * - {@link #anchorOffset}
58086  * - {@link #trackMouse}
58087  * - {@link #mouseOffset}
58088  *
58089  * # Showing/Hiding
58090  *
58091  * The following configuration properties allow control over how and when the ToolTip is automatically
58092  * shown and hidden:
58093  *
58094  * - {@link #autoHide}
58095  * - {@link #showDelay}
58096  * - {@link #hideDelay}
58097  * - {@link #dismissDelay}
58098  *
58099  * @docauthor Jason Johnston <jason@sencha.com>
58100  */
58101 Ext.define('Ext.tip.ToolTip', {
58102     extend: 'Ext.tip.Tip',
58103     alias: 'widget.tooltip',
58104     alternateClassName: 'Ext.ToolTip',
58105     /**
58106      * @property {HTMLElement} triggerElement
58107      * When a ToolTip is configured with the `{@link #delegate}`
58108      * option to cause selected child elements of the `{@link #target}`
58109      * Element to each trigger a seperate show event, this property is set to
58110      * the DOM element which triggered the show.
58111      */
58112     /**
58113      * @cfg {HTMLElement/Ext.Element/String} target
58114      * The target element or string id to monitor for mouseover events to trigger
58115      * showing this ToolTip.
58116      */
58117     /**
58118      * @cfg {Boolean} [autoHide=true]
58119      * True to automatically hide the tooltip after the
58120      * mouse exits the target element or after the `{@link #dismissDelay}`
58121      * has expired if set.  If `{@link #closable} = true`
58122      * a close tool button will be rendered into the tooltip header.
58123      */
58124     /**
58125      * @cfg {Number} showDelay
58126      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
58127      */
58128     showDelay: 500,
58129     /**
58130      * @cfg {Number} hideDelay
58131      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
58132      * Set to 0 for the tooltip to hide immediately.
58133      */
58134     hideDelay: 200,
58135     /**
58136      * @cfg {Number} dismissDelay
58137      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
58138      * dismissDelay = 0.
58139      */
58140     dismissDelay: 5000,
58141     /**
58142      * @cfg {Number[]} [mouseOffset=[15,18]]
58143      * An XY offset from the mouse position where the tooltip should be shown.
58144      */
58145     /**
58146      * @cfg {Boolean} trackMouse
58147      * True to have the tooltip follow the mouse as it moves over the target element.
58148      */
58149     trackMouse: false,
58150     /**
58151      * @cfg {String} anchor
58152      * If specified, indicates that the tip should be anchored to a
58153      * particular side of the target element or mouse pointer ("top", "right", "bottom",
58154      * or "left"), with an arrow pointing back at the target or mouse pointer. If
58155      * {@link #constrainPosition} is enabled, this will be used as a preferred value
58156      * only and may be flipped as needed.
58157      */
58158     /**
58159      * @cfg {Boolean} anchorToTarget
58160      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
58161      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
58162      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
58163      */
58164     anchorToTarget: true,
58165     /**
58166      * @cfg {Number} anchorOffset
58167      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
58168      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
58169      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
58170      * a vertical offset.
58171      */
58172     anchorOffset: 0,
58173     /**
58174      * @cfg {String} delegate
58175      *
58176      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
58177      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
58178      * target.
58179      *
58180      * When specified, the child element of the target which caused a show event is placed into the
58181      * `{@link #triggerElement}` property before the ToolTip is shown.
58182      *
58183      * This may be useful when a Component has regular, repeating elements in it, each of which need a
58184      * ToolTip which contains information specific to that element.
58185      *
58186      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
58187      */
58188
58189     // private
58190     targetCounter: 0,
58191     quickShowInterval: 250,
58192
58193     // private
58194     initComponent: function() {
58195         var me = this;
58196         me.callParent(arguments);
58197         me.lastActive = new Date();
58198         me.setTarget(me.target);
58199         me.origAnchor = me.anchor;
58200     },
58201
58202     // private
58203     onRender: function(ct, position) {
58204         var me = this;
58205         me.callParent(arguments);
58206         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58207         me.anchorEl = me.el.createChild({
58208             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
58209         });
58210     },
58211
58212     // private
58213     afterRender: function() {
58214         var me = this,
58215             zIndex;
58216
58217         me.callParent(arguments);
58218         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
58219         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
58220     },
58221
58222     /**
58223      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
58224      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
58225      */
58226     setTarget: function(target) {
58227         var me = this,
58228             t = Ext.get(target),
58229             tg;
58230
58231         if (me.target) {
58232             tg = Ext.get(me.target);
58233             me.mun(tg, 'mouseover', me.onTargetOver, me);
58234             me.mun(tg, 'mouseout', me.onTargetOut, me);
58235             me.mun(tg, 'mousemove', me.onMouseMove, me);
58236         }
58237
58238         me.target = t;
58239         if (t) {
58240
58241             me.mon(t, {
58242                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
58243                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
58244                 freezeEvent: true,
58245
58246                 mouseover: me.onTargetOver,
58247                 mouseout: me.onTargetOut,
58248                 mousemove: me.onMouseMove,
58249                 scope: me
58250             });
58251         }
58252         if (me.anchor) {
58253             me.anchorTarget = me.target;
58254         }
58255     },
58256
58257     // private
58258     onMouseMove: function(e) {
58259         var me = this,
58260             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
58261             xy;
58262         if (t) {
58263             me.targetXY = e.getXY();
58264             if (t === me.triggerElement) {
58265                 if (!me.hidden && me.trackMouse) {
58266                     xy = me.getTargetXY();
58267                     if (me.constrainPosition) {
58268                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
58269                     }
58270                     me.setPagePosition(xy);
58271                 }
58272             } else {
58273                 me.hide();
58274                 me.lastActive = new Date(0);
58275                 me.onTargetOver(e);
58276             }
58277         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
58278             me.hide();
58279         }
58280     },
58281
58282     // private
58283     getTargetXY: function() {
58284         var me = this,
58285             mouseOffset;
58286         if (me.delegate) {
58287             me.anchorTarget = me.triggerElement;
58288         }
58289         if (me.anchor) {
58290             me.targetCounter++;
58291                 var offsets = me.getOffsets(),
58292                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
58293                     dw = Ext.Element.getViewWidth() - 5,
58294                     dh = Ext.Element.getViewHeight() - 5,
58295                     de = document.documentElement,
58296                     bd = document.body,
58297                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
58298                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
58299                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
58300                     sz = me.getSize(),
58301                     constrainPosition = me.constrainPosition;
58302
58303             me.anchorEl.removeCls(me.anchorCls);
58304
58305             if (me.targetCounter < 2 && constrainPosition) {
58306                 if (axy[0] < scrollX) {
58307                     if (me.anchorToTarget) {
58308                         me.defaultAlign = 'l-r';
58309                         if (me.mouseOffset) {
58310                             me.mouseOffset[0] *= -1;
58311                         }
58312                     }
58313                     me.anchor = 'left';
58314                     return me.getTargetXY();
58315                 }
58316                 if (axy[0] + sz.width > dw) {
58317                     if (me.anchorToTarget) {
58318                         me.defaultAlign = 'r-l';
58319                         if (me.mouseOffset) {
58320                             me.mouseOffset[0] *= -1;
58321                         }
58322                     }
58323                     me.anchor = 'right';
58324                     return me.getTargetXY();
58325                 }
58326                 if (axy[1] < scrollY) {
58327                     if (me.anchorToTarget) {
58328                         me.defaultAlign = 't-b';
58329                         if (me.mouseOffset) {
58330                             me.mouseOffset[1] *= -1;
58331                         }
58332                     }
58333                     me.anchor = 'top';
58334                     return me.getTargetXY();
58335                 }
58336                 if (axy[1] + sz.height > dh) {
58337                     if (me.anchorToTarget) {
58338                         me.defaultAlign = 'b-t';
58339                         if (me.mouseOffset) {
58340                             me.mouseOffset[1] *= -1;
58341                         }
58342                     }
58343                     me.anchor = 'bottom';
58344                     return me.getTargetXY();
58345                 }
58346             }
58347
58348             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58349             me.anchorEl.addCls(me.anchorCls);
58350             me.targetCounter = 0;
58351             return axy;
58352         } else {
58353             mouseOffset = me.getMouseOffset();
58354             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
58355         }
58356     },
58357
58358     getMouseOffset: function() {
58359         var me = this,
58360         offset = me.anchor ? [0, 0] : [15, 18];
58361         if (me.mouseOffset) {
58362             offset[0] += me.mouseOffset[0];
58363             offset[1] += me.mouseOffset[1];
58364         }
58365         return offset;
58366     },
58367
58368     // private
58369     getAnchorPosition: function() {
58370         var me = this,
58371             m;
58372         if (me.anchor) {
58373             me.tipAnchor = me.anchor.charAt(0);
58374         } else {
58375             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
58376             me.tipAnchor = m[1].charAt(0);
58377         }
58378
58379         switch (me.tipAnchor) {
58380         case 't':
58381             return 'top';
58382         case 'b':
58383             return 'bottom';
58384         case 'r':
58385             return 'right';
58386         }
58387         return 'left';
58388     },
58389
58390     // private
58391     getAnchorAlign: function() {
58392         switch (this.anchor) {
58393         case 'top':
58394             return 'tl-bl';
58395         case 'left':
58396             return 'tl-tr';
58397         case 'right':
58398             return 'tr-tl';
58399         default:
58400             return 'bl-tl';
58401         }
58402     },
58403
58404     // private
58405     getOffsets: function() {
58406         var me = this,
58407             mouseOffset,
58408             offsets,
58409             ap = me.getAnchorPosition().charAt(0);
58410         if (me.anchorToTarget && !me.trackMouse) {
58411             switch (ap) {
58412             case 't':
58413                 offsets = [0, 9];
58414                 break;
58415             case 'b':
58416                 offsets = [0, -13];
58417                 break;
58418             case 'r':
58419                 offsets = [ - 13, 0];
58420                 break;
58421             default:
58422                 offsets = [9, 0];
58423                 break;
58424             }
58425         } else {
58426             switch (ap) {
58427             case 't':
58428                 offsets = [ - 15 - me.anchorOffset, 30];
58429                 break;
58430             case 'b':
58431                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
58432                 break;
58433             case 'r':
58434                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
58435                 break;
58436             default:
58437                 offsets = [25, -13 - me.anchorOffset];
58438                 break;
58439             }
58440         }
58441         mouseOffset = me.getMouseOffset();
58442         offsets[0] += mouseOffset[0];
58443         offsets[1] += mouseOffset[1];
58444
58445         return offsets;
58446     },
58447
58448     // private
58449     onTargetOver: function(e) {
58450         var me = this,
58451             t;
58452
58453         if (me.disabled || e.within(me.target.dom, true)) {
58454             return;
58455         }
58456         t = e.getTarget(me.delegate);
58457         if (t) {
58458             me.triggerElement = t;
58459             me.clearTimer('hide');
58460             me.targetXY = e.getXY();
58461             me.delayShow();
58462         }
58463     },
58464
58465     // private
58466     delayShow: function() {
58467         var me = this;
58468         if (me.hidden && !me.showTimer) {
58469             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
58470                 me.show();
58471             } else {
58472                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
58473             }
58474         }
58475         else if (!me.hidden && me.autoHide !== false) {
58476             me.show();
58477         }
58478     },
58479
58480     // private
58481     onTargetOut: function(e) {
58482         var me = this;
58483         if (me.disabled || e.within(me.target.dom, true)) {
58484             return;
58485         }
58486         me.clearTimer('show');
58487         if (me.autoHide !== false) {
58488             me.delayHide();
58489         }
58490     },
58491
58492     // private
58493     delayHide: function() {
58494         var me = this;
58495         if (!me.hidden && !me.hideTimer) {
58496             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
58497         }
58498     },
58499
58500     /**
58501      * Hides this tooltip if visible.
58502      */
58503     hide: function() {
58504         var me = this;
58505         me.clearTimer('dismiss');
58506         me.lastActive = new Date();
58507         if (me.anchorEl) {
58508             me.anchorEl.hide();
58509         }
58510         me.callParent(arguments);
58511         delete me.triggerElement;
58512     },
58513
58514     /**
58515      * Shows this tooltip at the current event target XY position.
58516      */
58517     show: function() {
58518         var me = this;
58519
58520         // Show this Component first, so that sizing can be calculated
58521         // pre-show it off screen so that the el will have dimensions
58522         this.callParent();
58523         if (this.hidden === false) {
58524             me.setPagePosition(-10000, -10000);
58525
58526             if (me.anchor) {
58527                 me.anchor = me.origAnchor;
58528             }
58529             me.showAt(me.getTargetXY());
58530
58531             if (me.anchor) {
58532                 me.syncAnchor();
58533                 me.anchorEl.show();
58534             } else {
58535                 me.anchorEl.hide();
58536             }
58537         }
58538     },
58539
58540     // inherit docs
58541     showAt: function(xy) {
58542         var me = this;
58543         me.lastActive = new Date();
58544         me.clearTimers();
58545
58546         // Only call if this is hidden. May have been called from show above.
58547         if (!me.isVisible()) {
58548             this.callParent(arguments);
58549         }
58550
58551         // Show may have been vetoed.
58552         if (me.isVisible()) {
58553             me.setPagePosition(xy[0], xy[1]);
58554             if (me.constrainPosition || me.constrain) {
58555                 me.doConstrain();
58556             }
58557             me.toFront(true);
58558         }
58559
58560         if (me.dismissDelay && me.autoHide !== false) {
58561             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
58562         }
58563         if (me.anchor) {
58564             me.syncAnchor();
58565             if (!me.anchorEl.isVisible()) {
58566                 me.anchorEl.show();
58567             }
58568         } else {
58569             me.anchorEl.hide();
58570         }
58571     },
58572
58573     // private
58574     syncAnchor: function() {
58575         var me = this,
58576             anchorPos,
58577             targetPos,
58578             offset;
58579         switch (me.tipAnchor.charAt(0)) {
58580         case 't':
58581             anchorPos = 'b';
58582             targetPos = 'tl';
58583             offset = [20 + me.anchorOffset, 1];
58584             break;
58585         case 'r':
58586             anchorPos = 'l';
58587             targetPos = 'tr';
58588             offset = [ - 1, 12 + me.anchorOffset];
58589             break;
58590         case 'b':
58591             anchorPos = 't';
58592             targetPos = 'bl';
58593             offset = [20 + me.anchorOffset, -1];
58594             break;
58595         default:
58596             anchorPos = 'r';
58597             targetPos = 'tl';
58598             offset = [1, 12 + me.anchorOffset];
58599             break;
58600         }
58601         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
58602     },
58603
58604     // private
58605     setPagePosition: function(x, y) {
58606         var me = this;
58607         me.callParent(arguments);
58608         if (me.anchor) {
58609             me.syncAnchor();
58610         }
58611     },
58612
58613     // private
58614     clearTimer: function(name) {
58615         name = name + 'Timer';
58616         clearTimeout(this[name]);
58617         delete this[name];
58618     },
58619
58620     // private
58621     clearTimers: function() {
58622         var me = this;
58623         me.clearTimer('show');
58624         me.clearTimer('dismiss');
58625         me.clearTimer('hide');
58626     },
58627
58628     // private
58629     onShow: function() {
58630         var me = this;
58631         me.callParent();
58632         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58633     },
58634
58635     // private
58636     onHide: function() {
58637         var me = this;
58638         me.callParent();
58639         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58640     },
58641
58642     // private
58643     onDocMouseDown: function(e) {
58644         var me = this;
58645         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
58646             me.disable();
58647             Ext.defer(me.doEnable, 100, me);
58648         }
58649     },
58650
58651     // private
58652     doEnable: function() {
58653         if (!this.isDestroyed) {
58654             this.enable();
58655         }
58656     },
58657
58658     // private
58659     onDisable: function() {
58660         this.callParent();
58661         this.clearTimers();
58662         this.hide();
58663     },
58664
58665     beforeDestroy: function() {
58666         var me = this;
58667         me.clearTimers();
58668         Ext.destroy(me.anchorEl);
58669         delete me.anchorEl;
58670         delete me.target;
58671         delete me.anchorTarget;
58672         delete me.triggerElement;
58673         me.callParent();
58674     },
58675
58676     // private
58677     onDestroy: function() {
58678         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
58679         this.callParent();
58680     }
58681 });
58682
58683 /**
58684  * @class Ext.tip.QuickTip
58685  * @extends Ext.tip.ToolTip
58686  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
58687  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
58688  * @xtype quicktip
58689  */
58690 Ext.define('Ext.tip.QuickTip', {
58691     extend: 'Ext.tip.ToolTip',
58692     alternateClassName: 'Ext.QuickTip',
58693     /**
58694      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
58695      */
58696     /**
58697      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
58698      */
58699     interceptTitles : false,
58700
58701     // Force creation of header Component
58702     title: '&#160;',
58703
58704     // private
58705     tagConfig : {
58706         namespace : "data-",
58707         attribute : "qtip",
58708         width : "qwidth",
58709         target : "target",
58710         title : "qtitle",
58711         hide : "hide",
58712         cls : "qclass",
58713         align : "qalign",
58714         anchor : "anchor"
58715     },
58716
58717     // private
58718     initComponent : function(){
58719         var me = this;
58720
58721         me.target = me.target || Ext.getDoc();
58722         me.targets = me.targets || {};
58723         me.callParent();
58724     },
58725
58726     /**
58727      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
58728      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
58729      * <div class="mdetail-params"><ul>
58730      * <li>autoHide</li>
58731      * <li>cls</li>
58732      * <li>dismissDelay (overrides the singleton value)</li>
58733      * <li>target (required)</li>
58734      * <li>text (required)</li>
58735      * <li>title</li>
58736      * <li>width</li></ul></div>
58737      * @param {Object} config The config object
58738      */
58739     register : function(config){
58740         var configs = Ext.isArray(config) ? config : arguments,
58741             i = 0,
58742             len = configs.length,
58743             target, j, targetLen;
58744
58745         for (; i < len; i++) {
58746             config = configs[i];
58747             target = config.target;
58748             if (target) {
58749                 if (Ext.isArray(target)) {
58750                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
58751                         this.targets[Ext.id(target[j])] = config;
58752                     }
58753                 } else{
58754                     this.targets[Ext.id(target)] = config;
58755                 }
58756             }
58757         }
58758     },
58759
58760     /**
58761      * Removes this quick tip from its element and destroys it.
58762      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
58763      */
58764     unregister : function(el){
58765         delete this.targets[Ext.id(el)];
58766     },
58767
58768     /**
58769      * Hides a visible tip or cancels an impending show for a particular element.
58770      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
58771      */
58772     cancelShow: function(el){
58773         var me = this,
58774             activeTarget = me.activeTarget;
58775
58776         el = Ext.get(el).dom;
58777         if (me.isVisible()) {
58778             if (activeTarget && activeTarget.el == el) {
58779                 me.hide();
58780             }
58781         } else if (activeTarget && activeTarget.el == el) {
58782             me.clearTimer('show');
58783         }
58784     },
58785
58786     /**
58787      * @private
58788      * Reads the tip text from the closest node to the event target which contains the attribute we
58789      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
58790      * which the text was read.
58791      */
58792     getTipCfg: function(e) {
58793         var t = e.getTarget(),
58794             titleText = t.title,
58795             cfg;
58796
58797         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
58798             t.qtip = titleText;
58799             t.removeAttribute("title");
58800             e.preventDefault();
58801             return {
58802                 text: titleText
58803             };
58804         }
58805         else {
58806             cfg = this.tagConfig;
58807             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
58808             if (t) {
58809                 return {
58810                     target: t,
58811                     text: t.getAttribute(cfg.namespace + cfg.attribute)
58812                 };
58813             }
58814         }
58815     },
58816
58817     // private
58818     onTargetOver : function(e){
58819         var me = this,
58820             target = e.getTarget(),
58821             elTarget,
58822             cfg,
58823             ns,
58824             tipConfig,
58825             autoHide;
58826
58827         if (me.disabled) {
58828             return;
58829         }
58830
58831         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
58832         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
58833         // that smashed Ext.EventObject.
58834         me.targetXY = e.getXY();
58835
58836         if(!target || target.nodeType !== 1 || target == document || target == document.body){
58837             return;
58838         }
58839
58840         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
58841             me.clearTimer('hide');
58842             me.show();
58843             return;
58844         }
58845
58846         if (target) {
58847             Ext.Object.each(me.targets, function(key, value) {
58848                 var targetEl = Ext.fly(value.target);
58849                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
58850                     elTarget = targetEl.dom;
58851                     return false;
58852                 }
58853             });
58854             if (elTarget) {
58855                 me.activeTarget = me.targets[elTarget.id];
58856                 me.activeTarget.el = target;
58857                 me.anchor = me.activeTarget.anchor;
58858                 if (me.anchor) {
58859                     me.anchorTarget = target;
58860                 }
58861                 me.delayShow();
58862                 return;
58863             }
58864         }
58865
58866         elTarget = Ext.get(target);
58867         cfg = me.tagConfig;
58868         ns = cfg.namespace;
58869         tipConfig = me.getTipCfg(e);
58870
58871         if (tipConfig) {
58872
58873             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
58874             // Change our target element to match that from which the tip text attribute was read.
58875             if (tipConfig.target) {
58876                 target = tipConfig.target;
58877                 elTarget = Ext.get(target);
58878             }
58879             autoHide = elTarget.getAttribute(ns + cfg.hide);
58880
58881             me.activeTarget = {
58882                 el: target,
58883                 text: tipConfig.text,
58884                 width: +elTarget.getAttribute(ns + cfg.width) || null,
58885                 autoHide: autoHide != "user" && autoHide !== 'false',
58886                 title: elTarget.getAttribute(ns + cfg.title),
58887                 cls: elTarget.getAttribute(ns + cfg.cls),
58888                 align: elTarget.getAttribute(ns + cfg.align)
58889
58890             };
58891             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
58892             if (me.anchor) {
58893                 me.anchorTarget = target;
58894             }
58895             me.delayShow();
58896         }
58897     },
58898
58899     // private
58900     onTargetOut : function(e){
58901         var me = this;
58902
58903         // If moving within the current target, and it does not have a new tip, ignore the mouseout
58904         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
58905             return;
58906         }
58907
58908         me.clearTimer('show');
58909         if (me.autoHide !== false) {
58910             me.delayHide();
58911         }
58912     },
58913
58914     // inherit docs
58915     showAt : function(xy){
58916         var me = this,
58917             target = me.activeTarget;
58918
58919         if (target) {
58920             if (!me.rendered) {
58921                 me.render(Ext.getBody());
58922                 me.activeTarget = target;
58923             }
58924             if (target.title) {
58925                 me.setTitle(target.title || '');
58926                 me.header.show();
58927             } else {
58928                 me.header.hide();
58929             }
58930             me.body.update(target.text);
58931             me.autoHide = target.autoHide;
58932             me.dismissDelay = target.dismissDelay || me.dismissDelay;
58933             if (me.lastCls) {
58934                 me.el.removeCls(me.lastCls);
58935                 delete me.lastCls;
58936             }
58937             if (target.cls) {
58938                 me.el.addCls(target.cls);
58939                 me.lastCls = target.cls;
58940             }
58941
58942             me.setWidth(target.width);
58943
58944             if (me.anchor) {
58945                 me.constrainPosition = false;
58946             } else if (target.align) { // TODO: this doesn't seem to work consistently
58947                 xy = me.el.getAlignToXY(target.el, target.align);
58948                 me.constrainPosition = false;
58949             }else{
58950                 me.constrainPosition = true;
58951             }
58952         }
58953         me.callParent([xy]);
58954     },
58955
58956     // inherit docs
58957     hide: function(){
58958         delete this.activeTarget;
58959         this.callParent();
58960     }
58961 });
58962
58963 /**
58964  * @class Ext.tip.QuickTipManager
58965  *
58966  * Provides attractive and customizable tooltips for any element. The QuickTips
58967  * singleton is used to configure and manage tooltips globally for multiple elements
58968  * in a generic manner.  To create individual tooltips with maximum customizability,
58969  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
58970  *
58971  * Quicktips can be configured via tag attributes directly in markup, or by
58972  * registering quick tips programmatically via the {@link #register} method.
58973  *
58974  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
58975  * {@link #getQuickTip}, and supports all the methods, and all the all the
58976  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
58977  * tooltips shown by the singleton.
58978  *
58979  * Below is the summary of the configuration properties which can be used.
58980  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
58981  *
58982  * ## QuickTips singleton configs (all are optional)
58983  *
58984  *  - `dismissDelay`
58985  *  - `hideDelay`
58986  *  - `maxWidth`
58987  *  - `minWidth`
58988  *  - `showDelay`
58989  *  - `trackMouse`
58990  *
58991  * ## Target element configs (optional unless otherwise noted)
58992  *
58993  *  - `autoHide`
58994  *  - `cls`
58995  *  - `dismissDelay` (overrides singleton value)
58996  *  - `target` (required)
58997  *  - `text` (required)
58998  *  - `title`
58999  *  - `width`
59000  *
59001  * Here is an example showing how some of these config options could be used:
59002  *
59003  *     @example
59004  *     // Init the singleton.  Any tag-based quick tips will start working.
59005  *     Ext.tip.QuickTipManager.init();
59006  *
59007  *     // Apply a set of config properties to the singleton
59008  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
59009  *         maxWidth: 200,
59010  *         minWidth: 100,
59011  *         showDelay: 50      // Show 50ms after entering target
59012  *     });
59013  *
59014  *     // Create a small panel to add a quick tip to
59015  *     Ext.create('Ext.container.Container', {
59016  *         id: 'quickTipContainer',
59017  *         width: 200,
59018  *         height: 150,
59019  *         style: {
59020  *             backgroundColor:'#000000'
59021  *         },
59022  *         renderTo: Ext.getBody()
59023  *     });
59024  *
59025  *
59026  *     // Manually register a quick tip for a specific element
59027  *     Ext.tip.QuickTipManager.register({
59028  *         target: 'quickTipContainer',
59029  *         title: 'My Tooltip',
59030  *         text: 'This tooltip was added in code',
59031  *         width: 100,
59032  *         dismissDelay: 10000 // Hide after 10 seconds hover
59033  *     });
59034  *
59035  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
59036  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
59037  * of supported attributes (optional unless otherwise noted):
59038  *
59039  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
59040  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
59041  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
59042  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
59043  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
59044  *
59045  * Here is an example of configuring an HTML element to display a tooltip from markup:
59046  *
59047  *     // Add a quick tip to an HTML button
59048  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
59049  *          data-qtip="This is a quick tip from markup!"></input>
59050  *
59051  * @singleton
59052  */
59053 Ext.define('Ext.tip.QuickTipManager', function() {
59054     var tip,
59055         disabled = false;
59056
59057     return {
59058         requires: ['Ext.tip.QuickTip'],
59059         singleton: true,
59060         alternateClassName: 'Ext.QuickTips',
59061
59062         /**
59063          * Initialize the global QuickTips instance and prepare any quick tips.
59064          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
59065          * preload images. (Defaults to true)
59066          * @param {Object} config (optional) config object for the created QuickTip. By
59067          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
59068          * be changed by supplying an xtype property or a className property in this object.
59069          * All other properties on this object are configuration for the created component.
59070          */
59071         init : function (autoRender, config) {
59072             if (!tip) {
59073                 if (!Ext.isReady) {
59074                     Ext.onReady(function(){
59075                         Ext.tip.QuickTipManager.init(autoRender);
59076                     });
59077                     return;
59078                 }
59079
59080                 var tipConfig = Ext.apply({ disabled: disabled }, config),
59081                     className = tipConfig.className,
59082                     xtype = tipConfig.xtype;
59083
59084                 if (className) {
59085                     delete tipConfig.className;
59086                 } else if (xtype) {
59087                     className = 'widget.' + xtype;
59088                     delete tipConfig.xtype;
59089                 }
59090
59091                 if (autoRender !== false) {
59092                     tipConfig.renderTo = document.body;
59093
59094                 }
59095
59096                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
59097             }
59098         },
59099
59100         /**
59101          * Destroy the QuickTips instance.
59102          */
59103         destroy: function() {
59104             if (tip) {
59105                 var undef;
59106                 tip.destroy();
59107                 tip = undef;
59108             }
59109         },
59110
59111         // Protected method called by the dd classes
59112         ddDisable : function(){
59113             // don't disable it if we don't need to
59114             if(tip && !disabled){
59115                 tip.disable();
59116             }
59117         },
59118
59119         // Protected method called by the dd classes
59120         ddEnable : function(){
59121             // only enable it if it hasn't been disabled
59122             if(tip && !disabled){
59123                 tip.enable();
59124             }
59125         },
59126
59127         /**
59128          * Enable quick tips globally.
59129          */
59130         enable : function(){
59131             if(tip){
59132                 tip.enable();
59133             }
59134             disabled = false;
59135         },
59136
59137         /**
59138          * Disable quick tips globally.
59139          */
59140         disable : function(){
59141             if(tip){
59142                 tip.disable();
59143             }
59144             disabled = true;
59145         },
59146
59147         /**
59148          * Returns true if quick tips are enabled, else false.
59149          * @return {Boolean}
59150          */
59151         isEnabled : function(){
59152             return tip !== undefined && !tip.disabled;
59153         },
59154
59155         /**
59156          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
59157          * @return {Ext.tip.QuickTip}
59158          */
59159         getQuickTip : function(){
59160             return tip;
59161         },
59162
59163         /**
59164          * Configures a new quick tip instance and assigns it to a target element.  See
59165          * {@link Ext.tip.QuickTip#register} for details.
59166          * @param {Object} config The config object
59167          */
59168         register : function(){
59169             tip.register.apply(tip, arguments);
59170         },
59171
59172         /**
59173          * Removes any registered quick tip from the target element and destroys it.
59174          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59175          */
59176         unregister : function(){
59177             tip.unregister.apply(tip, arguments);
59178         },
59179
59180         /**
59181          * Alias of {@link #register}.
59182          * @param {Object} config The config object
59183          */
59184         tips : function(){
59185             tip.register.apply(tip, arguments);
59186         }
59187     };
59188 }());
59189 /**
59190  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
59191  * A typical Ext.app.Application might look like this:
59192  *
59193  *     Ext.application({
59194  *         name: 'MyApp',
59195  *         launch: function() {
59196  *             Ext.create('Ext.container.Viewport', {
59197  *                 items: {
59198  *                     html: 'My App'
59199  *                 }
59200  *             });
59201  *         }
59202  *     });
59203  *
59204  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
59205  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
59206  * of colliding global variables.
59207  *
59208  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
59209  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
59210  * the example above.
59211  *
59212  * # Telling Application about the rest of the app
59213  *
59214  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
59215  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
59216  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
59217  * Here's how we'd tell our Application about all these things:
59218  *
59219  *     Ext.application({
59220  *         name: 'Blog',
59221  *         models: ['Post', 'Comment'],
59222  *         controllers: ['Posts', 'Comments'],
59223  *
59224  *         launch: function() {
59225  *             ...
59226  *         }
59227  *     });
59228  *
59229  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
59230  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
59231  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
59232  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
59233  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
59234  * automatically loaded. Here's how our Posts controller like be defined:
59235  *
59236  *     Ext.define('MyApp.controller.Posts', {
59237  *         extend: 'Ext.app.Controller',
59238  *         views: ['posts.List', 'posts.Edit'],
59239  *
59240  *         //the rest of the Controller here
59241  *     });
59242  *
59243  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
59244  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
59245  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
59246  * application using the Ext JS 4 SDK Tools.
59247  *
59248  * For more information about writing Ext JS 4 applications, please see the
59249  * [application architecture guide][mvc].
59250  *
59251  * [mvc]: #!/guide/application_architecture
59252  *
59253  * @docauthor Ed Spencer
59254  */
59255 Ext.define('Ext.app.Application', {
59256     extend: 'Ext.app.Controller',
59257
59258     requires: [
59259         'Ext.ModelManager',
59260         'Ext.data.Model',
59261         'Ext.data.StoreManager',
59262         'Ext.tip.QuickTipManager',
59263         'Ext.ComponentManager',
59264         'Ext.app.EventBus'
59265     ],
59266
59267     /**
59268      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
59269      * models and stores. Don't use spaces or special characters in the name.
59270      */
59271
59272     /**
59273      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
59274      * instance.
59275      */
59276     scope: undefined,
59277
59278     /**
59279      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
59280      */
59281     enableQuickTips: true,
59282
59283     /**
59284      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
59285      */
59286
59287     /**
59288      * @cfg {String} appFolder The path to the directory which contains all application's classes.
59289      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
59290      */
59291     appFolder: 'app',
59292
59293     /**
59294      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
59295      * before firing the launch function.
59296      */
59297     autoCreateViewport: false,
59298
59299     /**
59300      * Creates new Application.
59301      * @param {Object} [config] Config object.
59302      */
59303     constructor: function(config) {
59304         config = config || {};
59305         Ext.apply(this, config);
59306
59307         var requires = config.requires || [];
59308
59309         Ext.Loader.setPath(this.name, this.appFolder);
59310
59311         if (this.paths) {
59312             Ext.Object.each(this.paths, function(key, value) {
59313                 Ext.Loader.setPath(key, value);
59314             });
59315         }
59316
59317         this.callParent(arguments);
59318
59319         this.eventbus = Ext.create('Ext.app.EventBus');
59320
59321         var controllers = Ext.Array.from(this.controllers),
59322             ln = controllers && controllers.length,
59323             i, controller;
59324
59325         this.controllers = Ext.create('Ext.util.MixedCollection');
59326
59327         if (this.autoCreateViewport) {
59328             requires.push(this.getModuleClassName('Viewport', 'view'));
59329         }
59330
59331         for (i = 0; i < ln; i++) {
59332             requires.push(this.getModuleClassName(controllers[i], 'controller'));
59333         }
59334
59335         Ext.require(requires);
59336
59337         Ext.onReady(function() {
59338             for (i = 0; i < ln; i++) {
59339                 controller = this.getController(controllers[i]);
59340                 controller.init(this);
59341             }
59342
59343             this.onBeforeLaunch.call(this);
59344         }, this);
59345     },
59346
59347     control: function(selectors, listeners, controller) {
59348         this.eventbus.control(selectors, listeners, controller);
59349     },
59350
59351     /**
59352      * Called automatically when the page has completely loaded. This is an empty function that should be
59353      * overridden by each application that needs to take action on page load
59354      * @property launch
59355      * @type Function
59356      * @param {String} profile The detected {@link #profiles application profile}
59357      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
59358      * action immediately after running the launch function. Return false to prevent this behavior.
59359      */
59360     launch: Ext.emptyFn,
59361
59362     /**
59363      * @private
59364      */
59365     onBeforeLaunch: function() {
59366         if (this.enableQuickTips) {
59367             Ext.tip.QuickTipManager.init();
59368         }
59369
59370         if (this.autoCreateViewport) {
59371             this.getView('Viewport').create();
59372         }
59373
59374         this.launch.call(this.scope || this);
59375         this.launched = true;
59376         this.fireEvent('launch', this);
59377
59378         this.controllers.each(function(controller) {
59379             controller.onLaunch(this);
59380         }, this);
59381     },
59382
59383     getModuleClassName: function(name, type) {
59384         var namespace = Ext.Loader.getPrefix(name);
59385
59386         if (namespace.length > 0 && namespace !== name) {
59387             return name;
59388         }
59389
59390         return this.name + '.' + type + '.' + name;
59391     },
59392
59393     getController: function(name) {
59394         var controller = this.controllers.get(name);
59395
59396         if (!controller) {
59397             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
59398                 application: this,
59399                 id: name
59400             });
59401
59402             this.controllers.add(controller);
59403         }
59404
59405         return controller;
59406     },
59407
59408     getStore: function(name) {
59409         var store = Ext.StoreManager.get(name);
59410
59411         if (!store) {
59412             store = Ext.create(this.getModuleClassName(name, 'store'), {
59413                 storeId: name
59414             });
59415         }
59416
59417         return store;
59418     },
59419
59420     getModel: function(model) {
59421         model = this.getModuleClassName(model, 'model');
59422
59423         return Ext.ModelManager.getModel(model);
59424     },
59425
59426     getView: function(view) {
59427         view = this.getModuleClassName(view, 'view');
59428
59429         return Ext.ClassManager.get(view);
59430     }
59431 });
59432
59433 /**
59434  * @class Ext.chart.Callout
59435  * A mixin providing callout functionality for Ext.chart.series.Series.
59436  */
59437 Ext.define('Ext.chart.Callout', {
59438
59439     /* Begin Definitions */
59440
59441     /* End Definitions */
59442
59443     constructor: function(config) {
59444         if (config.callouts) {
59445             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
59446                 color: "#000",
59447                 font: "11px Helvetica, sans-serif"
59448             });
59449             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
59450             this.calloutsArray = [];
59451         }
59452     },
59453
59454     renderCallouts: function() {
59455         if (!this.callouts) {
59456             return;
59457         }
59458
59459         var me = this,
59460             items = me.items,
59461             animate = me.chart.animate,
59462             config = me.callouts,
59463             styles = config.styles,
59464             group = me.calloutsArray,
59465             store = me.chart.store,
59466             len = store.getCount(),
59467             ratio = items.length / len,
59468             previouslyPlacedCallouts = [],
59469             i,
59470             count,
59471             j,
59472             p;
59473             
59474         for (i = 0, count = 0; i < len; i++) {
59475             for (j = 0; j < ratio; j++) {
59476                 var item = items[count],
59477                     label = group[count],
59478                     storeItem = store.getAt(i),
59479                     display;
59480                 
59481                 display = config.filter(storeItem);
59482                 
59483                 if (!display && !label) {
59484                     count++;
59485                     continue;               
59486                 }
59487                 
59488                 if (!label) {
59489                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
59490                 }
59491                 for (p in label) {
59492                     if (label[p] && label[p].setAttributes) {
59493                         label[p].setAttributes(styles, true);
59494                     }
59495                 }
59496                 if (!display) {
59497                     for (p in label) {
59498                         if (label[p]) {
59499                             if (label[p].setAttributes) {
59500                                 label[p].setAttributes({
59501                                     hidden: true
59502                                 }, true);
59503                             } else if(label[p].setVisible) {
59504                                 label[p].setVisible(false);
59505                             }
59506                         }
59507                     }
59508                 }
59509                 config.renderer(label, storeItem);
59510                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
59511                                   j, count, previouslyPlacedCallouts);
59512                 previouslyPlacedCallouts.push(label);
59513                 count++;
59514             }
59515         }
59516         this.hideCallouts(count);
59517     },
59518
59519     onCreateCallout: function(storeItem, item, i, display) {
59520         var me = this,
59521             group = me.calloutsGroup,
59522             config = me.callouts,
59523             styles = config.styles,
59524             width = styles.width,
59525             height = styles.height,
59526             chart = me.chart,
59527             surface = chart.surface,
59528             calloutObj = {
59529                 //label: false,
59530                 //box: false,
59531                 lines: false
59532             };
59533
59534         calloutObj.lines = surface.add(Ext.apply({},
59535         {
59536             type: 'path',
59537             path: 'M0,0',
59538             stroke: me.getLegendColor() || '#555'
59539         },
59540         styles));
59541
59542         if (config.items) {
59543             calloutObj.panel = Ext.create('widget.panel', {
59544                 style: "position: absolute;",    
59545                 width: width,
59546                 height: height,
59547                 items: config.items,
59548                 renderTo: chart.el
59549             });
59550         }
59551
59552         return calloutObj;
59553     },
59554
59555     hideCallouts: function(index) {
59556         var calloutsArray = this.calloutsArray,
59557             len = calloutsArray.length,
59558             co,
59559             p;
59560         while (len-->index) {
59561             co = calloutsArray[len];
59562             for (p in co) {
59563                 if (co[p]) {
59564                     co[p].hide(true);
59565                 }
59566             }
59567         }
59568     }
59569 });
59570
59571 /**
59572  * @class Ext.draw.CompositeSprite
59573  * @extends Ext.util.MixedCollection
59574  *
59575  * A composite Sprite handles a group of sprites with common methods to a sprite
59576  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
59577  * added to the group.
59578  *
59579  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
59580  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
59581  *
59582  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
59583  * rendered:
59584  *
59585  *     var group = Ext.create('Ext.draw.CompositeSprite', {
59586  *         surface: drawComponent.surface
59587  *     });
59588  *                  
59589  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
59590  *  
59591  *     group.add(sprite1);
59592  *     group.add(sprite2);
59593  *     group.add(sprite3);
59594  *                  
59595  * And then apply common Sprite methods to them:
59596  *  
59597  *     group.setAttributes({
59598  *         fill: '#f00'
59599  *     }, true);
59600  */
59601 Ext.define('Ext.draw.CompositeSprite', {
59602
59603     /* Begin Definitions */
59604
59605     extend: 'Ext.util.MixedCollection',
59606     mixins: {
59607         animate: 'Ext.util.Animate'
59608     },
59609
59610     /* End Definitions */
59611     isCompositeSprite: true,
59612     constructor: function(config) {
59613         var me = this;
59614         
59615         config = config || {};
59616         Ext.apply(me, config);
59617
59618         me.addEvents(
59619             'mousedown',
59620             'mouseup',
59621             'mouseover',
59622             'mouseout',
59623             'click'
59624         );
59625         me.id = Ext.id(null, 'ext-sprite-group-');
59626         me.callParent();
59627     },
59628
59629     // @private
59630     onClick: function(e) {
59631         this.fireEvent('click', e);
59632     },
59633
59634     // @private
59635     onMouseUp: function(e) {
59636         this.fireEvent('mouseup', e);
59637     },
59638
59639     // @private
59640     onMouseDown: function(e) {
59641         this.fireEvent('mousedown', e);
59642     },
59643
59644     // @private
59645     onMouseOver: function(e) {
59646         this.fireEvent('mouseover', e);
59647     },
59648
59649     // @private
59650     onMouseOut: function(e) {
59651         this.fireEvent('mouseout', e);
59652     },
59653
59654     attachEvents: function(o) {
59655         var me = this;
59656         
59657         o.on({
59658             scope: me,
59659             mousedown: me.onMouseDown,
59660             mouseup: me.onMouseUp,
59661             mouseover: me.onMouseOver,
59662             mouseout: me.onMouseOut,
59663             click: me.onClick
59664         });
59665     },
59666
59667     // Inherit docs from MixedCollection
59668     add: function(key, o) {
59669         var result = this.callParent(arguments);
59670         this.attachEvents(result);
59671         return result;
59672     },
59673
59674     insert: function(index, key, o) {
59675         return this.callParent(arguments);
59676     },
59677
59678     // Inherit docs from MixedCollection
59679     remove: function(o) {
59680         var me = this;
59681         
59682         o.un({
59683             scope: me,
59684             mousedown: me.onMouseDown,
59685             mouseup: me.onMouseUp,
59686             mouseover: me.onMouseOver,
59687             mouseout: me.onMouseOut,
59688             click: me.onClick
59689         });
59690         return me.callParent(arguments);
59691     },
59692     
59693     /**
59694      * Returns the group bounding box.
59695      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
59696      * @return {Object} an object with x, y, width, and height properties.
59697      */
59698     getBBox: function() {
59699         var i = 0,
59700             sprite,
59701             bb,
59702             items = this.items,
59703             len = this.length,
59704             infinity = Infinity,
59705             minX = infinity,
59706             maxHeight = -infinity,
59707             minY = infinity,
59708             maxWidth = -infinity,
59709             maxWidthBBox, maxHeightBBox;
59710         
59711         for (; i < len; i++) {
59712             sprite = items[i];
59713             if (sprite.el) {
59714                 bb = sprite.getBBox();
59715                 minX = Math.min(minX, bb.x);
59716                 minY = Math.min(minY, bb.y);
59717                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
59718                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
59719             }
59720         }
59721         
59722         return {
59723             x: minX,
59724             y: minY,
59725             height: maxHeight - minY,
59726             width: maxWidth - minX
59727         };
59728     },
59729
59730     /**
59731      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
59732      * provides a description of the attributes that can be set with this method.
59733      * @param {Object} attrs Attributes to be changed on the sprite.
59734      * @param {Boolean} redraw Flag to immediatly draw the change.
59735      * @return {Ext.draw.CompositeSprite} this
59736      */
59737     setAttributes: function(attrs, redraw) {
59738         var i = 0,
59739             items = this.items,
59740             len = this.length;
59741             
59742         for (; i < len; i++) {
59743             items[i].setAttributes(attrs, redraw);
59744         }
59745         return this;
59746     },
59747
59748     /**
59749      * Hides all sprites. If the first parameter of the method is true
59750      * then a redraw will be forced for each sprite.
59751      * @param {Boolean} redraw Flag to immediatly draw the change.
59752      * @return {Ext.draw.CompositeSprite} this
59753      */
59754     hide: function(redraw) {
59755         var i = 0,
59756             items = this.items,
59757             len = this.length;
59758             
59759         for (; i < len; i++) {
59760             items[i].hide(redraw);
59761         }
59762         return this;
59763     },
59764
59765     /**
59766      * Shows all sprites. If the first parameter of the method is true
59767      * then a redraw will be forced for each sprite.
59768      * @param {Boolean} redraw Flag to immediatly draw the change.
59769      * @return {Ext.draw.CompositeSprite} this
59770      */
59771     show: function(redraw) {
59772         var i = 0,
59773             items = this.items,
59774             len = this.length;
59775             
59776         for (; i < len; i++) {
59777             items[i].show(redraw);
59778         }
59779         return this;
59780     },
59781
59782     redraw: function() {
59783         var me = this,
59784             i = 0,
59785             items = me.items,
59786             surface = me.getSurface(),
59787             len = me.length;
59788         
59789         if (surface) {
59790             for (; i < len; i++) {
59791                 surface.renderItem(items[i]);
59792             }
59793         }
59794         return me;
59795     },
59796
59797     setStyle: function(obj) {
59798         var i = 0,
59799             items = this.items,
59800             len = this.length,
59801             item, el;
59802             
59803         for (; i < len; i++) {
59804             item = items[i];
59805             el = item.el;
59806             if (el) {
59807                 el.setStyle(obj);
59808             }
59809         }
59810     },
59811
59812     addCls: function(obj) {
59813         var i = 0,
59814             items = this.items,
59815             surface = this.getSurface(),
59816             len = this.length;
59817         
59818         if (surface) {
59819             for (; i < len; i++) {
59820                 surface.addCls(items[i], obj);
59821             }
59822         }
59823     },
59824
59825     removeCls: function(obj) {
59826         var i = 0,
59827             items = this.items,
59828             surface = this.getSurface(),
59829             len = this.length;
59830         
59831         if (surface) {
59832             for (; i < len; i++) {
59833                 surface.removeCls(items[i], obj);
59834             }
59835         }
59836     },
59837     
59838     /**
59839      * Grab the surface from the items
59840      * @private
59841      * @return {Ext.draw.Surface} The surface, null if not found
59842      */
59843     getSurface: function(){
59844         var first = this.first();
59845         if (first) {
59846             return first.surface;
59847         }
59848         return null;
59849     },
59850     
59851     /**
59852      * Destroys the SpriteGroup
59853      */
59854     destroy: function(){
59855         var me = this,
59856             surface = me.getSurface(),
59857             item;
59858             
59859         if (surface) {
59860             while (me.getCount() > 0) {
59861                 item = me.first();
59862                 me.remove(item);
59863                 surface.remove(item);
59864             }
59865         }
59866         me.clearListeners();
59867     }
59868 });
59869
59870 /**
59871  * @class Ext.layout.component.Auto
59872  * @extends Ext.layout.component.Component
59873  * @private
59874  *
59875  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
59876  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
59877  */
59878
59879 Ext.define('Ext.layout.component.Auto', {
59880
59881     /* Begin Definitions */
59882
59883     alias: 'layout.autocomponent',
59884
59885     extend: 'Ext.layout.component.Component',
59886
59887     /* End Definitions */
59888
59889     type: 'autocomponent',
59890
59891     onLayout : function(width, height) {
59892         this.setTargetSize(width, height);
59893     }
59894 });
59895 /**
59896  * @class Ext.chart.theme.Theme
59897  * 
59898  * Provides chart theming.
59899  * 
59900  * Used as mixins by Ext.chart.Chart.
59901  */
59902 Ext.define('Ext.chart.theme.Theme', {
59903
59904     /* Begin Definitions */
59905
59906     requires: ['Ext.draw.Color'],
59907
59908     /* End Definitions */
59909
59910     theme: 'Base',
59911     themeAttrs: false,
59912     
59913     initTheme: function(theme) {
59914         var me = this,
59915             themes = Ext.chart.theme,
59916             key, gradients;
59917         if (theme) {
59918             theme = theme.split(':');
59919             for (key in themes) {
59920                 if (key == theme[0]) {
59921                     gradients = theme[1] == 'gradients';
59922                     me.themeAttrs = new themes[key]({
59923                         useGradients: gradients
59924                     });
59925                     if (gradients) {
59926                         me.gradients = me.themeAttrs.gradients;
59927                     }
59928                     if (me.themeAttrs.background) {
59929                         me.background = me.themeAttrs.background;
59930                     }
59931                     return;
59932                 }
59933             }
59934         }
59935     }
59936 }, 
59937 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
59938 function() {
59939    /* Theme constructor: takes either a complex object with styles like:
59940   
59941    {
59942         axis: {
59943             fill: '#000',
59944             'stroke-width': 1
59945         },
59946         axisLabelTop: {
59947             fill: '#000',
59948             font: '11px Arial'
59949         },
59950         axisLabelLeft: {
59951             fill: '#000',
59952             font: '11px Arial'
59953         },
59954         axisLabelRight: {
59955             fill: '#000',
59956             font: '11px Arial'
59957         },
59958         axisLabelBottom: {
59959             fill: '#000',
59960             font: '11px Arial'
59961         },
59962         axisTitleTop: {
59963             fill: '#000',
59964             font: '11px Arial'
59965         },
59966         axisTitleLeft: {
59967             fill: '#000',
59968             font: '11px Arial'
59969         },
59970         axisTitleRight: {
59971             fill: '#000',
59972             font: '11px Arial'
59973         },
59974         axisTitleBottom: {
59975             fill: '#000',
59976             font: '11px Arial'
59977         },
59978         series: {
59979             'stroke-width': 1
59980         },
59981         seriesLabel: {
59982             font: '12px Arial',
59983             fill: '#333'
59984         },
59985         marker: {
59986             stroke: '#555',
59987             fill: '#000',
59988             radius: 3,
59989             size: 3
59990         },
59991         seriesThemes: [{
59992             fill: '#C6DBEF'
59993         }, {
59994             fill: '#9ECAE1'
59995         }, {
59996             fill: '#6BAED6'
59997         }, {
59998             fill: '#4292C6'
59999         }, {
60000             fill: '#2171B5'
60001         }, {
60002             fill: '#084594'
60003         }],
60004         markerThemes: [{
60005             fill: '#084594',
60006             type: 'circle' 
60007         }, {
60008             fill: '#2171B5',
60009             type: 'cross'
60010         }, {
60011             fill: '#4292C6',
60012             type: 'plus'
60013         }]
60014     }
60015   
60016   ...or also takes just an array of colors and creates the complex object:
60017   
60018   {
60019       colors: ['#aaa', '#bcd', '#eee']
60020   }
60021   
60022   ...or takes just a base color and makes a theme from it
60023   
60024   {
60025       baseColor: '#bce'
60026   }
60027   
60028   To create a new theme you may add it to the Themes object:
60029   
60030   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
60031       constructor: function(config) {
60032           Ext.chart.theme.call(this, config, {
60033               baseColor: '#mybasecolor'
60034           });
60035       }
60036   });
60037   
60038   //Proposal:
60039   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
60040   
60041   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
60042   
60043   {
60044       theme: 'mynewtheme'
60045   }
60046  */
60047
60048 (function() {
60049     Ext.chart.theme = function(config, base) {
60050         config = config || {};
60051         var i = 0, l, colors, color,
60052             seriesThemes, markerThemes,
60053             seriesTheme, markerTheme, 
60054             key, gradients = [],
60055             midColor, midL;
60056         
60057         if (config.baseColor) {
60058             midColor = Ext.draw.Color.fromString(config.baseColor);
60059             midL = midColor.getHSL()[2];
60060             if (midL < 0.15) {
60061                 midColor = midColor.getLighter(0.3);
60062             } else if (midL < 0.3) {
60063                 midColor = midColor.getLighter(0.15);
60064             } else if (midL > 0.85) {
60065                 midColor = midColor.getDarker(0.3);
60066             } else if (midL > 0.7) {
60067                 midColor = midColor.getDarker(0.15);
60068             }
60069             config.colors = [ midColor.getDarker(0.3).toString(),
60070                               midColor.getDarker(0.15).toString(),
60071                               midColor.toString(),
60072                               midColor.getLighter(0.15).toString(),
60073                               midColor.getLighter(0.3).toString()];
60074
60075             delete config.baseColor;
60076         }
60077         if (config.colors) {
60078             colors = config.colors.slice();
60079             markerThemes = base.markerThemes;
60080             seriesThemes = base.seriesThemes;
60081             l = colors.length;
60082             base.colors = colors;
60083             for (; i < l; i++) {
60084                 color = colors[i];
60085                 markerTheme = markerThemes[i] || {};
60086                 seriesTheme = seriesThemes[i] || {};
60087                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
60088                 markerThemes[i] = markerTheme;
60089                 seriesThemes[i] = seriesTheme;
60090             }
60091             base.markerThemes = markerThemes.slice(0, l);
60092             base.seriesThemes = seriesThemes.slice(0, l);
60093         //the user is configuring something in particular (either markers, series or pie slices)
60094         }
60095         for (key in base) {
60096             if (key in config) {
60097                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
60098                     Ext.apply(base[key], config[key]);
60099                 } else {
60100                     base[key] = config[key];
60101                 }
60102             }
60103         }
60104         if (config.useGradients) {
60105             colors = base.colors || (function () {
60106                 var ans = [];
60107                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
60108                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
60109                 }
60110                 return ans;
60111             })();
60112             for (i = 0, l = colors.length; i < l; i++) {
60113                 midColor = Ext.draw.Color.fromString(colors[i]);
60114                 if (midColor) {
60115                     color = midColor.getDarker(0.1).toString();
60116                     midColor = midColor.toString();
60117                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
60118                     gradients.push({
60119                         id: key,
60120                         angle: 45,
60121                         stops: {
60122                             0: {
60123                                 color: midColor.toString()
60124                             },
60125                             100: {
60126                                 color: color.toString()
60127                             }
60128                         }
60129                     });
60130                     colors[i] = 'url(#' + key + ')'; 
60131                 }
60132             }
60133             base.gradients = gradients;
60134             base.colors = colors;
60135         }
60136         /*
60137         base.axis = Ext.apply(base.axis || {}, config.axis || {});
60138         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
60139         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
60140         */
60141         Ext.apply(this, base);
60142     };
60143 })();
60144 });
60145
60146 /**
60147  * @class Ext.chart.Mask
60148  *
60149  * Defines a mask for a chart's series.
60150  * The 'chart' member must be set prior to rendering.
60151  *
60152  * A Mask can be used to select a certain region in a chart.
60153  * When enabled, the `select` event will be triggered when a
60154  * region is selected by the mask, allowing the user to perform
60155  * other tasks like zooming on that region, etc.
60156  *
60157  * In order to use the mask one has to set the Chart `mask` option to
60158  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
60159  * listener could be:
60160  *
60161         items: {
60162             xtype: 'chart',
60163             animate: true,
60164             store: store1,
60165             mask: 'horizontal',
60166             listeners: {
60167                 select: {
60168                     fn: function(me, selection) {
60169                         me.setZoom(selection);
60170                         me.mask.hide();
60171                     }
60172                 }
60173             },
60174
60175  * In this example we zoom the chart to that particular region. You can also get
60176  * a handle to a mask instance from the chart object. The `chart.mask` element is a
60177  * `Ext.Panel`.
60178  * 
60179  */
60180 Ext.define('Ext.chart.Mask', {
60181     require: ['Ext.chart.MaskLayer'],
60182     /**
60183      * Creates new Mask.
60184      * @param {Object} config (optional) Config object.
60185      */
60186     constructor: function(config) {
60187         var me = this;
60188
60189         me.addEvents('select');
60190
60191         if (config) {
60192             Ext.apply(me, config);
60193         }
60194         if (me.mask) {
60195             me.on('afterrender', function() {
60196                 //create a mask layer component
60197                 var comp = Ext.create('Ext.chart.MaskLayer', {
60198                     renderTo: me.el
60199                 });
60200                 comp.el.on({
60201                     'mousemove': function(e) {
60202                         me.onMouseMove(e);
60203                     },
60204                     'mouseup': function(e) {
60205                         me.resized(e);
60206                     }
60207                 });
60208                 //create a resize handler for the component
60209                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
60210                     el: comp.el,
60211                     handles: 'all',
60212                     pinned: true
60213                 });
60214                 resizeHandler.on({
60215                     'resize': function(e) {
60216                         me.resized(e);    
60217                     }    
60218                 });
60219                 comp.initDraggable();
60220                 me.maskType = me.mask;
60221                 me.mask = comp;
60222                 me.maskSprite = me.surface.add({
60223                     type: 'path',
60224                     path: ['M', 0, 0],
60225                     zIndex: 1001,
60226                     opacity: 0.7,
60227                     hidden: true,
60228                     stroke: '#444'
60229                 });
60230             }, me, { single: true });
60231         }
60232     },
60233     
60234     resized: function(e) {
60235         var me = this,
60236             bbox = me.bbox || me.chartBBox,
60237             x = bbox.x,
60238             y = bbox.y,
60239             width = bbox.width,
60240             height = bbox.height,
60241             box = me.mask.getBox(true),
60242             max = Math.max,
60243             min = Math.min,
60244             staticX = box.x - x,
60245             staticY = box.y - y;
60246         
60247         staticX = max(staticX, x);
60248         staticY = max(staticY, y);
60249         staticX = min(staticX, width);
60250         staticY = min(staticY, height);
60251         box.x = staticX;
60252         box.y = staticY;
60253         me.fireEvent('select', me, box);
60254     },
60255
60256     onMouseUp: function(e) {
60257         var me = this,
60258             bbox = me.bbox || me.chartBBox,
60259             sel = me.maskSelection;
60260         me.maskMouseDown = false;
60261         me.mouseDown = false;
60262         if (me.mouseMoved) {
60263             me.onMouseMove(e);
60264             me.mouseMoved = false;
60265             me.fireEvent('select', me, {
60266                 x: sel.x - bbox.x,
60267                 y: sel.y - bbox.y,
60268                 width: sel.width,
60269                 height: sel.height
60270             });
60271         }
60272     },
60273
60274     onMouseDown: function(e) {
60275         var me = this;
60276         me.mouseDown = true;
60277         me.mouseMoved = false;
60278         me.maskMouseDown = {
60279             x: e.getPageX() - me.el.getX(),
60280             y: e.getPageY() - me.el.getY()
60281         };
60282     },
60283
60284     onMouseMove: function(e) {
60285         var me = this,
60286             mask = me.maskType,
60287             bbox = me.bbox || me.chartBBox,
60288             x = bbox.x,
60289             y = bbox.y,
60290             math = Math,
60291             floor = math.floor,
60292             abs = math.abs,
60293             min = math.min,
60294             max = math.max,
60295             height = floor(y + bbox.height),
60296             width = floor(x + bbox.width),
60297             posX = e.getPageX(),
60298             posY = e.getPageY(),
60299             staticX = posX - me.el.getX(),
60300             staticY = posY - me.el.getY(),
60301             maskMouseDown = me.maskMouseDown,
60302             path;
60303         
60304         me.mouseMoved = me.mouseDown;
60305         staticX = max(staticX, x);
60306         staticY = max(staticY, y);
60307         staticX = min(staticX, width);
60308         staticY = min(staticY, height);
60309         if (maskMouseDown && me.mouseDown) {
60310             if (mask == 'horizontal') {
60311                 staticY = y;
60312                 maskMouseDown.y = height;
60313                 posY = me.el.getY() + bbox.height + me.insetPadding;
60314             }
60315             else if (mask == 'vertical') {
60316                 staticX = x;
60317                 maskMouseDown.x = width;
60318             }
60319             width = maskMouseDown.x - staticX;
60320             height = maskMouseDown.y - staticY;
60321             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
60322             me.maskSelection = {
60323                 x: width > 0 ? staticX : staticX + width,
60324                 y: height > 0 ? staticY : staticY + height,
60325                 width: abs(width),
60326                 height: abs(height)
60327             };
60328             me.mask.updateBox(me.maskSelection);
60329             me.mask.show();
60330             me.maskSprite.setAttributes({
60331                 hidden: true    
60332             }, true);
60333         }
60334         else {
60335             if (mask == 'horizontal') {
60336                 path = ['M', staticX, y, 'L', staticX, height];
60337             }
60338             else if (mask == 'vertical') {
60339                 path = ['M', x, staticY, 'L', width, staticY];
60340             }
60341             else {
60342                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
60343             }
60344             me.maskSprite.setAttributes({
60345                 path: path,
60346                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
60347                 'stroke-width': mask === true ? 1 : 3,
60348                 hidden: false
60349             }, true);
60350         }
60351     },
60352
60353     onMouseLeave: function(e) {
60354         var me = this;
60355         me.mouseMoved = false;
60356         me.mouseDown = false;
60357         me.maskMouseDown = false;
60358         me.mask.hide();
60359         me.maskSprite.hide(true);
60360     }
60361 });
60362     
60363 /**
60364  * @class Ext.chart.Navigation
60365  *
60366  * Handles panning and zooming capabilities.
60367  *
60368  * Used as mixin by Ext.chart.Chart.
60369  */
60370 Ext.define('Ext.chart.Navigation', {
60371
60372     constructor: function() {
60373         this.originalStore = this.store;
60374     },
60375
60376     /**
60377      * Zooms the chart to the specified selection range.
60378      * Can be used with a selection mask. For example:
60379      *
60380      *     items: {
60381      *         xtype: 'chart',
60382      *         animate: true,
60383      *         store: store1,
60384      *         mask: 'horizontal',
60385      *         listeners: {
60386      *             select: {
60387      *                 fn: function(me, selection) {
60388      *                     me.setZoom(selection);
60389      *                     me.mask.hide();
60390      *                 }
60391      *             }
60392      *         }
60393      *     }
60394      */
60395     setZoom: function(zoomConfig) {
60396         var me = this,
60397             axes = me.axes,
60398             bbox = me.chartBBox,
60399             xScale = 1 / bbox.width,
60400             yScale = 1 / bbox.height,
60401             zoomer = {
60402                 x : zoomConfig.x * xScale,
60403                 y : zoomConfig.y * yScale,
60404                 width : zoomConfig.width * xScale,
60405                 height : zoomConfig.height * yScale
60406             };
60407         axes.each(function(axis) {
60408             var ends = axis.calcEnds();
60409             if (axis.position == 'bottom' || axis.position == 'top') {
60410                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
60411                     to = (ends.to - ends.from) * zoomer.width + from;
60412                 axis.minimum = from;
60413                 axis.maximum = to;
60414             } else {
60415                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
60416                     from = to - (ends.to - ends.from) * zoomer.height;
60417                 axis.minimum = from;
60418                 axis.maximum = to;
60419             }
60420         });
60421         me.redraw(false);
60422     },
60423
60424     /**
60425      * Restores the zoom to the original value. This can be used to reset
60426      * the previous zoom state set by `setZoom`. For example:
60427      *
60428      *     myChart.restoreZoom();
60429      */
60430     restoreZoom: function() {
60431         this.store = this.substore = this.originalStore;
60432         this.redraw(true);
60433     }
60434
60435 });
60436
60437 /**
60438  * @class Ext.chart.Shape
60439  * @ignore
60440  */
60441 Ext.define('Ext.chart.Shape', {
60442
60443     /* Begin Definitions */
60444
60445     singleton: true,
60446
60447     /* End Definitions */
60448
60449     circle: function (surface, opts) {
60450         return surface.add(Ext.apply({
60451             type: 'circle',
60452             x: opts.x,
60453             y: opts.y,
60454             stroke: null,
60455             radius: opts.radius
60456         }, opts));
60457     },
60458     line: function (surface, opts) {
60459         return surface.add(Ext.apply({
60460             type: 'rect',
60461             x: opts.x - opts.radius,
60462             y: opts.y - opts.radius,
60463             height: 2 * opts.radius,
60464             width: 2 * opts.radius / 5
60465         }, opts));
60466     },
60467     square: function (surface, opts) {
60468         return surface.add(Ext.applyIf({
60469             type: 'rect',
60470             x: opts.x - opts.radius,
60471             y: opts.y - opts.radius,
60472             height: 2 * opts.radius,
60473             width: 2 * opts.radius,
60474             radius: null
60475         }, opts));
60476     },
60477     triangle: function (surface, opts) {
60478         opts.radius *= 1.75;
60479         return surface.add(Ext.apply({
60480             type: 'path',
60481             stroke: null,
60482             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
60483         }, opts));
60484     },
60485     diamond: function (surface, opts) {
60486         var r = opts.radius;
60487         r *= 1.5;
60488         return surface.add(Ext.apply({
60489             type: 'path',
60490             stroke: null,
60491             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
60492         }, opts));
60493     },
60494     cross: function (surface, opts) {
60495         var r = opts.radius;
60496         r = r / 1.7;
60497         return surface.add(Ext.apply({
60498             type: 'path',
60499             stroke: null,
60500             path: "M".concat(opts.x - r, ",", opts.y, "l", [-r, -r, r, -r, r, r, r, -r, r, r, -r, r, r, r, -r, r, -r, -r, -r, r, -r, -r, "z"])
60501         }, opts));
60502     },
60503     plus: function (surface, opts) {
60504         var r = opts.radius / 1.3;
60505         return surface.add(Ext.apply({
60506             type: 'path',
60507             stroke: null,
60508             path: "M".concat(opts.x - r / 2, ",", opts.y - r / 2, "l", [0, -r, r, 0, 0, r, r, 0, 0, r, -r, 0, 0, r, -r, 0, 0, -r, -r, 0, 0, -r, "z"])
60509         }, opts));
60510     },
60511     arrow: function (surface, opts) {
60512         var r = opts.radius;
60513         return surface.add(Ext.apply({
60514             type: 'path',
60515             path: "M".concat(opts.x - r * 0.7, ",", opts.y - r * 0.4, "l", [r * 0.6, 0, 0, -r * 0.4, r, r * 0.8, -r, r * 0.8, 0, -r * 0.4, -r * 0.6, 0], "z")
60516         }, opts));
60517     },
60518     drop: function (surface, x, y, text, size, angle) {
60519         size = size || 30;
60520         angle = angle || 0;
60521         surface.add({
60522             type: 'path',
60523             path: ['M', x, y, 'l', size, 0, 'A', size * 0.4, size * 0.4, 0, 1, 0, x + size * 0.7, y - size * 0.7, 'z'],
60524             fill: '#000',
60525             stroke: 'none',
60526             rotate: {
60527                 degrees: 22.5 - angle,
60528                 x: x,
60529                 y: y
60530             }
60531         });
60532         angle = (angle + 90) * Math.PI / 180;
60533         surface.add({
60534             type: 'text',
60535             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
60536             y: y + size * Math.cos(angle) + 5,
60537             text:  text,
60538             'font-size': size * 12 / 40,
60539             stroke: 'none',
60540             fill: '#fff'
60541         });
60542     }
60543 });
60544 /**
60545  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
60546  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
60547  * sprites to the canvas, initialize other graphic components, etc. One of the most used
60548  * methods for this class is the `add` method, to add Sprites to the surface.
60549  *
60550  * Most of the Surface methods are abstract and they have a concrete implementation
60551  * in VML or SVG engines.
60552  *
60553  * A Surface instance can be accessed as a property of a draw component. For example:
60554  *
60555  *     drawComponent.surface.add({
60556  *         type: 'circle',
60557  *         fill: '#ffc',
60558  *         radius: 100,
60559  *         x: 100,
60560  *         y: 100
60561  *     });
60562  *
60563  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
60564  * class documentation.
60565  *
60566  * # Listeners
60567  *
60568  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
60569  *
60570  * - mousedown
60571  * - mouseup
60572  * - mouseover
60573  * - mouseout
60574  * - mousemove
60575  * - mouseenter
60576  * - mouseleave
60577  * - click
60578  *
60579  * For example:
60580  *
60581  *     drawComponent.surface.on({
60582  *        'mousemove': function() {
60583  *             console.log('moving the mouse over the surface');
60584  *         }
60585  *     });
60586  *
60587  * # Example
60588  *
60589  *     var drawComponent = Ext.create('Ext.draw.Component', {
60590  *         width: 800,
60591  *         height: 600,
60592  *         renderTo: document.body
60593  *     }), surface = drawComponent.surface;
60594  *
60595  *     surface.add([{
60596  *         type: 'circle',
60597  *         radius: 10,
60598  *         fill: '#f00',
60599  *         x: 10,
60600  *         y: 10,
60601  *         group: 'circles'
60602  *     }, {
60603  *         type: 'circle',
60604  *         radius: 10,
60605  *         fill: '#0f0',
60606  *         x: 50,
60607  *         y: 50,
60608  *         group: 'circles'
60609  *     }, {
60610  *         type: 'circle',
60611  *         radius: 10,
60612  *         fill: '#00f',
60613  *         x: 100,
60614  *         y: 100,
60615  *         group: 'circles'
60616  *     }, {
60617  *         type: 'rect',
60618  *         width: 20,
60619  *         height: 20,
60620  *         fill: '#f00',
60621  *         x: 10,
60622  *         y: 10,
60623  *         group: 'rectangles'
60624  *     }, {
60625  *         type: 'rect',
60626  *         width: 20,
60627  *         height: 20,
60628  *         fill: '#0f0',
60629  *         x: 50,
60630  *         y: 50,
60631  *         group: 'rectangles'
60632  *     }, {
60633  *         type: 'rect',
60634  *         width: 20,
60635  *         height: 20,
60636  *         fill: '#00f',
60637  *         x: 100,
60638  *         y: 100,
60639  *         group: 'rectangles'
60640  *     }]);
60641  *
60642  *     // Get references to my groups
60643  *     circles = surface.getGroup('circles');
60644  *     rectangles = surface.getGroup('rectangles');
60645  *
60646  *     // Animate the circles down
60647  *     circles.animate({
60648  *         duration: 1000,
60649  *         to: {
60650  *             translate: {
60651  *                 y: 200
60652  *             }
60653  *         }
60654  *     });
60655  *
60656  *     // Animate the rectangles across
60657  *     rectangles.animate({
60658  *         duration: 1000,
60659  *         to: {
60660  *             translate: {
60661  *                 x: 200
60662  *             }
60663  *         }
60664  *     });
60665  */
60666 Ext.define('Ext.draw.Surface', {
60667
60668     /* Begin Definitions */
60669
60670     mixins: {
60671         observable: 'Ext.util.Observable'
60672     },
60673
60674     requires: ['Ext.draw.CompositeSprite'],
60675     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
60676
60677     separatorRe: /[, ]+/,
60678
60679     statics: {
60680         /**
60681          * Creates and returns a new concrete Surface instance appropriate for the current environment.
60682          * @param {Object} config Initial configuration for the Surface instance
60683          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
60684          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
60685          * @return {Object} The created Surface or false.
60686          * @static
60687          */
60688         create: function(config, enginePriority) {
60689             enginePriority = enginePriority || ['Svg', 'Vml'];
60690
60691             var i = 0,
60692                 len = enginePriority.length,
60693                 surfaceClass;
60694
60695             for (; i < len; i++) {
60696                 if (Ext.supports[enginePriority[i]]) {
60697                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
60698                 }
60699             }
60700             return false;
60701         }
60702     },
60703
60704     /* End Definitions */
60705
60706     // @private
60707     availableAttrs: {
60708         blur: 0,
60709         "clip-rect": "0 0 1e9 1e9",
60710         cursor: "default",
60711         cx: 0,
60712         cy: 0,
60713         'dominant-baseline': 'auto',
60714         fill: "none",
60715         "fill-opacity": 1,
60716         font: '10px "Arial"',
60717         "font-family": '"Arial"',
60718         "font-size": "10",
60719         "font-style": "normal",
60720         "font-weight": 400,
60721         gradient: "",
60722         height: 0,
60723         hidden: false,
60724         href: "http://sencha.com/",
60725         opacity: 1,
60726         path: "M0,0",
60727         radius: 0,
60728         rx: 0,
60729         ry: 0,
60730         scale: "1 1",
60731         src: "",
60732         stroke: "#000",
60733         "stroke-dasharray": "",
60734         "stroke-linecap": "butt",
60735         "stroke-linejoin": "butt",
60736         "stroke-miterlimit": 0,
60737         "stroke-opacity": 1,
60738         "stroke-width": 1,
60739         target: "_blank",
60740         text: "",
60741         "text-anchor": "middle",
60742         title: "Ext Draw",
60743         width: 0,
60744         x: 0,
60745         y: 0,
60746         zIndex: 0
60747     },
60748
60749     /**
60750      * @cfg {Number} height
60751      * The height of this component in pixels (defaults to auto).
60752      */
60753     /**
60754      * @cfg {Number} width
60755      * The width of this component in pixels (defaults to auto).
60756      */
60757
60758     container: undefined,
60759     height: 352,
60760     width: 512,
60761     x: 0,
60762     y: 0,
60763
60764     /**
60765      * @private Flag indicating that the surface implementation requires sprites to be maintained
60766      * in order of their zIndex. Impls that don't require this can set it to false.
60767      */
60768     orderSpritesByZIndex: true,
60769
60770
60771     /**
60772      * Creates new Surface.
60773      * @param {Object} config (optional) Config object.
60774      */
60775     constructor: function(config) {
60776         var me = this;
60777         config = config || {};
60778         Ext.apply(me, config);
60779
60780         me.domRef = Ext.getDoc().dom;
60781
60782         me.customAttributes = {};
60783
60784         me.addEvents(
60785             'mousedown',
60786             'mouseup',
60787             'mouseover',
60788             'mouseout',
60789             'mousemove',
60790             'mouseenter',
60791             'mouseleave',
60792             'click'
60793         );
60794
60795         me.mixins.observable.constructor.call(me);
60796
60797         me.getId();
60798         me.initGradients();
60799         me.initItems();
60800         if (me.renderTo) {
60801             me.render(me.renderTo);
60802             delete me.renderTo;
60803         }
60804         me.initBackground(config.background);
60805     },
60806
60807     // @private called to initialize components in the surface
60808     // this is dependent on the underlying implementation.
60809     initSurface: Ext.emptyFn,
60810
60811     // @private called to setup the surface to render an item
60812     //this is dependent on the underlying implementation.
60813     renderItem: Ext.emptyFn,
60814
60815     // @private
60816     renderItems: Ext.emptyFn,
60817
60818     // @private
60819     setViewBox: function (x, y, width, height) {
60820         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
60821             this.viewBox = {x: x, y: y, width: width, height: height};
60822             this.applyViewBox();
60823         }
60824     },
60825
60826     /**
60827      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
60828      *
60829      * For example:
60830      *
60831      *     drawComponent.surface.addCls(sprite, 'x-visible');
60832      *
60833      * @param {Object} sprite The sprite to add the class to.
60834      * @param {String/String[]} className The CSS class to add, or an array of classes
60835      * @method
60836      */
60837     addCls: Ext.emptyFn,
60838
60839     /**
60840      * Removes one or more CSS classes from the element.
60841      *
60842      * For example:
60843      *
60844      *     drawComponent.surface.removeCls(sprite, 'x-visible');
60845      *
60846      * @param {Object} sprite The sprite to remove the class from.
60847      * @param {String/String[]} className The CSS class to remove, or an array of classes
60848      * @method
60849      */
60850     removeCls: Ext.emptyFn,
60851
60852     /**
60853      * Sets CSS style attributes to an element.
60854      *
60855      * For example:
60856      *
60857      *     drawComponent.surface.setStyle(sprite, {
60858      *         'cursor': 'pointer'
60859      *     });
60860      *
60861      * @param {Object} sprite The sprite to add, or an array of classes to
60862      * @param {Object} styles An Object with CSS styles.
60863      * @method
60864      */
60865     setStyle: Ext.emptyFn,
60866
60867     // @private
60868     initGradients: function() {
60869         var gradients = this.gradients;
60870         if (gradients) {
60871             Ext.each(gradients, this.addGradient, this);
60872         }
60873     },
60874
60875     // @private
60876     initItems: function() {
60877         var items = this.items;
60878         this.items = Ext.create('Ext.draw.CompositeSprite');
60879         this.groups = Ext.create('Ext.draw.CompositeSprite');
60880         if (items) {
60881             this.add(items);
60882         }
60883     },
60884
60885     // @private
60886     initBackground: function(config) {
60887         var me = this,
60888             width = me.width,
60889             height = me.height,
60890             gradientId, gradient, backgroundSprite;
60891         if (config) {
60892             if (config.gradient) {
60893                 gradient = config.gradient;
60894                 gradientId = gradient.id;
60895                 me.addGradient(gradient);
60896                 me.background = me.add({
60897                     type: 'rect',
60898                     x: 0,
60899                     y: 0,
60900                     width: width,
60901                     height: height,
60902                     fill: 'url(#' + gradientId + ')'
60903                 });
60904             } else if (config.fill) {
60905                 me.background = me.add({
60906                     type: 'rect',
60907                     x: 0,
60908                     y: 0,
60909                     width: width,
60910                     height: height,
60911                     fill: config.fill
60912                 });
60913             } else if (config.image) {
60914                 me.background = me.add({
60915                     type: 'image',
60916                     x: 0,
60917                     y: 0,
60918                     width: width,
60919                     height: height,
60920                     src: config.image
60921                 });
60922             }
60923         }
60924     },
60925
60926     /**
60927      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
60928      *
60929      * For example:
60930      *
60931      *     drawComponent.surface.setSize(500, 500);
60932      *
60933      * This method is generally called when also setting the size of the draw Component.
60934      *
60935      * @param {Number} w The new width of the canvas.
60936      * @param {Number} h The new height of the canvas.
60937      */
60938     setSize: function(w, h) {
60939         if (this.background) {
60940             this.background.setAttributes({
60941                 width: w,
60942                 height: h,
60943                 hidden: false
60944             }, true);
60945         }
60946         this.applyViewBox();
60947     },
60948
60949     // @private
60950     scrubAttrs: function(sprite) {
60951         var i,
60952             attrs = {},
60953             exclude = {},
60954             sattr = sprite.attr;
60955         for (i in sattr) {
60956             // Narrow down attributes to the main set
60957             if (this.translateAttrs.hasOwnProperty(i)) {
60958                 // Translated attr
60959                 attrs[this.translateAttrs[i]] = sattr[i];
60960                 exclude[this.translateAttrs[i]] = true;
60961             }
60962             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
60963                 // Passtrhough attr
60964                 attrs[i] = sattr[i];
60965             }
60966         }
60967         return attrs;
60968     },
60969
60970     // @private
60971     onClick: function(e) {
60972         this.processEvent('click', e);
60973     },
60974
60975     // @private
60976     onMouseUp: function(e) {
60977         this.processEvent('mouseup', e);
60978     },
60979
60980     // @private
60981     onMouseDown: function(e) {
60982         this.processEvent('mousedown', e);
60983     },
60984
60985     // @private
60986     onMouseOver: function(e) {
60987         this.processEvent('mouseover', e);
60988     },
60989
60990     // @private
60991     onMouseOut: function(e) {
60992         this.processEvent('mouseout', e);
60993     },
60994
60995     // @private
60996     onMouseMove: function(e) {
60997         this.fireEvent('mousemove', e);
60998     },
60999
61000     // @private
61001     onMouseEnter: Ext.emptyFn,
61002
61003     // @private
61004     onMouseLeave: Ext.emptyFn,
61005
61006     /**
61007      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
61008      * a gradient via this method will not take effect if the surface has already been rendered.
61009      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
61010      * than calling this method, especially if the surface is rendered immediately (e.g. due to
61011      * 'renderTo' in its config). For more information on how to create gradients in the Chart
61012      * configuration object please refer to {@link Ext.chart.Chart}.
61013      *
61014      * The gradient object to be passed into this method is composed by:
61015      *
61016      * - **id** - string - The unique name of the gradient.
61017      * - **angle** - number, optional - The angle of the gradient in degrees.
61018      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
61019      *
61020      * For example:
61021      *
61022      *    drawComponent.surface.addGradient({
61023      *        id: 'gradientId',
61024      *        angle: 45,
61025      *        stops: {
61026      *            0: {
61027      *                color: '#555'
61028      *            },
61029      *            100: {
61030      *                color: '#ddd'
61031      *            }
61032      *        }
61033      *    });
61034      *
61035      * @method
61036      */
61037     addGradient: Ext.emptyFn,
61038
61039     /**
61040      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
61041      * passed into this method.
61042      *
61043      * For example:
61044      *
61045      *     drawComponent.surface.add({
61046      *         type: 'circle',
61047      *         fill: '#ffc',
61048      *         radius: 100,
61049      *         x: 100,
61050      *         y: 100
61051      *     });
61052      *
61053      */
61054     add: function() {
61055         var args = Array.prototype.slice.call(arguments),
61056             sprite,
61057             index;
61058
61059         var hasMultipleArgs = args.length > 1;
61060         if (hasMultipleArgs || Ext.isArray(args[0])) {
61061             var items = hasMultipleArgs ? args : args[0],
61062                 results = [],
61063                 i, ln, item;
61064
61065             for (i = 0, ln = items.length; i < ln; i++) {
61066                 item = items[i];
61067                 item = this.add(item);
61068                 results.push(item);
61069             }
61070
61071             return results;
61072         }
61073         sprite = this.prepareItems(args[0], true)[0];
61074         this.insertByZIndex(sprite);
61075         this.onAdd(sprite);
61076         return sprite;
61077     },
61078
61079     /**
61080      * @private
61081      * Inserts a given sprite into the correct position in the items collection, according to
61082      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
61083      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
61084      * the sprites in the correct order for proper z-index stacking.
61085      * @param {Ext.draw.Sprite} sprite
61086      * @return {Number} the sprite's new index in the list
61087      */
61088     insertByZIndex: function(sprite) {
61089         var me = this,
61090             sprites = me.items.items,
61091             len = sprites.length,
61092             ceil = Math.ceil,
61093             zIndex = sprite.attr.zIndex,
61094             idx = len,
61095             high = idx - 1,
61096             low = 0,
61097             otherZIndex;
61098
61099         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
61100             // Find the target index via a binary search for speed
61101             while (low <= high) {
61102                 idx = ceil((low + high) / 2);
61103                 otherZIndex = sprites[idx].attr.zIndex;
61104                 if (otherZIndex > zIndex) {
61105                     high = idx - 1;
61106                 }
61107                 else if (otherZIndex < zIndex) {
61108                     low = idx + 1;
61109                 }
61110                 else {
61111                     break;
61112                 }
61113             }
61114             // Step forward to the end of a sequence of the same or lower z-index
61115             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
61116                 idx++;
61117             }
61118         }
61119
61120         me.items.insert(idx, sprite);
61121         return idx;
61122     },
61123
61124     onAdd: function(sprite) {
61125         var group = sprite.group,
61126             draggable = sprite.draggable,
61127             groups, ln, i;
61128         if (group) {
61129             groups = [].concat(group);
61130             ln = groups.length;
61131             for (i = 0; i < ln; i++) {
61132                 group = groups[i];
61133                 this.getGroup(group).add(sprite);
61134             }
61135             delete sprite.group;
61136         }
61137         if (draggable) {
61138             sprite.initDraggable();
61139         }
61140     },
61141
61142     /**
61143      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
61144      * You can also call the sprite own `remove` method.
61145      *
61146      * For example:
61147      *
61148      *     drawComponent.surface.remove(sprite);
61149      *     //or...
61150      *     sprite.remove();
61151      *
61152      * @param {Ext.draw.Sprite} sprite
61153      * @param {Boolean} destroySprite
61154      * @return {Number} the sprite's new index in the list
61155      */
61156     remove: function(sprite, destroySprite) {
61157         if (sprite) {
61158             this.items.remove(sprite);
61159             this.groups.each(function(item) {
61160                 item.remove(sprite);
61161             });
61162             sprite.onRemove();
61163             if (destroySprite === true) {
61164                 sprite.destroy();
61165             }
61166         }
61167     },
61168
61169     /**
61170      * Removes all sprites from the surface, optionally destroying the sprites in the process.
61171      *
61172      * For example:
61173      *
61174      *     drawComponent.surface.removeAll();
61175      *
61176      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
61177      * @return {Number} The sprite's new index in the list.
61178      */
61179     removeAll: function(destroySprites) {
61180         var items = this.items.items,
61181             ln = items.length,
61182             i;
61183         for (i = ln - 1; i > -1; i--) {
61184             this.remove(items[i], destroySprites);
61185         }
61186     },
61187
61188     onRemove: Ext.emptyFn,
61189
61190     onDestroy: Ext.emptyFn,
61191
61192     /**
61193      * @private Using the current viewBox property and the surface's width and height, calculate the
61194      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
61195      */
61196     applyViewBox: function() {
61197         var me = this,
61198             viewBox = me.viewBox,
61199             width = me.width,
61200             height = me.height,
61201             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
61202             relativeHeight, relativeWidth, size;
61203
61204         if (viewBox && (width || height)) {
61205             viewBoxX = viewBox.x;
61206             viewBoxY = viewBox.y;
61207             viewBoxWidth = viewBox.width;
61208             viewBoxHeight = viewBox.height;
61209             relativeHeight = height / viewBoxHeight;
61210             relativeWidth = width / viewBoxWidth;
61211
61212             if (viewBoxWidth * relativeHeight < width) {
61213                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
61214             }
61215             if (viewBoxHeight * relativeWidth < height) {
61216                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
61217             }
61218
61219             size = 1 / Math.min(viewBoxWidth, relativeHeight);
61220
61221             me.viewBoxShift = {
61222                 dx: -viewBoxX,
61223                 dy: -viewBoxY,
61224                 scale: size
61225             };
61226         }
61227     },
61228
61229     transformToViewBox: function (x, y) {
61230         if (this.viewBoxShift) {
61231             var me = this, shift = me.viewBoxShift;
61232             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
61233         } else {
61234             return [x, y];
61235         }
61236     },
61237
61238     // @private
61239     applyTransformations: function(sprite) {
61240             sprite.bbox.transform = 0;
61241             this.transform(sprite);
61242
61243         var me = this,
61244             dirty = false,
61245             attr = sprite.attr;
61246
61247         if (attr.translation.x != null || attr.translation.y != null) {
61248             me.translate(sprite);
61249             dirty = true;
61250         }
61251         if (attr.scaling.x != null || attr.scaling.y != null) {
61252             me.scale(sprite);
61253             dirty = true;
61254         }
61255         if (attr.rotation.degrees != null) {
61256             me.rotate(sprite);
61257             dirty = true;
61258         }
61259         if (dirty) {
61260             sprite.bbox.transform = 0;
61261             this.transform(sprite);
61262             sprite.transformations = [];
61263         }
61264     },
61265
61266     // @private
61267     rotate: function (sprite) {
61268         var bbox,
61269             deg = sprite.attr.rotation.degrees,
61270             centerX = sprite.attr.rotation.x,
61271             centerY = sprite.attr.rotation.y;
61272         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61273             bbox = this.getBBox(sprite);
61274             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61275             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61276         }
61277         sprite.transformations.push({
61278             type: "rotate",
61279             degrees: deg,
61280             x: centerX,
61281             y: centerY
61282         });
61283     },
61284
61285     // @private
61286     translate: function(sprite) {
61287         var x = sprite.attr.translation.x || 0,
61288             y = sprite.attr.translation.y || 0;
61289         sprite.transformations.push({
61290             type: "translate",
61291             x: x,
61292             y: y
61293         });
61294     },
61295
61296     // @private
61297     scale: function(sprite) {
61298         var bbox,
61299             x = sprite.attr.scaling.x || 1,
61300             y = sprite.attr.scaling.y || 1,
61301             centerX = sprite.attr.scaling.centerX,
61302             centerY = sprite.attr.scaling.centerY;
61303
61304         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61305             bbox = this.getBBox(sprite);
61306             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61307             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61308         }
61309         sprite.transformations.push({
61310             type: "scale",
61311             x: x,
61312             y: y,
61313             centerX: centerX,
61314             centerY: centerY
61315         });
61316     },
61317
61318     // @private
61319     rectPath: function (x, y, w, h, r) {
61320         if (r) {
61321             return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
61322         }
61323         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
61324     },
61325
61326     // @private
61327     ellipsePath: function (x, y, rx, ry) {
61328         if (ry == null) {
61329             ry = rx;
61330         }
61331         return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
61332     },
61333
61334     // @private
61335     getPathpath: function (el) {
61336         return el.attr.path;
61337     },
61338
61339     // @private
61340     getPathcircle: function (el) {
61341         var a = el.attr;
61342         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
61343     },
61344
61345     // @private
61346     getPathellipse: function (el) {
61347         var a = el.attr;
61348         return this.ellipsePath(a.x, a.y,
61349                                 a.radiusX || (a.width / 2) || 0,
61350                                 a.radiusY || (a.height / 2) || 0);
61351     },
61352
61353     // @private
61354     getPathrect: function (el) {
61355         var a = el.attr;
61356         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
61357     },
61358
61359     // @private
61360     getPathimage: function (el) {
61361         var a = el.attr;
61362         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
61363     },
61364
61365     // @private
61366     getPathtext: function (el) {
61367         var bbox = this.getBBoxText(el);
61368         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
61369     },
61370
61371     createGroup: function(id) {
61372         var group = this.groups.get(id);
61373         if (!group) {
61374             group = Ext.create('Ext.draw.CompositeSprite', {
61375                 surface: this
61376             });
61377             group.id = id || Ext.id(null, 'ext-surface-group-');
61378             this.groups.add(group);
61379         }
61380         return group;
61381     },
61382
61383     /**
61384      * Returns a new group or an existent group associated with the current surface.
61385      * The group returned is a {@link Ext.draw.CompositeSprite} group.
61386      *
61387      * For example:
61388      *
61389      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
61390      *
61391      * @param {String} id The unique identifier of the group.
61392      * @return {Object} The {@link Ext.draw.CompositeSprite}.
61393      */
61394     getGroup: function(id) {
61395         if (typeof id == "string") {
61396             var group = this.groups.get(id);
61397             if (!group) {
61398                 group = this.createGroup(id);
61399             }
61400         } else {
61401             group = id;
61402         }
61403         return group;
61404     },
61405
61406     // @private
61407     prepareItems: function(items, applyDefaults) {
61408         items = [].concat(items);
61409         // Make sure defaults are applied and item is initialized
61410         var item, i, ln;
61411         for (i = 0, ln = items.length; i < ln; i++) {
61412             item = items[i];
61413             if (!(item instanceof Ext.draw.Sprite)) {
61414                 // Temporary, just take in configs...
61415                 item.surface = this;
61416                 items[i] = this.createItem(item);
61417             } else {
61418                 item.surface = this;
61419             }
61420         }
61421         return items;
61422     },
61423
61424     /**
61425      * Changes the text in the sprite element. The sprite must be a `text` sprite.
61426      * This method can also be called from {@link Ext.draw.Sprite}.
61427      *
61428      * For example:
61429      *
61430      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
61431      *
61432      * @param {Object} sprite The Sprite to change the text.
61433      * @param {String} text The new text to be set.
61434      * @method
61435      */
61436     setText: Ext.emptyFn,
61437
61438     //@private Creates an item and appends it to the surface. Called
61439     //as an internal method when calling `add`.
61440     createItem: Ext.emptyFn,
61441
61442     /**
61443      * Retrieves the id of this component.
61444      * Will autogenerate an id if one has not already been set.
61445      */
61446     getId: function() {
61447         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
61448     },
61449
61450     /**
61451      * Destroys the surface. This is done by removing all components from it and
61452      * also removing its reference to a DOM element.
61453      *
61454      * For example:
61455      *
61456      *      drawComponent.surface.destroy();
61457      */
61458     destroy: function() {
61459         delete this.domRef;
61460         this.removeAll();
61461     }
61462 });
61463 /**
61464  * @class Ext.layout.component.Draw
61465  * @extends Ext.layout.component.Component
61466  * @private
61467  *
61468  */
61469
61470 Ext.define('Ext.layout.component.Draw', {
61471
61472     /* Begin Definitions */
61473
61474     alias: 'layout.draw',
61475
61476     extend: 'Ext.layout.component.Auto',
61477
61478     /* End Definitions */
61479
61480     type: 'draw',
61481
61482     onLayout : function(width, height) {
61483         this.owner.surface.setSize(width, height);
61484         this.callParent(arguments);
61485     }
61486 });
61487 /**
61488  * @class Ext.draw.Component
61489  * @extends Ext.Component
61490  *
61491  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
61492  * manages and holds a `Surface` instance: an interface that has
61493  * an SVG or VML implementation depending on the browser capabilities and where
61494  * Sprites can be appended.
61495  *
61496  * One way to create a draw component is:
61497  *
61498  *     @example
61499  *     var drawComponent = Ext.create('Ext.draw.Component', {
61500  *         viewBox: false,
61501  *         items: [{
61502  *             type: 'circle',
61503  *             fill: '#79BB3F',
61504  *             radius: 100,
61505  *             x: 100,
61506  *             y: 100
61507  *         }]
61508  *     });
61509  *
61510  *     Ext.create('Ext.Window', {
61511  *         width: 215,
61512  *         height: 235,
61513  *         layout: 'fit',
61514  *         items: [drawComponent]
61515  *     }).show();
61516  *
61517  * In this case we created a draw component and added a sprite to it.
61518  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
61519  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
61520  * dimensions accordingly.
61521  *
61522  * You can also add sprites by using the surface's add method:
61523  *
61524  *     drawComponent.surface.add({
61525  *         type: 'circle',
61526  *         fill: '#79BB3F',
61527  *         radius: 100,
61528  *         x: 100,
61529  *         y: 100
61530  *     });
61531  *
61532  * For more information on Sprites, the core elements added to a draw component's surface,
61533  * refer to the Ext.draw.Sprite documentation.
61534  */
61535 Ext.define('Ext.draw.Component', {
61536
61537     /* Begin Definitions */
61538
61539     alias: 'widget.draw',
61540
61541     extend: 'Ext.Component',
61542
61543     requires: [
61544         'Ext.draw.Surface',
61545         'Ext.layout.component.Draw'
61546     ],
61547
61548     /* End Definitions */
61549
61550     /**
61551      * @cfg {String[]} enginePriority
61552      * Defines the priority order for which Surface implementation to use. The first
61553      * one supported by the current environment will be used.
61554      */
61555     enginePriority: ['Svg', 'Vml'],
61556
61557     baseCls: Ext.baseCSSPrefix + 'surface',
61558
61559     componentLayout: 'draw',
61560
61561     /**
61562      * @cfg {Boolean} viewBox
61563      * Turn on view box support which will scale and position items in the draw component to fit to the component while
61564      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
61565      */
61566     viewBox: true,
61567
61568     /**
61569      * @cfg {Boolean} autoSize
61570      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
61571      */
61572     autoSize: false,
61573
61574     /**
61575      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
61576      * The gradients array is an array of objects with the following properties:
61577      *
61578      *  - `id` - string - The unique name of the gradient.
61579      *  - `angle` - number, optional - The angle of the gradient in degrees.
61580      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
61581      *
61582      * ## Example
61583      *
61584      *     gradients: [{
61585      *         id: 'gradientId',
61586      *         angle: 45,
61587      *         stops: {
61588      *             0: {
61589      *                 color: '#555'
61590      *             },
61591      *             100: {
61592      *                 color: '#ddd'
61593      *             }
61594      *         }
61595      *     }, {
61596      *         id: 'gradientId2',
61597      *         angle: 0,
61598      *         stops: {
61599      *             0: {
61600      *                 color: '#590'
61601      *             },
61602      *             20: {
61603      *                 color: '#599'
61604      *             },
61605      *             100: {
61606      *                 color: '#ddd'
61607      *             }
61608      *         }
61609      *     }]
61610      *
61611      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
61612      *
61613      *     sprite.setAttributes({
61614      *         fill: 'url(#gradientId)'
61615      *     }, true);
61616      */
61617     initComponent: function() {
61618         this.callParent(arguments);
61619
61620         this.addEvents(
61621             'mousedown',
61622             'mouseup',
61623             'mousemove',
61624             'mouseenter',
61625             'mouseleave',
61626             'click'
61627         );
61628     },
61629
61630     /**
61631      * @private
61632      *
61633      * Create the Surface on initial render
61634      */
61635     onRender: function() {
61636         var me = this,
61637             viewBox = me.viewBox,
61638             autoSize = me.autoSize,
61639             bbox, items, width, height, x, y;
61640         me.callParent(arguments);
61641
61642         if (me.createSurface() !== false) {
61643             items = me.surface.items;
61644
61645             if (viewBox || autoSize) {
61646                 bbox = items.getBBox();
61647                 width = bbox.width;
61648                 height = bbox.height;
61649                 x = bbox.x;
61650                 y = bbox.y;
61651                 if (me.viewBox) {
61652                     me.surface.setViewBox(x, y, width, height);
61653                 }
61654                 else {
61655                     // AutoSized
61656                     me.autoSizeSurface();
61657                 }
61658             }
61659         }
61660     },
61661
61662     //@private
61663     autoSizeSurface: function() {
61664         var me = this,
61665             items = me.surface.items,
61666             bbox = items.getBBox(),
61667             width = bbox.width,
61668             height = bbox.height;
61669         items.setAttributes({
61670             translate: {
61671                 x: -bbox.x,
61672                 //Opera has a slight offset in the y axis.
61673                 y: -bbox.y + (+Ext.isOpera)
61674             }
61675         }, true);
61676         if (me.rendered) {
61677             me.setSize(width, height);
61678             me.surface.setSize(width, height);
61679         }
61680         else {
61681             me.surface.setSize(width, height);
61682         }
61683         me.el.setSize(width, height);
61684     },
61685
61686     /**
61687      * Create the Surface instance. Resolves the correct Surface implementation to
61688      * instantiate based on the 'enginePriority' config. Once the Surface instance is
61689      * created you can use the handle to that instance to add sprites. For example:
61690      *
61691      *     drawComponent.surface.add(sprite);
61692      */
61693     createSurface: function() {
61694         var surface = Ext.draw.Surface.create(Ext.apply({}, {
61695                 width: this.width,
61696                 height: this.height,
61697                 renderTo: this.el
61698             }, this.initialConfig));
61699         if (!surface) {
61700             // In case we cannot create a surface, return false so we can stop
61701             return false;
61702         }
61703         this.surface = surface;
61704
61705
61706         function refire(eventName) {
61707             return function(e) {
61708                 this.fireEvent(eventName, e);
61709             };
61710         }
61711
61712         surface.on({
61713             scope: this,
61714             mouseup: refire('mouseup'),
61715             mousedown: refire('mousedown'),
61716             mousemove: refire('mousemove'),
61717             mouseenter: refire('mouseenter'),
61718             mouseleave: refire('mouseleave'),
61719             click: refire('click')
61720         });
61721     },
61722
61723
61724     /**
61725      * @private
61726      *
61727      * Clean up the Surface instance on component destruction
61728      */
61729     onDestroy: function() {
61730         var surface = this.surface;
61731         if (surface) {
61732             surface.destroy();
61733         }
61734         this.callParent(arguments);
61735     }
61736
61737 });
61738
61739 /**
61740  * @class Ext.chart.LegendItem
61741  * @extends Ext.draw.CompositeSprite
61742  * A single item of a legend (marker plus label)
61743  */
61744 Ext.define('Ext.chart.LegendItem', {
61745
61746     /* Begin Definitions */
61747
61748     extend: 'Ext.draw.CompositeSprite',
61749
61750     requires: ['Ext.chart.Shape'],
61751
61752     /* End Definitions */
61753
61754     // Position of the item, relative to the upper-left corner of the legend box
61755     x: 0,
61756     y: 0,
61757     zIndex: 500,
61758
61759     constructor: function(config) {
61760         this.callParent(arguments);
61761         this.createLegend(config);
61762     },
61763
61764     /**
61765      * Creates all the individual sprites for this legend item
61766      */
61767     createLegend: function(config) {
61768         var me = this,
61769             index = config.yFieldIndex,
61770             series = me.series,
61771             seriesType = series.type,
61772             idx = me.yFieldIndex,
61773             legend = me.legend,
61774             surface = me.surface,
61775             refX = legend.x + me.x,
61776             refY = legend.y + me.y,
61777             bbox, z = me.zIndex,
61778             markerConfig, label, mask,
61779             radius, toggle = false,
61780             seriesStyle = Ext.apply(series.seriesStyle, series.style);
61781
61782         function getSeriesProp(name) {
61783             var val = series[name];
61784             return (Ext.isArray(val) ? val[idx] : val);
61785         }
61786         
61787         label = me.add('label', surface.add({
61788             type: 'text',
61789             x: 20,
61790             y: 0,
61791             zIndex: z || 0,
61792             font: legend.labelFont,
61793             text: getSeriesProp('title') || getSeriesProp('yField')
61794         }));
61795
61796         // Line series - display as short line with optional marker in the middle
61797         if (seriesType === 'line' || seriesType === 'scatter') {
61798             if(seriesType === 'line') {
61799                 me.add('line', surface.add({
61800                     type: 'path',
61801                     path: 'M0.5,0.5L16.5,0.5',
61802                     zIndex: z,
61803                     "stroke-width": series.lineWidth,
61804                     "stroke-linejoin": "round",
61805                     "stroke-dasharray": series.dash,
61806                     stroke: seriesStyle.stroke || '#000',
61807                     style: {
61808                         cursor: 'pointer'
61809                     }
61810                 }));
61811             }
61812             if (series.showMarkers || seriesType === 'scatter') {
61813                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
61814                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
61815                     fill: markerConfig.fill,
61816                     x: 8.5,
61817                     y: 0.5,
61818                     zIndex: z,
61819                     radius: markerConfig.radius || markerConfig.size,
61820                     style: {
61821                         cursor: 'pointer'
61822                     }
61823                 }));
61824             }
61825         }
61826         // All other series types - display as filled box
61827         else {
61828             me.add('box', surface.add({
61829                 type: 'rect',
61830                 zIndex: z,
61831                 x: 0,
61832                 y: 0,
61833                 width: 12,
61834                 height: 12,
61835                 fill: series.getLegendColor(index),
61836                 style: {
61837                     cursor: 'pointer'
61838                 }
61839             }));
61840         }
61841         
61842         me.setAttributes({
61843             hidden: false
61844         }, true);
61845         
61846         bbox = me.getBBox();
61847         
61848         mask = me.add('mask', surface.add({
61849             type: 'rect',
61850             x: bbox.x,
61851             y: bbox.y,
61852             width: bbox.width || 20,
61853             height: bbox.height || 20,
61854             zIndex: (z || 0) + 1000,
61855             fill: '#f00',
61856             opacity: 0,
61857             style: {
61858                 'cursor': 'pointer'
61859             }
61860         }));
61861
61862         //add toggle listener
61863         me.on('mouseover', function() {
61864             label.setStyle({
61865                 'font-weight': 'bold'
61866             });
61867             mask.setStyle({
61868                 'cursor': 'pointer'
61869             });
61870             series._index = index;
61871             series.highlightItem();
61872         }, me);
61873
61874         me.on('mouseout', function() {
61875             label.setStyle({
61876                 'font-weight': 'normal'
61877             });
61878             series._index = index;
61879             series.unHighlightItem();
61880         }, me);
61881         
61882         if (!series.visibleInLegend(index)) {
61883             toggle = true;
61884             label.setAttributes({
61885                opacity: 0.5
61886             }, true);
61887         }
61888
61889         me.on('mousedown', function() {
61890             if (!toggle) {
61891                 series.hideAll();
61892                 label.setAttributes({
61893                     opacity: 0.5
61894                 }, true);
61895             } else {
61896                 series.showAll();
61897                 label.setAttributes({
61898                     opacity: 1
61899                 }, true);
61900             }
61901             toggle = !toggle;
61902         }, me);
61903         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
61904     },
61905
61906     /**
61907      * Update the positions of all this item's sprites to match the root position
61908      * of the legend box.
61909      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
61910      *                 as the reference point for the relative positioning. Defaults to the Legend.
61911      */
61912     updatePosition: function(relativeTo) {
61913         var me = this,
61914             items = me.items,
61915             ln = items.length,
61916             i = 0,
61917             item;
61918         if (!relativeTo) {
61919             relativeTo = me.legend;
61920         }
61921         for (; i < ln; i++) {
61922             item = items[i];
61923             switch (item.type) {
61924                 case 'text':
61925                     item.setAttributes({
61926                         x: 20 + relativeTo.x + me.x,
61927                         y: relativeTo.y + me.y
61928                     }, true);
61929                     break;
61930                 case 'rect':
61931                     item.setAttributes({
61932                         translate: {
61933                             x: relativeTo.x + me.x,
61934                             y: relativeTo.y + me.y - 6
61935                         }
61936                     }, true);
61937                     break;
61938                 default:
61939                     item.setAttributes({
61940                         translate: {
61941                             x: relativeTo.x + me.x,
61942                             y: relativeTo.y + me.y
61943                         }
61944                     }, true);
61945             }
61946         }
61947     }
61948 });
61949
61950 /**
61951  * @class Ext.chart.Legend
61952  *
61953  * Defines a legend for a chart's series.
61954  * The 'chart' member must be set prior to rendering.
61955  * The legend class displays a list of legend items each of them related with a
61956  * series being rendered. In order to render the legend item of the proper series
61957  * the series configuration object must have `showInSeries` set to true.
61958  *
61959  * The legend configuration object accepts a `position` as parameter.
61960  * The `position` parameter can be `left`, `right`
61961  * `top` or `bottom`. For example:
61962  *
61963  *     legend: {
61964  *         position: 'right'
61965  *     },
61966  *
61967  * ## Example
61968  *
61969  *     @example
61970  *     var store = Ext.create('Ext.data.JsonStore', {
61971  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61972  *         data: [
61973  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
61974  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
61975  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
61976  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
61977  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
61978  *         ]
61979  *     });
61980  *
61981  *     Ext.create('Ext.chart.Chart', {
61982  *         renderTo: Ext.getBody(),
61983  *         width: 500,
61984  *         height: 300,
61985  *         animate: true,
61986  *         store: store,
61987  *         shadow: true,
61988  *         theme: 'Category1',
61989  *         legend: {
61990  *             position: 'top'
61991  *         },
61992  *         axes: [
61993  *             {
61994  *                 type: 'Numeric',
61995  *                 grid: true,
61996  *                 position: 'left',
61997  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61998  *                 title: 'Sample Values',
61999  *                 grid: {
62000  *                     odd: {
62001  *                         opacity: 1,
62002  *                         fill: '#ddd',
62003  *                         stroke: '#bbb',
62004  *                         'stroke-width': 1
62005  *                     }
62006  *                 },
62007  *                 minimum: 0,
62008  *                 adjustMinimumByMajorUnit: 0
62009  *             },
62010  *             {
62011  *                 type: 'Category',
62012  *                 position: 'bottom',
62013  *                 fields: ['name'],
62014  *                 title: 'Sample Metrics',
62015  *                 grid: true,
62016  *                 label: {
62017  *                     rotate: {
62018  *                         degrees: 315
62019  *                     }
62020  *                 }
62021  *             }
62022  *         ],
62023  *         series: [{
62024  *             type: 'area',
62025  *             highlight: false,
62026  *             axis: 'left',
62027  *             xField: 'name',
62028  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
62029  *             style: {
62030  *                 opacity: 0.93
62031  *             }
62032  *         }]
62033  *     });
62034  */
62035 Ext.define('Ext.chart.Legend', {
62036
62037     /* Begin Definitions */
62038
62039     requires: ['Ext.chart.LegendItem'],
62040
62041     /* End Definitions */
62042
62043     /**
62044      * @cfg {Boolean} visible
62045      * Whether or not the legend should be displayed.
62046      */
62047     visible: true,
62048
62049     /**
62050      * @cfg {String} position
62051      * The position of the legend in relation to the chart. One of: "top",
62052      * "bottom", "left", "right", or "float". If set to "float", then the legend
62053      * box will be positioned at the point denoted by the x and y parameters.
62054      */
62055     position: 'bottom',
62056
62057     /**
62058      * @cfg {Number} x
62059      * X-position of the legend box. Used directly if position is set to "float", otherwise
62060      * it will be calculated dynamically.
62061      */
62062     x: 0,
62063
62064     /**
62065      * @cfg {Number} y
62066      * Y-position of the legend box. Used directly if position is set to "float", otherwise
62067      * it will be calculated dynamically.
62068      */
62069     y: 0,
62070
62071     /**
62072      * @cfg {String} labelFont
62073      * Font to be used for the legend labels, eg '12px Helvetica'
62074      */
62075     labelFont: '12px Helvetica, sans-serif',
62076
62077     /**
62078      * @cfg {String} boxStroke
62079      * Style of the stroke for the legend box
62080      */
62081     boxStroke: '#000',
62082
62083     /**
62084      * @cfg {String} boxStrokeWidth
62085      * Width of the stroke for the legend box
62086      */
62087     boxStrokeWidth: 1,
62088
62089     /**
62090      * @cfg {String} boxFill
62091      * Fill style for the legend box
62092      */
62093     boxFill: '#FFF',
62094
62095     /**
62096      * @cfg {Number} itemSpacing
62097      * Amount of space between legend items
62098      */
62099     itemSpacing: 10,
62100
62101     /**
62102      * @cfg {Number} padding
62103      * Amount of padding between the legend box's border and its items
62104      */
62105     padding: 5,
62106
62107     // @private
62108     width: 0,
62109     // @private
62110     height: 0,
62111
62112     /**
62113      * @cfg {Number} boxZIndex
62114      * Sets the z-index for the legend. Defaults to 100.
62115      */
62116     boxZIndex: 100,
62117
62118     /**
62119      * Creates new Legend.
62120      * @param {Object} config  (optional) Config object.
62121      */
62122     constructor: function(config) {
62123         var me = this;
62124         if (config) {
62125             Ext.apply(me, config);
62126         }
62127         me.items = [];
62128         /**
62129          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
62130          * @type {Boolean}
62131          */
62132         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
62133
62134         // cache these here since they may get modified later on
62135         me.origX = me.x;
62136         me.origY = me.y;
62137     },
62138
62139     /**
62140      * @private Create all the sprites for the legend
62141      */
62142     create: function() {
62143         var me = this;
62144         me.createBox();
62145         me.createItems();
62146         if (!me.created && me.isDisplayed()) {
62147             me.created = true;
62148
62149             // Listen for changes to series titles to trigger regeneration of the legend
62150             me.chart.series.each(function(series) {
62151                 series.on('titlechange', function() {
62152                     me.create();
62153                     me.updatePosition();
62154                 });
62155             });
62156         }
62157     },
62158
62159     /**
62160      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
62161      * and also the 'showInLegend' config for each of the series.
62162      */
62163     isDisplayed: function() {
62164         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
62165     },
62166
62167     /**
62168      * @private Create the series markers and labels
62169      */
62170     createItems: function() {
62171         var me = this,
62172             chart = me.chart,
62173             surface = chart.surface,
62174             items = me.items,
62175             padding = me.padding,
62176             itemSpacing = me.itemSpacing,
62177             spacingOffset = 2,
62178             maxWidth = 0,
62179             maxHeight = 0,
62180             totalWidth = 0,
62181             totalHeight = 0,
62182             vertical = me.isVertical,
62183             math = Math,
62184             mfloor = math.floor,
62185             mmax = math.max,
62186             index = 0,
62187             i = 0,
62188             len = items ? items.length : 0,
62189             x, y, spacing, item, bbox, height, width;
62190
62191         //remove all legend items
62192         if (len) {
62193             for (; i < len; i++) {
62194                 items[i].destroy();
62195             }
62196         }
62197         //empty array
62198         items.length = [];
62199         // Create all the item labels, collecting their dimensions and positioning each one
62200         // properly in relation to the previous item
62201         chart.series.each(function(series, i) {
62202             if (series.showInLegend) {
62203                 Ext.each([].concat(series.yField), function(field, j) {
62204                     item = Ext.create('Ext.chart.LegendItem', {
62205                         legend: this,
62206                         series: series,
62207                         surface: chart.surface,
62208                         yFieldIndex: j
62209                     });
62210                     bbox = item.getBBox();
62211
62212                     //always measure from x=0, since not all markers go all the way to the left
62213                     width = bbox.width;
62214                     height = bbox.height;
62215
62216                     if (i + j === 0) {
62217                         spacing = vertical ? padding + height / 2 : padding;
62218                     }
62219                     else {
62220                         spacing = itemSpacing / (vertical ? 2 : 1);
62221                     }
62222                     // Set the item's position relative to the legend box
62223                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
62224                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
62225
62226                     // Collect cumulative dimensions
62227                     totalWidth += width + spacing;
62228                     totalHeight += height + spacing;
62229                     maxWidth = mmax(maxWidth, width);
62230                     maxHeight = mmax(maxHeight, height);
62231
62232                     items.push(item);
62233                 }, this);
62234             }
62235         }, me);
62236
62237         // Store the collected dimensions for later
62238         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
62239         if (vertical && items.length === 1) {
62240             spacingOffset = 1;
62241         }
62242         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
62243         me.itemHeight = maxHeight;
62244     },
62245
62246     /**
62247      * @private Get the bounds for the legend's outer box
62248      */
62249     getBBox: function() {
62250         var me = this;
62251         return {
62252             x: Math.round(me.x) - me.boxStrokeWidth / 2,
62253             y: Math.round(me.y) - me.boxStrokeWidth / 2,
62254             width: me.width,
62255             height: me.height
62256         };
62257     },
62258
62259     /**
62260      * @private Create the box around the legend items
62261      */
62262     createBox: function() {
62263         var me = this,
62264             box;
62265
62266         if (me.boxSprite) {
62267             me.boxSprite.destroy();
62268         }
62269         
62270         box = me.boxSprite = me.chart.surface.add(Ext.apply({
62271             type: 'rect',
62272             stroke: me.boxStroke,
62273             "stroke-width": me.boxStrokeWidth,
62274             fill: me.boxFill,
62275             zIndex: me.boxZIndex
62276         }, me.getBBox()));
62277
62278         box.redraw();
62279     },
62280
62281     /**
62282      * @private Update the position of all the legend's sprites to match its current x/y values
62283      */
62284     updatePosition: function() {
62285         var me = this,
62286             x, y,
62287             legendWidth = me.width,
62288             legendHeight = me.height,
62289             padding = me.padding,
62290             chart = me.chart,
62291             chartBBox = chart.chartBBox,
62292             insets = chart.insetPadding,
62293             chartWidth = chartBBox.width - (insets * 2),
62294             chartHeight = chartBBox.height - (insets * 2),
62295             chartX = chartBBox.x + insets,
62296             chartY = chartBBox.y + insets,
62297             surface = chart.surface,
62298             mfloor = Math.floor;
62299
62300         if (me.isDisplayed()) {
62301             // Find the position based on the dimensions
62302             switch(me.position) {
62303                 case "left":
62304                     x = insets;
62305                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62306                     break;
62307                 case "right":
62308                     x = mfloor(surface.width - legendWidth) - insets;
62309                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62310                     break;
62311                 case "top":
62312                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62313                     y = insets;
62314                     break;
62315                 case "bottom":
62316                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62317                     y = mfloor(surface.height - legendHeight) - insets;
62318                     break;
62319                 default:
62320                     x = mfloor(me.origX) + insets;
62321                     y = mfloor(me.origY) + insets;
62322             }
62323             me.x = x;
62324             me.y = y;
62325
62326             // Update the position of each item
62327             Ext.each(me.items, function(item) {
62328                 item.updatePosition();
62329             });
62330             // Update the position of the outer box
62331             me.boxSprite.setAttributes(me.getBBox(), true);
62332         }
62333     }
62334 });
62335
62336 /**
62337  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
62338  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
62339  * updates its display whenever data in the Store changes. In addition, the look and feel
62340  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
62341  * 
62342  * ## Creating a Simple Chart
62343  * 
62344  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
62345  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
62346  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
62347  * 
62348  * ### 1. Creating a Store
62349  * 
62350  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
62351  * data that will be displayed in the Chart. For example the data for a chart that displays
62352  * a weather forecast could be represented as a series of "WeatherPoint" data points with
62353  * two fields - "temperature", and "date":
62354  * 
62355  *     Ext.define('WeatherPoint', {
62356  *         extend: 'Ext.data.Model',
62357  *         fields: ['temperature', 'date']
62358  *     });
62359  * 
62360  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
62361  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
62362  * 
62363  *     var store = Ext.create('Ext.data.Store', {
62364  *         model: 'WeatherPoint',
62365  *         data: [
62366  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
62367  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
62368  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
62369  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
62370  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
62371  *         ]
62372  *     });
62373  *    
62374  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
62375  * 
62376  * ### 2. Creating the Chart object
62377  * 
62378  * Now that a Store has been created it can be used in a Chart:
62379  * 
62380  *     Ext.create('Ext.chart.Chart', {
62381  *        renderTo: Ext.getBody(),
62382  *        width: 400,
62383  *        height: 300,
62384  *        store: store
62385  *     });
62386  *    
62387  * That's all it takes to create a Chart instance that is backed by a Store.
62388  * However, if the above code is run in a browser, a blank screen will be displayed.
62389  * This is because the two pieces that are responsible for the visual display,
62390  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
62391  * 
62392  * ### 3. Configuring the Axes
62393  * 
62394  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
62395  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
62396  * 
62397  *     Ext.create('Ext.chart.Chart', {
62398  *         ...
62399  *         axes: [
62400  *             {
62401  *                 title: 'Temperature',
62402  *                 type: 'Numeric',
62403  *                 position: 'left',
62404  *                 fields: ['temperature'],
62405  *                 minimum: 0,
62406  *                 maximum: 100
62407  *             },
62408  *             {
62409  *                 title: 'Time',
62410  *                 type: 'Time',
62411  *                 position: 'bottom',
62412  *                 fields: ['date'],
62413  *                 dateFormat: 'ga'
62414  *             }
62415  *         ]
62416  *     });
62417  *    
62418  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
62419  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
62420  * defined above. The minimum value for this axis is "0", and the maximum is "100".
62421  * 
62422  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
62423  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
62424  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
62425  * configuration tells the Time Axis how to format it's labels.
62426  * 
62427  * Here's what the Chart looks like now that it has its Axes configured:
62428  * 
62429  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
62430  * 
62431  * ### 4. Configuring the Series
62432  * 
62433  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
62434  * Series are responsible for the visual representation of the data points contained in the Store.
62435  * This example only has one Series:
62436  * 
62437  *     Ext.create('Ext.chart.Chart', {
62438  *         ...
62439  *         axes: [
62440  *             ...
62441  *         ],
62442  *         series: [
62443  *             {
62444  *                 type: 'line',
62445  *                 xField: 'date',
62446  *                 yField: 'temperature'
62447  *             }
62448  *         ]
62449  *     });
62450  *     
62451  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
62452  * from the "WeatherPoint" Models in the Store to plot its data points:
62453  * 
62454  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
62455  * 
62456  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
62457  * 
62458  * ## Themes
62459  * 
62460  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
62461  * 
62462  *     Ext.create('Ext.chart.Chart', {
62463  *         ...
62464  *         theme: 'Green',
62465  *         ...
62466  *     });
62467  * 
62468  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
62469  * 
62470  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
62471  * 
62472  */
62473 Ext.define('Ext.chart.Chart', {
62474
62475     /* Begin Definitions */
62476
62477     alias: 'widget.chart',
62478
62479     extend: 'Ext.draw.Component',
62480     
62481     mixins: {
62482         themeManager: 'Ext.chart.theme.Theme',
62483         mask: 'Ext.chart.Mask',
62484         navigation: 'Ext.chart.Navigation'
62485     },
62486
62487     requires: [
62488         'Ext.util.MixedCollection',
62489         'Ext.data.StoreManager',
62490         'Ext.chart.Legend',
62491         'Ext.util.DelayedTask'
62492     ],
62493
62494     /* End Definitions */
62495
62496     // @private
62497     viewBox: false,
62498
62499     /**
62500      * @cfg {String} theme
62501      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
62502      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
62503      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
62504      * is 'Base'.
62505      */
62506
62507     /**
62508      * @cfg {Boolean/Object} animate
62509      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
62510      * object to be used for default chart animations. Defaults to false.
62511      */
62512     animate: false,
62513
62514     /**
62515      * @cfg {Boolean/Object} legend
62516      * True for the default legend display or a legend config object. Defaults to false.
62517      */
62518     legend: false,
62519
62520     /**
62521      * @cfg {Number} insetPadding
62522      * The amount of inset padding in pixels for the chart. Defaults to 10.
62523      */
62524     insetPadding: 10,
62525
62526     /**
62527      * @cfg {String[]} enginePriority
62528      * Defines the priority order for which Surface implementation to use. The first one supported by the current
62529      * environment will be used. Defaults to `['Svg', 'Vml']`.
62530      */
62531     enginePriority: ['Svg', 'Vml'],
62532
62533     /**
62534      * @cfg {Object/Boolean} background
62535      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
62536      * background. For example, if `background` were to be a color we could set the object as
62537      *
62538      *     background: {
62539      *         //color string
62540      *         fill: '#ccc'
62541      *     }
62542      *
62543      * You can specify an image by using:
62544      *
62545      *     background: {
62546      *         image: 'http://path.to.image/'
62547      *     }
62548      *
62549      * Also you can specify a gradient by using the gradient object syntax:
62550      *
62551      *     background: {
62552      *         gradient: {
62553      *             id: 'gradientId',
62554      *             angle: 45,
62555      *             stops: {
62556      *                 0: {
62557      *                     color: '#555'
62558      *                 }
62559      *                 100: {
62560      *                     color: '#ddd'
62561      *                 }
62562      *             }
62563      *         }
62564      *     }
62565      */
62566     background: false,
62567
62568     /**
62569      * @cfg {Object[]} gradients
62570      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
62571      * array of objects with the following properties:
62572      *
62573      * - **id** - string - The unique name of the gradient.
62574      * - **angle** - number, optional - The angle of the gradient in degrees.
62575      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
62576      *
62577      * For example:
62578      *
62579      *     gradients: [{
62580      *         id: 'gradientId',
62581      *         angle: 45,
62582      *         stops: {
62583      *             0: {
62584      *                 color: '#555'
62585      *             },
62586      *             100: {
62587      *                 color: '#ddd'
62588      *             }
62589      *         }
62590      *     }, {
62591      *         id: 'gradientId2',
62592      *         angle: 0,
62593      *         stops: {
62594      *             0: {
62595      *                 color: '#590'
62596      *             },
62597      *             20: {
62598      *                 color: '#599'
62599      *             },
62600      *             100: {
62601      *                 color: '#ddd'
62602      *             }
62603      *         }
62604      *     }]
62605      *
62606      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
62607      *
62608      *     sprite.setAttributes({
62609      *         fill: 'url(#gradientId)'
62610      *     }, true);
62611      */
62612
62613     /**
62614      * @cfg {Ext.data.Store} store
62615      * The store that supplies data to this chart.
62616      */
62617
62618     /**
62619      * @cfg {Ext.chart.series.Series[]} series
62620      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
62621      * 
62622      *     series: [{
62623      *         type: 'column',
62624      *         axis: 'left',
62625      *         listeners: {
62626      *             'afterrender': function() {
62627      *                 console('afterrender');
62628      *             }
62629      *         },
62630      *         xField: 'category',
62631      *         yField: 'data1'
62632      *     }]
62633      */
62634
62635     /**
62636      * @cfg {Ext.chart.axis.Axis[]} axes
62637      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
62638      * 
62639      *     axes: [{
62640      *         type: 'Numeric',
62641      *         position: 'left',
62642      *         fields: ['data1'],
62643      *         title: 'Number of Hits',
62644      *         minimum: 0,
62645      *         //one minor tick between two major ticks
62646      *         minorTickSteps: 1
62647      *     }, {
62648      *         type: 'Category',
62649      *         position: 'bottom',
62650      *         fields: ['name'],
62651      *         title: 'Month of the Year'
62652      *     }]
62653      */
62654
62655     constructor: function(config) {
62656         var me = this,
62657             defaultAnim;
62658             
62659         config = Ext.apply({}, config);
62660         me.initTheme(config.theme || me.theme);
62661         if (me.gradients) {
62662             Ext.apply(config, { gradients: me.gradients });
62663         }
62664         if (me.background) {
62665             Ext.apply(config, { background: me.background });
62666         }
62667         if (config.animate) {
62668             defaultAnim = {
62669                 easing: 'ease',
62670                 duration: 500
62671             };
62672             if (Ext.isObject(config.animate)) {
62673                 config.animate = Ext.applyIf(config.animate, defaultAnim);
62674             }
62675             else {
62676                 config.animate = defaultAnim;
62677             }
62678         }
62679         me.mixins.mask.constructor.call(me, config);
62680         me.mixins.navigation.constructor.call(me, config);
62681         me.callParent([config]);
62682     },
62683     
62684     getChartStore: function(){
62685         return this.substore || this.store;    
62686     },
62687
62688     initComponent: function() {
62689         var me = this,
62690             axes,
62691             series;
62692         me.callParent();
62693         me.addEvents(
62694             'itemmousedown',
62695             'itemmouseup',
62696             'itemmouseover',
62697             'itemmouseout',
62698             'itemclick',
62699             'itemdoubleclick',
62700             'itemdragstart',
62701             'itemdrag',
62702             'itemdragend',
62703             /**
62704              * @event beforerefresh
62705              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
62706              * {@link #refresh} action will be cancelled.
62707              * @param {Ext.chart.Chart} this
62708              */
62709             'beforerefresh',
62710             /**
62711              * @event refresh
62712              * Fires after the chart data has been refreshed.
62713              * @param {Ext.chart.Chart} this
62714              */
62715             'refresh'
62716         );
62717         Ext.applyIf(me, {
62718             zoom: {
62719                 width: 1,
62720                 height: 1,
62721                 x: 0,
62722                 y: 0
62723             }
62724         });
62725         me.maxGutter = [0, 0];
62726         me.store = Ext.data.StoreManager.lookup(me.store);
62727         axes = me.axes;
62728         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
62729         if (axes) {
62730             me.axes.addAll(axes);
62731         }
62732         series = me.series;
62733         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
62734         if (series) {
62735             me.series.addAll(series);
62736         }
62737         if (me.legend !== false) {
62738             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
62739         }
62740
62741         me.on({
62742             mousemove: me.onMouseMove,
62743             mouseleave: me.onMouseLeave,
62744             mousedown: me.onMouseDown,
62745             mouseup: me.onMouseUp,
62746             scope: me
62747         });
62748     },
62749
62750     // @private overrides the component method to set the correct dimensions to the chart.
62751     afterComponentLayout: function(width, height) {
62752         var me = this;
62753         if (Ext.isNumber(width) && Ext.isNumber(height)) {
62754             me.curWidth = width;
62755             me.curHeight = height;
62756             me.redraw(true);
62757         }
62758         this.callParent(arguments);
62759     },
62760
62761     /**
62762      * Redraws the chart. If animations are set this will animate the chart too. 
62763      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
62764      */
62765     redraw: function(resize) {
62766         var me = this,
62767             chartBBox = me.chartBBox = {
62768                 x: 0,
62769                 y: 0,
62770                 height: me.curHeight,
62771                 width: me.curWidth
62772             },
62773             legend = me.legend;
62774         me.surface.setSize(chartBBox.width, chartBBox.height);
62775         // Instantiate Series and Axes
62776         me.series.each(me.initializeSeries, me);
62777         me.axes.each(me.initializeAxis, me);
62778         //process all views (aggregated data etc) on stores
62779         //before rendering.
62780         me.axes.each(function(axis) {
62781             axis.processView();
62782         });
62783         me.axes.each(function(axis) {
62784             axis.drawAxis(true);
62785         });
62786
62787         // Create legend if not already created
62788         if (legend !== false) {
62789             legend.create();
62790         }
62791
62792         // Place axes properly, including influence from each other
62793         me.alignAxes();
62794
62795         // Reposition legend based on new axis alignment
62796         if (me.legend !== false) {
62797             legend.updatePosition();
62798         }
62799
62800         // Find the max gutter
62801         me.getMaxGutter();
62802
62803         // Draw axes and series
62804         me.resizing = !!resize;
62805
62806         me.axes.each(me.drawAxis, me);
62807         me.series.each(me.drawCharts, me);
62808         me.resizing = false;
62809     },
62810
62811     // @private set the store after rendering the chart.
62812     afterRender: function() {
62813         var ref,
62814             me = this;
62815         this.callParent();
62816
62817         if (me.categoryNames) {
62818             me.setCategoryNames(me.categoryNames);
62819         }
62820
62821         if (me.tipRenderer) {
62822             ref = me.getFunctionRef(me.tipRenderer);
62823             me.setTipRenderer(ref.fn, ref.scope);
62824         }
62825         me.bindStore(me.store, true);
62826         me.refresh();
62827     },
62828
62829     // @private get x and y position of the mouse cursor.
62830     getEventXY: function(e) {
62831         var me = this,
62832             box = this.surface.getRegion(),
62833             pageXY = e.getXY(),
62834             x = pageXY[0] - box.left,
62835             y = pageXY[1] - box.top;
62836         return [x, y];
62837     },
62838
62839     // @private wrap the mouse down position to delegate the event to the series.
62840     onClick: function(e) {
62841         var me = this,
62842             position = me.getEventXY(e),
62843             item;
62844
62845         // Ask each series if it has an item corresponding to (not necessarily exactly
62846         // on top of) the current mouse coords. Fire itemclick event.
62847         me.series.each(function(series) {
62848             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62849                 if (series.getItemForPoint) {
62850                     item = series.getItemForPoint(position[0], position[1]);
62851                     if (item) {
62852                         series.fireEvent('itemclick', item);
62853                     }
62854                 }
62855             }
62856         }, me);
62857     },
62858
62859     // @private wrap the mouse down position to delegate the event to the series.
62860     onMouseDown: function(e) {
62861         var me = this,
62862             position = me.getEventXY(e),
62863             item;
62864
62865         if (me.mask) {
62866             me.mixins.mask.onMouseDown.call(me, e);
62867         }
62868         // Ask each series if it has an item corresponding to (not necessarily exactly
62869         // on top of) the current mouse coords. Fire mousedown event.
62870         me.series.each(function(series) {
62871             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62872                 if (series.getItemForPoint) {
62873                     item = series.getItemForPoint(position[0], position[1]);
62874                     if (item) {
62875                         series.fireEvent('itemmousedown', item);
62876                     }
62877                 }
62878             }
62879         }, me);
62880     },
62881
62882     // @private wrap the mouse up event to delegate it to the series.
62883     onMouseUp: function(e) {
62884         var me = this,
62885             position = me.getEventXY(e),
62886             item;
62887
62888         if (me.mask) {
62889             me.mixins.mask.onMouseUp.call(me, e);
62890         }
62891         // Ask each series if it has an item corresponding to (not necessarily exactly
62892         // on top of) the current mouse coords. Fire mousedown event.
62893         me.series.each(function(series) {
62894             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62895                 if (series.getItemForPoint) {
62896                     item = series.getItemForPoint(position[0], position[1]);
62897                     if (item) {
62898                         series.fireEvent('itemmouseup', item);
62899                     }
62900                 }
62901             }
62902         }, me);
62903     },
62904
62905     // @private wrap the mouse move event so it can be delegated to the series.
62906     onMouseMove: function(e) {
62907         var me = this,
62908             position = me.getEventXY(e),
62909             item, last, storeItem, storeField;
62910
62911         if (me.mask) {
62912             me.mixins.mask.onMouseMove.call(me, e);
62913         }
62914         // Ask each series if it has an item corresponding to (not necessarily exactly
62915         // on top of) the current mouse coords. Fire itemmouseover/out events.
62916         me.series.each(function(series) {
62917             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62918                 if (series.getItemForPoint) {
62919                     item = series.getItemForPoint(position[0], position[1]);
62920                     last = series._lastItemForPoint;
62921                     storeItem = series._lastStoreItem;
62922                     storeField = series._lastStoreField;
62923
62924
62925                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
62926                         if (last) {
62927                             series.fireEvent('itemmouseout', last);
62928                             delete series._lastItemForPoint;
62929                             delete series._lastStoreField;
62930                             delete series._lastStoreItem;
62931                         }
62932                         if (item) {
62933                             series.fireEvent('itemmouseover', item);
62934                             series._lastItemForPoint = item;
62935                             series._lastStoreItem = item.storeItem;
62936                             series._lastStoreField = item.storeField;
62937                         }
62938                     }
62939                 }
62940             } else {
62941                 last = series._lastItemForPoint;
62942                 if (last) {
62943                     series.fireEvent('itemmouseout', last);
62944                     delete series._lastItemForPoint;
62945                     delete series._lastStoreField;
62946                     delete series._lastStoreItem;
62947                 }
62948             }
62949         }, me);
62950     },
62951
62952     // @private handle mouse leave event.
62953     onMouseLeave: function(e) {
62954         var me = this;
62955         if (me.mask) {
62956             me.mixins.mask.onMouseLeave.call(me, e);
62957         }
62958         me.series.each(function(series) {
62959             delete series._lastItemForPoint;
62960         });
62961     },
62962
62963     // @private buffered refresh for when we update the store
62964     delayRefresh: function() {
62965         var me = this;
62966         if (!me.refreshTask) {
62967             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
62968         }
62969         me.refreshTask.delay(me.refreshBuffer);
62970     },
62971
62972     // @private
62973     refresh: function() {
62974         var me = this;
62975         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
62976             if (me.fireEvent('beforerefresh', me) !== false) {
62977                 me.redraw();
62978                 me.fireEvent('refresh', me);
62979             }
62980         }
62981     },
62982
62983     /**
62984      * Changes the data store bound to this chart and refreshes it.
62985      * @param {Ext.data.Store} store The store to bind to this chart
62986      */
62987     bindStore: function(store, initial) {
62988         var me = this;
62989         if (!initial && me.store) {
62990             if (store !== me.store && me.store.autoDestroy) {
62991                 me.store.destroyStore();
62992             }
62993             else {
62994                 me.store.un('datachanged', me.refresh, me);
62995                 me.store.un('add', me.delayRefresh, me);
62996                 me.store.un('remove', me.delayRefresh, me);
62997                 me.store.un('update', me.delayRefresh, me);
62998                 me.store.un('clear', me.refresh, me);
62999             }
63000         }
63001         if (store) {
63002             store = Ext.data.StoreManager.lookup(store);
63003             store.on({
63004                 scope: me,
63005                 datachanged: me.refresh,
63006                 add: me.delayRefresh,
63007                 remove: me.delayRefresh,
63008                 update: me.delayRefresh,
63009                 clear: me.refresh
63010             });
63011         }
63012         me.store = store;
63013         if (store && !initial) {
63014             me.refresh();
63015         }
63016     },
63017
63018     // @private Create Axis
63019     initializeAxis: function(axis) {
63020         var me = this,
63021             chartBBox = me.chartBBox,
63022             w = chartBBox.width,
63023             h = chartBBox.height,
63024             x = chartBBox.x,
63025             y = chartBBox.y,
63026             themeAttrs = me.themeAttrs,
63027             config = {
63028                 chart: me
63029             };
63030         if (themeAttrs) {
63031             config.axisStyle = Ext.apply({}, themeAttrs.axis);
63032             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
63033             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
63034             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
63035             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
63036             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
63037             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
63038             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
63039             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
63040         }
63041         switch (axis.position) {
63042             case 'top':
63043                 Ext.apply(config, {
63044                     length: w,
63045                     width: h,
63046                     x: x,
63047                     y: y
63048                 });
63049             break;
63050             case 'bottom':
63051                 Ext.apply(config, {
63052                     length: w,
63053                     width: h,
63054                     x: x,
63055                     y: h
63056                 });
63057             break;
63058             case 'left':
63059                 Ext.apply(config, {
63060                     length: h,
63061                     width: w,
63062                     x: x,
63063                     y: h
63064                 });
63065             break;
63066             case 'right':
63067                 Ext.apply(config, {
63068                     length: h,
63069                     width: w,
63070                     x: w,
63071                     y: h
63072                 });
63073             break;
63074         }
63075         if (!axis.chart) {
63076             Ext.apply(config, axis);
63077             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
63078         }
63079         else {
63080             Ext.apply(axis, config);
63081         }
63082     },
63083
63084
63085     /**
63086      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
63087      * for the space taken up on each side by the axes and legend.
63088      */
63089     alignAxes: function() {
63090         var me = this,
63091             axes = me.axes,
63092             legend = me.legend,
63093             edges = ['top', 'right', 'bottom', 'left'],
63094             chartBBox,
63095             insetPadding = me.insetPadding,
63096             insets = {
63097                 top: insetPadding,
63098                 right: insetPadding,
63099                 bottom: insetPadding,
63100                 left: insetPadding
63101             };
63102
63103         function getAxis(edge) {
63104             var i = axes.findIndex('position', edge);
63105             return (i < 0) ? null : axes.getAt(i);
63106         }
63107
63108         // Find the space needed by axes and legend as a positive inset from each edge
63109         Ext.each(edges, function(edge) {
63110             var isVertical = (edge === 'left' || edge === 'right'),
63111                 axis = getAxis(edge),
63112                 bbox;
63113
63114             // Add legend size if it's on this edge
63115             if (legend !== false) {
63116                 if (legend.position === edge) {
63117                     bbox = legend.getBBox();
63118                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
63119                 }
63120             }
63121
63122             // Add axis size if there's one on this edge only if it has been
63123             //drawn before.
63124             if (axis && axis.bbox) {
63125                 bbox = axis.bbox;
63126                 insets[edge] += (isVertical ? bbox.width : bbox.height);
63127             }
63128         });
63129         // Build the chart bbox based on the collected inset values
63130         chartBBox = {
63131             x: insets.left,
63132             y: insets.top,
63133             width: me.curWidth - insets.left - insets.right,
63134             height: me.curHeight - insets.top - insets.bottom
63135         };
63136         me.chartBBox = chartBBox;
63137
63138         // Go back through each axis and set its length and position based on the
63139         // corresponding edge of the chartBBox
63140         axes.each(function(axis) {
63141             var pos = axis.position,
63142                 isVertical = (pos === 'left' || pos === 'right');
63143
63144             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
63145             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
63146             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
63147             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
63148         });
63149     },
63150
63151     // @private initialize the series.
63152     initializeSeries: function(series, idx) {
63153         var me = this,
63154             themeAttrs = me.themeAttrs,
63155             seriesObj, markerObj, seriesThemes, st,
63156             markerThemes, colorArrayStyle = [],
63157             i = 0, l,
63158             config = {
63159                 chart: me,
63160                 seriesId: series.seriesId
63161             };
63162         if (themeAttrs) {
63163             seriesThemes = themeAttrs.seriesThemes;
63164             markerThemes = themeAttrs.markerThemes;
63165             seriesObj = Ext.apply({}, themeAttrs.series);
63166             markerObj = Ext.apply({}, themeAttrs.marker);
63167             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
63168             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
63169             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
63170             if (themeAttrs.colors) {
63171                 config.colorArrayStyle = themeAttrs.colors;
63172             } else {
63173                 colorArrayStyle = [];
63174                 for (l = seriesThemes.length; i < l; i++) {
63175                     st = seriesThemes[i];
63176                     if (st.fill || st.stroke) {
63177                         colorArrayStyle.push(st.fill || st.stroke);
63178                     }
63179                 }
63180                 if (colorArrayStyle.length) {
63181                     config.colorArrayStyle = colorArrayStyle;
63182                 }
63183             }
63184             config.seriesIdx = idx;
63185         }
63186         if (series instanceof Ext.chart.series.Series) {
63187             Ext.apply(series, config);
63188         } else {
63189             Ext.applyIf(config, series);
63190             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
63191         }
63192         if (series.initialize) {
63193             series.initialize();
63194         }
63195     },
63196
63197     // @private
63198     getMaxGutter: function() {
63199         var me = this,
63200             maxGutter = [0, 0];
63201         me.series.each(function(s) {
63202             var gutter = s.getGutters && s.getGutters() || [0, 0];
63203             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
63204             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
63205         });
63206         me.maxGutter = maxGutter;
63207     },
63208
63209     // @private draw axis.
63210     drawAxis: function(axis) {
63211         axis.drawAxis();
63212     },
63213
63214     // @private draw series.
63215     drawCharts: function(series) {
63216         series.triggerafterrender = false;
63217         series.drawSeries();
63218         if (!this.animate) {
63219             series.fireEvent('afterrender');
63220         }
63221     },
63222
63223     // @private remove gently.
63224     destroy: function() {
63225         Ext.destroy(this.surface);
63226         this.bindStore(null);
63227         this.callParent(arguments);
63228     }
63229 });
63230
63231 /**
63232  * @class Ext.chart.Highlight
63233  * A mixin providing highlight functionality for Ext.chart.series.Series.
63234  */
63235 Ext.define('Ext.chart.Highlight', {
63236
63237     /* Begin Definitions */
63238
63239     requires: ['Ext.fx.Anim'],
63240
63241     /* End Definitions */
63242
63243     /**
63244      * Highlight the given series item.
63245      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
63246      * or just use default styles per series by setting highlight = true.
63247      */
63248     highlight: false,
63249
63250     highlightCfg : null,
63251
63252     constructor: function(config) {
63253         if (config.highlight) {
63254             if (config.highlight !== true) { //is an object
63255                 this.highlightCfg = Ext.apply({}, config.highlight);
63256             }
63257             else {
63258                 this.highlightCfg = {
63259                     fill: '#fdd',
63260                     radius: 20,
63261                     lineWidth: 5,
63262                     stroke: '#f55'
63263                 };
63264             }
63265         }
63266     },
63267
63268     /**
63269      * Highlight the given series item.
63270      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
63271      */
63272     highlightItem: function(item) {
63273         if (!item) {
63274             return;
63275         }
63276         
63277         var me = this,
63278             sprite = item.sprite,
63279             opts = me.highlightCfg,
63280             surface = me.chart.surface,
63281             animate = me.chart.animate,
63282             p, from, to, pi;
63283
63284         if (!me.highlight || !sprite || sprite._highlighted) {
63285             return;
63286         }
63287         if (sprite._anim) {
63288             sprite._anim.paused = true;
63289         }
63290         sprite._highlighted = true;
63291         if (!sprite._defaults) {
63292             sprite._defaults = Ext.apply({}, sprite.attr);
63293             from = {};
63294             to = {};
63295             for (p in opts) {
63296                 if (! (p in sprite._defaults)) {
63297                     sprite._defaults[p] = surface.availableAttrs[p];
63298                 }
63299                 from[p] = sprite._defaults[p];
63300                 to[p] = opts[p];
63301                 if (Ext.isObject(opts[p])) {
63302                     from[p] = {};
63303                     to[p] = {};
63304                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
63305                     Ext.apply(from[p], sprite._defaults[p]);
63306                     for (pi in sprite._defaults[p]) {
63307                         if (! (pi in opts[p])) {
63308                             to[p][pi] = from[p][pi];
63309                         } else {
63310                             to[p][pi] = opts[p][pi];
63311                         }
63312                     }
63313                     for (pi in opts[p]) {
63314                         if (! (pi in to[p])) {
63315                             to[p][pi] = opts[p][pi];
63316                         }
63317                     }
63318                 }
63319             }
63320             sprite._from = from;
63321             sprite._to = to;
63322             sprite._endStyle = to;
63323         }
63324         if (animate) {
63325             sprite._anim = Ext.create('Ext.fx.Anim', {
63326                 target: sprite,
63327                 from: sprite._from,
63328                 to: sprite._to,
63329                 duration: 150
63330             });
63331         } else {
63332             sprite.setAttributes(sprite._to, true);
63333         }
63334     },
63335
63336     /**
63337      * Un-highlight any existing highlights
63338      */
63339     unHighlightItem: function() {
63340         if (!this.highlight || !this.items) {
63341             return;
63342         }
63343
63344         var me = this,
63345             items = me.items,
63346             len = items.length,
63347             opts = me.highlightCfg,
63348             animate = me.chart.animate,
63349             i = 0,
63350             obj, p, sprite;
63351
63352         for (; i < len; i++) {
63353             if (!items[i]) {
63354                 continue;
63355             }
63356             sprite = items[i].sprite;
63357             if (sprite && sprite._highlighted) {
63358                 if (sprite._anim) {
63359                     sprite._anim.paused = true;
63360                 }
63361                 obj = {};
63362                 for (p in opts) {
63363                     if (Ext.isObject(sprite._defaults[p])) {
63364                         obj[p] = {};
63365                         Ext.apply(obj[p], sprite._defaults[p]);
63366                     }
63367                     else {
63368                         obj[p] = sprite._defaults[p];
63369                     }
63370                 }
63371                 if (animate) {
63372                     //sprite._to = obj;
63373                     sprite._endStyle = obj;
63374                     sprite._anim = Ext.create('Ext.fx.Anim', {
63375                         target: sprite,
63376                         to: obj,
63377                         duration: 150
63378                     });
63379                 }
63380                 else {
63381                     sprite.setAttributes(obj, true);
63382                 }
63383                 delete sprite._highlighted;
63384                 //delete sprite._defaults;
63385             }
63386         }
63387     },
63388
63389     cleanHighlights: function() {
63390         if (!this.highlight) {
63391             return;
63392         }
63393
63394         var group = this.group,
63395             markerGroup = this.markerGroup,
63396             i = 0,
63397             l;
63398         for (l = group.getCount(); i < l; i++) {
63399             delete group.getAt(i)._defaults;
63400         }
63401         if (markerGroup) {
63402             for (l = markerGroup.getCount(); i < l; i++) {
63403                 delete markerGroup.getAt(i)._defaults;
63404             }
63405         }
63406     }
63407 });
63408 /**
63409  * @class Ext.chart.Label
63410  *
63411  * Labels is a mixin to the Series class. Labels methods are implemented
63412  * in each of the Series (Pie, Bar, etc) for label creation and placement.
63413  *
63414  * The methods implemented by the Series are:
63415  *
63416  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
63417  *   The arguments of the method are:
63418  *   - *`storeItem`* The element of the store that is related to the label sprite.
63419  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63420  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63421  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
63422  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
63423  *
63424  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
63425  *    The arguments of the method are:
63426  *    - *`label`* The sprite label.</li>
63427  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
63428  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63429  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63430  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
63431  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
63432  *    - *`animate`* A boolean value to set or unset animations for the labels.
63433  */
63434 Ext.define('Ext.chart.Label', {
63435
63436     /* Begin Definitions */
63437
63438     requires: ['Ext.draw.Color'],
63439
63440     /* End Definitions */
63441
63442     /**
63443      * @cfg {Object} label
63444      * Object with the following properties:
63445      *
63446      * - **display** : String
63447      *
63448      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
63449      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
63450      *   Default value: 'none'.
63451      *
63452      * - **color** : String
63453      *
63454      *   The color of the label text.
63455      *   Default value: '#000' (black).
63456      *
63457      * - **contrast** : Boolean
63458      *
63459      *   True to render the label in contrasting color with the backround.
63460      *   Default value: false.
63461      *
63462      * - **field** : String
63463      *
63464      *   The name of the field to be displayed in the label.
63465      *   Default value: 'name'.
63466      *
63467      * - **minMargin** : Number
63468      *
63469      *   Specifies the minimum distance from a label to the origin of the visualization.
63470      *   This parameter is useful when using PieSeries width variable pie slice lengths.
63471      *   Default value: 50.
63472      *
63473      * - **font** : String
63474      *
63475      *   The font used for the labels.
63476      *   Default value: "11px Helvetica, sans-serif".
63477      *
63478      * - **orientation** : String
63479      *
63480      *   Either "horizontal" or "vertical".
63481      *   Dafault value: "horizontal".
63482      *
63483      * - **renderer** : Function
63484      *
63485      *   Optional function for formatting the label into a displayable value.
63486      *   Default value: function(v) { return v; }
63487      */
63488
63489     //@private a regex to parse url type colors.
63490     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
63491
63492     //@private the mixin constructor. Used internally by Series.
63493     constructor: function(config) {
63494         var me = this;
63495         me.label = Ext.applyIf(me.label || {},
63496         {
63497             display: "none",
63498             color: "#000",
63499             field: "name",
63500             minMargin: 50,
63501             font: "11px Helvetica, sans-serif",
63502             orientation: "horizontal",
63503             renderer: function(v) {
63504                 return v;
63505             }
63506         });
63507
63508         if (me.label.display !== 'none') {
63509             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
63510         }
63511     },
63512
63513     //@private a method to render all labels in the labelGroup
63514     renderLabels: function() {
63515         var me = this,
63516             chart = me.chart,
63517             gradients = chart.gradients,
63518             items = me.items,
63519             animate = chart.animate,
63520             config = me.label,
63521             display = config.display,
63522             color = config.color,
63523             field = [].concat(config.field),
63524             group = me.labelsGroup,
63525             groupLength = (group || 0) && group.length,
63526             store = me.chart.store,
63527             len = store.getCount(),
63528             itemLength = (items || 0) && items.length,
63529             ratio = itemLength / len,
63530             gradientsCount = (gradients || 0) && gradients.length,
63531             Color = Ext.draw.Color,
63532             hides = [],
63533             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
63534             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
63535
63536         if (display == 'none') {
63537             return;
63538         }
63539         // no items displayed, hide all labels
63540         if(itemLength == 0){
63541             while(groupLength--)
63542                 hides.push(groupLength);
63543         }else{
63544             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
63545                 index = 0;
63546                 for (j = 0; j < ratio; j++) {
63547                     item = items[count];
63548                     label = group.getAt(groupIndex);
63549                     storeItem = store.getAt(i);
63550                     //check the excludes
63551                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
63552                         if(field[j]){
63553                             hides.push(groupIndex);
63554                         }
63555                         index++;
63556
63557                     }
63558
63559                     if (!item && label) {
63560                         label.hide(true);
63561                         groupIndex++;
63562                     }
63563
63564                     if (item && field[j]) {
63565                         if (!label) {
63566                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
63567                         }
63568                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
63569                         groupIndex++;
63570
63571                         //set contrast
63572                         if (config.contrast && item.sprite) {
63573                             sprite = item.sprite;
63574                             //set the color string to the color to be set.
63575                             if (sprite._endStyle) {
63576                                 colorString = sprite._endStyle.fill;
63577                             }
63578                             else if (sprite._to) {
63579                                 colorString = sprite._to.fill;
63580                             }
63581                             else {
63582                                 colorString = sprite.attr.fill;
63583                             }
63584                             colorString = colorString || sprite.attr.fill;
63585
63586                             spriteColor = Color.fromString(colorString);
63587                             //color wasn't parsed property maybe because it's a gradient id
63588                             if (colorString && !spriteColor) {
63589                                 colorString = colorString.match(me.colorStringRe)[1];
63590                                 for (k = 0; k < gradientsCount; k++) {
63591                                     gradient = gradients[k];
63592                                     if (gradient.id == colorString) {
63593                                         //avg color stops
63594                                         colorStop = 0; colorStopTotal = 0;
63595                                         for (colorStopIndex in gradient.stops) {
63596                                             colorStop++;
63597                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
63598                                         }
63599                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
63600                                         break;
63601                                     }
63602                                 }
63603                             }
63604                             else {
63605                                 spriteBrightness = spriteColor.getGrayscale() / 255;
63606                             }
63607                             if (label.isOutside) {
63608                                 spriteBrightness = 1;
63609                             }
63610                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
63611                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
63612                             label.setAttributes({
63613                                 fill: String(Color.fromHSL.apply({}, labelColor))
63614                             }, true);
63615                         }
63616
63617                     }
63618                     count++;
63619                     index++;
63620                 }
63621             }
63622         }
63623         me.hideLabels(hides);
63624     },
63625     hideLabels: function(hides){
63626         var labelsGroup = this.labelsGroup,
63627             hlen = hides.length;
63628         while(hlen--)
63629             labelsGroup.getAt(hides[hlen]).hide(true);
63630     }
63631 });
63632 Ext.define('Ext.chart.MaskLayer', {
63633     extend: 'Ext.Component',
63634     
63635     constructor: function(config) {
63636         config = Ext.apply(config || {}, {
63637             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
63638         });
63639         this.callParent([config]);    
63640     },
63641     
63642     initComponent: function() {
63643         var me = this;
63644         me.callParent(arguments);
63645         me.addEvents(
63646             'mousedown',
63647             'mouseup',
63648             'mousemove',
63649             'mouseenter',
63650             'mouseleave'
63651         );
63652     },
63653
63654     initDraggable: function() {
63655         this.callParent(arguments);
63656         this.dd.onStart = function (e) {
63657             var me = this,
63658                 comp = me.comp;
63659     
63660             // Cache the start [X, Y] array
63661             this.startPosition = comp.getPosition(true);
63662     
63663             // If client Component has a ghost method to show a lightweight version of itself
63664             // then use that as a drag proxy unless configured to liveDrag.
63665             if (comp.ghost && !comp.liveDrag) {
63666                  me.proxy = comp.ghost();
63667                  me.dragTarget = me.proxy.header.el;
63668             }
63669     
63670             // Set the constrainTo Region before we start dragging.
63671             if (me.constrain || me.constrainDelegate) {
63672                 me.constrainTo = me.calculateConstrainRegion();
63673             }
63674         };
63675     }
63676 });
63677 /**
63678  * @class Ext.chart.TipSurface
63679  * @ignore
63680  */
63681 Ext.define('Ext.chart.TipSurface', {
63682
63683     /* Begin Definitions */
63684
63685     extend: 'Ext.draw.Component',
63686
63687     /* End Definitions */
63688
63689     spriteArray: false,
63690     renderFirst: true,
63691
63692     constructor: function(config) {
63693         this.callParent([config]);
63694         if (config.sprites) {
63695             this.spriteArray = [].concat(config.sprites);
63696             delete config.sprites;
63697         }
63698     },
63699
63700     onRender: function() {
63701         var me = this,
63702             i = 0,
63703             l = 0,
63704             sp,
63705             sprites;
63706             this.callParent(arguments);
63707         sprites = me.spriteArray;
63708         if (me.renderFirst && sprites) {
63709             me.renderFirst = false;
63710             for (l = sprites.length; i < l; i++) {
63711                 sp = me.surface.add(sprites[i]);
63712                 sp.setAttributes({
63713                     hidden: false
63714                 },
63715                 true);
63716             }
63717         }
63718     }
63719 });
63720
63721 /**
63722  * @class Ext.chart.Tip
63723  * Provides tips for Ext.chart.series.Series.
63724  */
63725 Ext.define('Ext.chart.Tip', {
63726
63727     /* Begin Definitions */
63728
63729     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
63730
63731     /* End Definitions */
63732
63733     constructor: function(config) {
63734         var me = this,
63735             surface,
63736             sprites,
63737             tipSurface;
63738         if (config.tips) {
63739             me.tipTimeout = null;
63740             me.tipConfig = Ext.apply({}, config.tips, {
63741                 renderer: Ext.emptyFn,
63742                 constrainPosition: false
63743             });
63744             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
63745             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
63746             me.chart.surface.on('mouseleave', function() {
63747                 me.hideTip();
63748             });
63749             if (me.tipConfig.surface) {
63750                 //initialize a surface
63751                 surface = me.tipConfig.surface;
63752                 sprites = surface.sprites;
63753                 tipSurface = Ext.create('Ext.chart.TipSurface', {
63754                     id: 'tipSurfaceComponent',
63755                     sprites: sprites
63756                 });
63757                 if (surface.width && surface.height) {
63758                     tipSurface.setSize(surface.width, surface.height);
63759                 }
63760                 me.tooltip.add(tipSurface);
63761                 me.spriteTip = tipSurface;
63762             }
63763         }
63764     },
63765
63766     showTip: function(item) {
63767         var me = this;
63768         if (!me.tooltip) {
63769             return;
63770         }
63771         clearTimeout(me.tipTimeout);
63772         var tooltip = me.tooltip,
63773             spriteTip = me.spriteTip,
63774             tipConfig = me.tipConfig,
63775             trackMouse = tooltip.trackMouse,
63776             sprite, surface, surfaceExt, pos, x, y;
63777         if (!trackMouse) {
63778             tooltip.trackMouse = true;
63779             sprite = item.sprite;
63780             surface = sprite.surface;
63781             surfaceExt = Ext.get(surface.getId());
63782             if (surfaceExt) {
63783                 pos = surfaceExt.getXY();
63784                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
63785                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
63786                 tooltip.targetXY = [x, y];
63787             }
63788         }
63789         if (spriteTip) {
63790             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
63791         } else {
63792             tipConfig.renderer.call(tooltip, item.storeItem, item);
63793         }
63794         tooltip.show();
63795         tooltip.trackMouse = trackMouse;
63796     },
63797
63798     hideTip: function(item) {
63799         var tooltip = this.tooltip;
63800         if (!tooltip) {
63801             return;
63802         }
63803         clearTimeout(this.tipTimeout);
63804         this.tipTimeout = setTimeout(function() {
63805             tooltip.hide();
63806         }, 0);
63807     }
63808 });
63809 /**
63810  * @class Ext.chart.axis.Abstract
63811  * Base class for all axis classes.
63812  * @private
63813  */
63814 Ext.define('Ext.chart.axis.Abstract', {
63815
63816     /* Begin Definitions */
63817
63818     requires: ['Ext.chart.Chart'],
63819
63820     /* End Definitions */
63821
63822     /**
63823      * Creates new Axis.
63824      * @param {Object} config (optional) Config options.
63825      */
63826     constructor: function(config) {
63827         config = config || {};
63828
63829         var me = this,
63830             pos = config.position || 'left';
63831
63832         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
63833         //axisLabel(Top|Bottom|Right|Left)Style
63834         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
63835         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
63836         Ext.apply(me, config);
63837         me.fields = [].concat(me.fields);
63838         this.callParent();
63839         me.labels = [];
63840         me.getId();
63841         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
63842     },
63843
63844     alignment: null,
63845     grid: false,
63846     steps: 10,
63847     x: 0,
63848     y: 0,
63849     minValue: 0,
63850     maxValue: 0,
63851
63852     getId: function() {
63853         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
63854     },
63855
63856     /*
63857       Called to process a view i.e to make aggregation and filtering over
63858       a store creating a substore to be used to render the axis. Since many axes
63859       may do different things on the data and we want the final result of all these
63860       operations to be rendered we need to call processView on all axes before drawing
63861       them.
63862     */
63863     processView: Ext.emptyFn,
63864
63865     drawAxis: Ext.emptyFn,
63866     addDisplayAndLabels: Ext.emptyFn
63867 });
63868
63869 /**
63870  * @class Ext.chart.axis.Axis
63871  * @extends Ext.chart.axis.Abstract
63872  *
63873  * Defines axis for charts. The axis position, type, style can be configured.
63874  * The axes are defined in an axes array of configuration objects where the type,
63875  * field, grid and other configuration options can be set. To know more about how
63876  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
63877  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
63878  *
63879  *     axes: [{
63880  *         type: 'Numeric',
63881  *         grid: true,
63882  *         position: 'left',
63883  *         fields: ['data1', 'data2', 'data3'],
63884  *         title: 'Number of Hits',
63885  *         grid: {
63886  *             odd: {
63887  *                 opacity: 1,
63888  *                 fill: '#ddd',
63889  *                 stroke: '#bbb',
63890  *                 'stroke-width': 1
63891  *             }
63892  *         },
63893  *         minimum: 0
63894  *     }, {
63895  *         type: 'Category',
63896  *         position: 'bottom',
63897  *         fields: ['name'],
63898  *         title: 'Month of the Year',
63899  *         grid: true,
63900  *         label: {
63901  *             rotate: {
63902  *                 degrees: 315
63903  *             }
63904  *         }
63905  *     }]
63906  *
63907  * In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
63908  * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
63909  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
63910  * category axis the labels will be rotated so they can fit the space better.
63911  */
63912 Ext.define('Ext.chart.axis.Axis', {
63913
63914     /* Begin Definitions */
63915
63916     extend: 'Ext.chart.axis.Abstract',
63917
63918     alternateClassName: 'Ext.chart.Axis',
63919
63920     requires: ['Ext.draw.Draw'],
63921
63922     /* End Definitions */
63923
63924     /**
63925      * @cfg {Boolean/Object} grid
63926      * The grid configuration enables you to set a background grid for an axis.
63927      * If set to *true* on a vertical axis, vertical lines will be drawn.
63928      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
63929      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
63930      *
63931      * You can set specific options for the grid configuration for odd and/or even lines/rows.
63932      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
63933      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
63934      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
63935      *
63936      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
63937      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
63938      *
63939      * For example:
63940      *
63941      *     axes: [{
63942      *         type: 'Numeric',
63943      *         grid: true,
63944      *         position: 'left',
63945      *         fields: ['data1', 'data2', 'data3'],
63946      *         title: 'Number of Hits',
63947      *         grid: {
63948      *             odd: {
63949      *                 opacity: 1,
63950      *                 fill: '#ddd',
63951      *                 stroke: '#bbb',
63952      *                 'stroke-width': 1
63953      *             }
63954      *         }
63955      *     }, {
63956      *         type: 'Category',
63957      *         position: 'bottom',
63958      *         fields: ['name'],
63959      *         title: 'Month of the Year',
63960      *         grid: true
63961      *     }]
63962      *
63963      */
63964
63965     /**
63966      * @cfg {Number} majorTickSteps
63967      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
63968      */
63969
63970     /**
63971      * @cfg {Number} minorTickSteps
63972      * The number of small ticks between two major ticks. Default is zero.
63973      */
63974
63975     /**
63976      * @cfg {String} title
63977      * The title for the Axis
63978      */
63979
63980     //@private force min/max values from store
63981     forceMinMax: false,
63982
63983     /**
63984      * @cfg {Number} dashSize
63985      * The size of the dash marker. Default's 3.
63986      */
63987     dashSize: 3,
63988
63989     /**
63990      * @cfg {String} position
63991      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
63992      */
63993     position: 'bottom',
63994
63995     // @private
63996     skipFirst: false,
63997
63998     /**
63999      * @cfg {Number} length
64000      * Offset axis position. Default's 0.
64001      */
64002     length: 0,
64003
64004     /**
64005      * @cfg {Number} width
64006      * Offset axis width. Default's 0.
64007      */
64008     width: 0,
64009
64010     majorTickSteps: false,
64011
64012     // @private
64013     applyData: Ext.emptyFn,
64014
64015     getRange: function () {
64016         var me = this,
64017             store = me.chart.getChartStore(),
64018             fields = me.fields,
64019             ln = fields.length,
64020             math = Math,
64021             mmax = math.max,
64022             mmin = math.min,
64023             aggregate = false,
64024             min = isNaN(me.minimum) ? Infinity : me.minimum,
64025             max = isNaN(me.maximum) ? -Infinity : me.maximum,
64026             total = 0, i, l, value, values, rec,
64027             excludes = [],
64028             series = me.chart.series.items;
64029
64030         //if one series is stacked I have to aggregate the values
64031         //for the scale.
64032         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
64033         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
64034         // Assuming only stack on y-axis.
64035         // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since 
64036         // it was constraining the minmax calculation to y-axis stacked
64037         // visualizations.
64038         for (i = 0, l = series.length; !aggregate && i < l; i++) {
64039             aggregate = aggregate || series[i].stacked;
64040             excludes = series[i].__excludes || excludes;
64041         }
64042         store.each(function(record) {
64043             if (aggregate) {
64044                 if (!isFinite(min)) {
64045                     min = 0;
64046                 }
64047                 for (values = [0, 0], i = 0; i < ln; i++) {
64048                     if (excludes[i]) {
64049                         continue;
64050                     }
64051                     rec = record.get(fields[i]);
64052                     values[+(rec > 0)] += math.abs(rec);
64053                 }
64054                 max = mmax(max, -values[0], +values[1]);
64055                 min = mmin(min, -values[0], +values[1]);
64056             }
64057             else {
64058                 for (i = 0; i < ln; i++) {
64059                     if (excludes[i]) {
64060                         continue;
64061                     }
64062                     value = record.get(fields[i]);
64063                     max = mmax(max, +value);
64064                     min = mmin(min, +value);
64065                 }
64066             }
64067         });
64068         if (!isFinite(max)) {
64069             max = me.prevMax || 0;
64070         }
64071         if (!isFinite(min)) {
64072             min = me.prevMin || 0;
64073         }
64074         //normalize min max for snapEnds.
64075         if (min != max && (max != Math.floor(max))) {
64076             max = Math.floor(max) + 1;
64077         }
64078
64079         if (!isNaN(me.minimum)) {
64080             min = me.minimum;
64081         }
64082         
64083         if (!isNaN(me.maximum)) {
64084             max = me.maximum;
64085         }
64086
64087         return {min: min, max: max};
64088     },
64089
64090     // @private creates a structure with start, end and step points.
64091     calcEnds: function() {
64092         var me = this,
64093             fields = me.fields,
64094             range = me.getRange(),
64095             min = range.min,
64096             max = range.max,
64097             outfrom, outto, out;
64098
64099         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
64100         outfrom = out.from;
64101         outto = out.to;
64102         if (me.forceMinMax) {
64103             if (!isNaN(max)) {
64104                 out.to = max;
64105             }
64106             if (!isNaN(min)) {
64107                 out.from = min;
64108             }
64109         }
64110         if (!isNaN(me.maximum)) {
64111             //TODO(nico) users are responsible for their own minimum/maximum values set.
64112             //Clipping should be added to remove lines in the chart which are below the axis.
64113             out.to = me.maximum;
64114         }
64115         if (!isNaN(me.minimum)) {
64116             //TODO(nico) users are responsible for their own minimum/maximum values set.
64117             //Clipping should be added to remove lines in the chart which are below the axis.
64118             out.from = me.minimum;
64119         }
64120
64121         //Adjust after adjusting minimum and maximum
64122         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
64123
64124         if (me.adjustMaximumByMajorUnit) {
64125             out.to += out.step;
64126         }
64127         if (me.adjustMinimumByMajorUnit) {
64128             out.from -= out.step;
64129         }
64130         me.prevMin = min == max? 0 : min;
64131         me.prevMax = max;
64132         return out;
64133     },
64134
64135     /**
64136      * Renders the axis into the screen and updates its position.
64137      */
64138     drawAxis: function (init) {
64139         var me = this,
64140             i, j,
64141             x = me.x,
64142             y = me.y,
64143             gutterX = me.chart.maxGutter[0],
64144             gutterY = me.chart.maxGutter[1],
64145             dashSize = me.dashSize,
64146             subDashesX = me.minorTickSteps || 0,
64147             subDashesY = me.minorTickSteps || 0,
64148             length = me.length,
64149             position = me.position,
64150             inflections = [],
64151             calcLabels = false,
64152             stepCalcs = me.applyData(),
64153             step = stepCalcs.step,
64154             steps = stepCalcs.steps,
64155             from = stepCalcs.from,
64156             to = stepCalcs.to,
64157             trueLength,
64158             currentX,
64159             currentY,
64160             path,
64161             prev,
64162             dashesX,
64163             dashesY,
64164             delta;
64165
64166         //If no steps are specified
64167         //then don't draw the axis. This generally happens
64168         //when an empty store.
64169         if (me.hidden || isNaN(step) || (from == to)) {
64170             return;
64171         }
64172
64173         me.from = stepCalcs.from;
64174         me.to = stepCalcs.to;
64175         if (position == 'left' || position == 'right') {
64176             currentX = Math.floor(x) + 0.5;
64177             path = ["M", currentX, y, "l", 0, -length];
64178             trueLength = length - (gutterY * 2);
64179         }
64180         else {
64181             currentY = Math.floor(y) + 0.5;
64182             path = ["M", x, currentY, "l", length, 0];
64183             trueLength = length - (gutterX * 2);
64184         }
64185
64186         delta = trueLength / (steps || 1);
64187         dashesX = Math.max(subDashesX +1, 0);
64188         dashesY = Math.max(subDashesY +1, 0);
64189         if (me.type == 'Numeric' || me.type == 'Time') {
64190             calcLabels = true;
64191             me.labels = [stepCalcs.from];
64192         }
64193         if (position == 'right' || position == 'left') {
64194             currentY = y - gutterY;
64195             currentX = x - ((position == 'left') * dashSize * 2);
64196             while (currentY >= y - gutterY - trueLength) {
64197                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
64198                 if (currentY != y - gutterY) {
64199                     for (i = 1; i < dashesY; i++) {
64200                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64201                     }
64202                 }
64203                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64204                 currentY -= delta;
64205                 if (calcLabels) {
64206                     me.labels.push(me.labels[me.labels.length -1] + step);
64207                 }
64208                 if (delta === 0) {
64209                     break;
64210                 }
64211             }
64212             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
64213                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
64214                 for (i = 1; i < dashesY; i++) {
64215                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64216                 }
64217                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64218                 if (calcLabels) {
64219                     me.labels.push(me.labels[me.labels.length -1] + step);
64220                 }
64221             }
64222         } else {
64223             currentX = x + gutterX;
64224             currentY = y - ((position == 'top') * dashSize * 2);
64225             while (currentX <= x + gutterX + trueLength) {
64226                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64227                 if (currentX != x + gutterX) {
64228                     for (i = 1; i < dashesX; i++) {
64229                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64230                     }
64231                 }
64232                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64233                 currentX += delta;
64234                 if (calcLabels) {
64235                     me.labels.push(me.labels[me.labels.length -1] + step);
64236                 }
64237                 if (delta === 0) {
64238                     break;
64239                 }
64240             }
64241             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
64242                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64243                 for (i = 1; i < dashesX; i++) {
64244                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64245                 }
64246                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64247                 if (calcLabels) {
64248                     me.labels.push(me.labels[me.labels.length -1] + step);
64249                 }
64250             }
64251         }
64252         if (!me.axis) {
64253             me.axis = me.chart.surface.add(Ext.apply({
64254                 type: 'path',
64255                 path: path
64256             }, me.axisStyle));
64257         }
64258         me.axis.setAttributes({
64259             path: path
64260         }, true);
64261         me.inflections = inflections;
64262         if (!init && me.grid) {
64263             me.drawGrid();
64264         }
64265         me.axisBBox = me.axis.getBBox();
64266         me.drawLabel();
64267     },
64268
64269     /**
64270      * Renders an horizontal and/or vertical grid into the Surface.
64271      */
64272     drawGrid: function() {
64273         var me = this,
64274             surface = me.chart.surface,
64275             grid = me.grid,
64276             odd = grid.odd,
64277             even = grid.even,
64278             inflections = me.inflections,
64279             ln = inflections.length - ((odd || even)? 0 : 1),
64280             position = me.position,
64281             gutter = me.chart.maxGutter,
64282             width = me.width - 2,
64283             vert = false,
64284             point, prevPoint,
64285             i = 1,
64286             path = [], styles, lineWidth, dlineWidth,
64287             oddPath = [], evenPath = [];
64288
64289         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
64290             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
64291             i = 0;
64292             ln++;
64293         }
64294         for (; i < ln; i++) {
64295             point = inflections[i];
64296             prevPoint = inflections[i - 1];
64297             if (odd || even) {
64298                 path = (i % 2)? oddPath : evenPath;
64299                 styles = ((i % 2)? odd : even) || {};
64300                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
64301                 dlineWidth = 2 * lineWidth;
64302                 if (position == 'left') {
64303                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64304                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64305                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
64306                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
64307                 }
64308                 else if (position == 'right') {
64309                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64310                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64311                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
64312                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
64313                 }
64314                 else if (position == 'top') {
64315                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
64316                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
64317                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
64318                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
64319                 }
64320                 else {
64321                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
64322                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
64323                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
64324                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
64325                 }
64326             } else {
64327                 if (position == 'left') {
64328                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
64329                 }
64330                 else if (position == 'right') {
64331                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
64332                 }
64333                 else if (position == 'top') {
64334                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
64335                 }
64336                 else {
64337                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
64338                 }
64339             }
64340         }
64341         if (odd || even) {
64342             if (oddPath.length) {
64343                 if (!me.gridOdd && oddPath.length) {
64344                     me.gridOdd = surface.add({
64345                         type: 'path',
64346                         path: oddPath
64347                     });
64348                 }
64349                 me.gridOdd.setAttributes(Ext.apply({
64350                     path: oddPath,
64351                     hidden: false
64352                 }, odd || {}), true);
64353             }
64354             if (evenPath.length) {
64355                 if (!me.gridEven) {
64356                     me.gridEven = surface.add({
64357                         type: 'path',
64358                         path: evenPath
64359                     });
64360                 }
64361                 me.gridEven.setAttributes(Ext.apply({
64362                     path: evenPath,
64363                     hidden: false
64364                 }, even || {}), true);
64365             }
64366         }
64367         else {
64368             if (path.length) {
64369                 if (!me.gridLines) {
64370                     me.gridLines = me.chart.surface.add({
64371                         type: 'path',
64372                         path: path,
64373                         "stroke-width": me.lineWidth || 1,
64374                         stroke: me.gridColor || '#ccc'
64375                     });
64376                 }
64377                 me.gridLines.setAttributes({
64378                     hidden: false,
64379                     path: path
64380                 }, true);
64381             }
64382             else if (me.gridLines) {
64383                 me.gridLines.hide(true);
64384             }
64385         }
64386     },
64387
64388     //@private
64389     getOrCreateLabel: function(i, text) {
64390         var me = this,
64391             labelGroup = me.labelGroup,
64392             textLabel = labelGroup.getAt(i),
64393             surface = me.chart.surface;
64394         if (textLabel) {
64395             if (text != textLabel.attr.text) {
64396                 textLabel.setAttributes(Ext.apply({
64397                     text: text
64398                 }, me.label), true);
64399                 textLabel._bbox = textLabel.getBBox();
64400             }
64401         }
64402         else {
64403             textLabel = surface.add(Ext.apply({
64404                 group: labelGroup,
64405                 type: 'text',
64406                 x: 0,
64407                 y: 0,
64408                 text: text
64409             }, me.label));
64410             surface.renderItem(textLabel);
64411             textLabel._bbox = textLabel.getBBox();
64412         }
64413         //get untransformed bounding box
64414         if (me.label.rotation) {
64415             textLabel.setAttributes({
64416                 rotation: {
64417                     degrees: 0
64418                 }
64419             }, true);
64420             textLabel._ubbox = textLabel.getBBox();
64421             textLabel.setAttributes(me.label, true);
64422         } else {
64423             textLabel._ubbox = textLabel._bbox;
64424         }
64425         return textLabel;
64426     },
64427
64428     rect2pointArray: function(sprite) {
64429         var surface = this.chart.surface,
64430             rect = surface.getBBox(sprite, true),
64431             p1 = [rect.x, rect.y],
64432             p1p = p1.slice(),
64433             p2 = [rect.x + rect.width, rect.y],
64434             p2p = p2.slice(),
64435             p3 = [rect.x + rect.width, rect.y + rect.height],
64436             p3p = p3.slice(),
64437             p4 = [rect.x, rect.y + rect.height],
64438             p4p = p4.slice(),
64439             matrix = sprite.matrix;
64440         //transform the points
64441         p1[0] = matrix.x.apply(matrix, p1p);
64442         p1[1] = matrix.y.apply(matrix, p1p);
64443
64444         p2[0] = matrix.x.apply(matrix, p2p);
64445         p2[1] = matrix.y.apply(matrix, p2p);
64446
64447         p3[0] = matrix.x.apply(matrix, p3p);
64448         p3[1] = matrix.y.apply(matrix, p3p);
64449
64450         p4[0] = matrix.x.apply(matrix, p4p);
64451         p4[1] = matrix.y.apply(matrix, p4p);
64452         return [p1, p2, p3, p4];
64453     },
64454
64455     intersect: function(l1, l2) {
64456         var r1 = this.rect2pointArray(l1),
64457             r2 = this.rect2pointArray(l2);
64458         return !!Ext.draw.Draw.intersect(r1, r2).length;
64459     },
64460
64461     drawHorizontalLabels: function() {
64462        var  me = this,
64463             labelConf = me.label,
64464             floor = Math.floor,
64465             max = Math.max,
64466             axes = me.chart.axes,
64467             position = me.position,
64468             inflections = me.inflections,
64469             ln = inflections.length,
64470             labels = me.labels,
64471             labelGroup = me.labelGroup,
64472             maxHeight = 0,
64473             ratio,
64474             gutterY = me.chart.maxGutter[1],
64475             ubbox, bbox, point, prevX, prevLabel,
64476             projectedWidth = 0,
64477             textLabel, attr, textRight, text,
64478             label, last, x, y, i, firstLabel;
64479
64480         last = ln - 1;
64481         //get a reference to the first text label dimensions
64482         point = inflections[0];
64483         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
64484         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
64485
64486         for (i = 0; i < ln; i++) {
64487             point = inflections[i];
64488             text = me.label.renderer(labels[i]);
64489             textLabel = me.getOrCreateLabel(i, text);
64490             bbox = textLabel._bbox;
64491             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
64492             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
64493             if (me.chart.maxGutter[0] == 0) {
64494                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
64495                     x = point[0];
64496                 }
64497                 else if (i == last && axes.findIndex('position', 'right') == -1) {
64498                     x = point[0] - bbox.width;
64499                 }
64500             }
64501             if (position == 'top') {
64502                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
64503             }
64504             else {
64505                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
64506             }
64507
64508             textLabel.setAttributes({
64509                 hidden: false,
64510                 x: x,
64511                 y: y
64512             }, true);
64513
64514             // Skip label if there isn't available minimum space
64515             if (i != 0 && (me.intersect(textLabel, prevLabel)
64516                 || me.intersect(textLabel, firstLabel))) {
64517                 textLabel.hide(true);
64518                 continue;
64519             }
64520
64521             prevLabel = textLabel;
64522         }
64523
64524         return maxHeight;
64525     },
64526
64527     drawVerticalLabels: function() {
64528         var me = this,
64529             inflections = me.inflections,
64530             position = me.position,
64531             ln = inflections.length,
64532             labels = me.labels,
64533             maxWidth = 0,
64534             max = Math.max,
64535             floor = Math.floor,
64536             ceil = Math.ceil,
64537             axes = me.chart.axes,
64538             gutterY = me.chart.maxGutter[1],
64539             ubbox, bbox, point, prevLabel,
64540             projectedWidth = 0,
64541             textLabel, attr, textRight, text,
64542             label, last, x, y, i;
64543
64544         last = ln;
64545         for (i = 0; i < last; i++) {
64546             point = inflections[i];
64547             text = me.label.renderer(labels[i]);
64548             textLabel = me.getOrCreateLabel(i, text);
64549             bbox = textLabel._bbox;
64550
64551             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
64552             y = point[1];
64553             if (gutterY < bbox.height / 2) {
64554                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
64555                     y = me.y - me.length + ceil(bbox.height / 2);
64556                 }
64557                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
64558                     y = me.y - floor(bbox.height / 2);
64559                 }
64560             }
64561             if (position == 'left') {
64562                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
64563             }
64564             else {
64565                 x = point[0] + me.dashSize + me.label.padding + 2;
64566             }
64567             textLabel.setAttributes(Ext.apply({
64568                 hidden: false,
64569                 x: x,
64570                 y: y
64571             }, me.label), true);
64572             // Skip label if there isn't available minimum space
64573             if (i != 0 && me.intersect(textLabel, prevLabel)) {
64574                 textLabel.hide(true);
64575                 continue;
64576             }
64577             prevLabel = textLabel;
64578         }
64579
64580         return maxWidth;
64581     },
64582
64583     /**
64584      * Renders the labels in the axes.
64585      */
64586     drawLabel: function() {
64587         var me = this,
64588             position = me.position,
64589             labelGroup = me.labelGroup,
64590             inflections = me.inflections,
64591             maxWidth = 0,
64592             maxHeight = 0,
64593             ln, i;
64594
64595         if (position == 'left' || position == 'right') {
64596             maxWidth = me.drawVerticalLabels();
64597         } else {
64598             maxHeight = me.drawHorizontalLabels();
64599         }
64600
64601         // Hide unused bars
64602         ln = labelGroup.getCount();
64603         i = inflections.length;
64604         for (; i < ln; i++) {
64605             labelGroup.getAt(i).hide(true);
64606         }
64607
64608         me.bbox = {};
64609         Ext.apply(me.bbox, me.axisBBox);
64610         me.bbox.height = maxHeight;
64611         me.bbox.width = maxWidth;
64612         if (Ext.isString(me.title)) {
64613             me.drawTitle(maxWidth, maxHeight);
64614         }
64615     },
64616
64617     // @private creates the elipsis for the text.
64618     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
64619         var bbox,
64620             x;
64621
64622         if (desiredWidth < minWidth) {
64623             sprite.hide(true);
64624             return false;
64625         }
64626         while (text.length > 4) {
64627             text = text.substr(0, text.length - 4) + "...";
64628             sprite.setAttributes({
64629                 text: text
64630             }, true);
64631             bbox = sprite.getBBox();
64632             if (bbox.width < desiredWidth) {
64633                 if (typeof center == 'number') {
64634                     sprite.setAttributes({
64635                         x: Math.floor(center - (bbox.width / 2))
64636                     }, true);
64637                 }
64638                 break;
64639             }
64640         }
64641         return true;
64642     },
64643
64644     /**
64645      * Updates the {@link #title} of this axis.
64646      * @param {String} title
64647      */
64648     setTitle: function(title) {
64649         this.title = title;
64650         this.drawLabel();
64651     },
64652
64653     // @private draws the title for the axis.
64654     drawTitle: function(maxWidth, maxHeight) {
64655         var me = this,
64656             position = me.position,
64657             surface = me.chart.surface,
64658             displaySprite = me.displaySprite,
64659             title = me.title,
64660             rotate = (position == 'left' || position == 'right'),
64661             x = me.x,
64662             y = me.y,
64663             base, bbox, pad;
64664
64665         if (displaySprite) {
64666             displaySprite.setAttributes({text: title}, true);
64667         } else {
64668             base = {
64669                 type: 'text',
64670                 x: 0,
64671                 y: 0,
64672                 text: title
64673             };
64674             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
64675             surface.renderItem(displaySprite);
64676         }
64677         bbox = displaySprite.getBBox();
64678         pad = me.dashSize + me.label.padding;
64679
64680         if (rotate) {
64681             y -= ((me.length / 2) - (bbox.height / 2));
64682             if (position == 'left') {
64683                 x -= (maxWidth + pad + (bbox.width / 2));
64684             }
64685             else {
64686                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
64687             }
64688             me.bbox.width += bbox.width + 10;
64689         }
64690         else {
64691             x += (me.length / 2) - (bbox.width * 0.5);
64692             if (position == 'top') {
64693                 y -= (maxHeight + pad + (bbox.height * 0.3));
64694             }
64695             else {
64696                 y += (maxHeight + pad + (bbox.height * 0.8));
64697             }
64698             me.bbox.height += bbox.height + 10;
64699         }
64700         displaySprite.setAttributes({
64701             translate: {
64702                 x: x,
64703                 y: y
64704             }
64705         }, true);
64706     }
64707 });
64708
64709 /**
64710  * @class Ext.chart.axis.Category
64711  * @extends Ext.chart.axis.Axis
64712  *
64713  * A type of axis that displays items in categories. This axis is generally used to
64714  * display categorical information like names of items, month names, quarters, etc.
64715  * but no quantitative values. For that other type of information `Number`
64716  * axis are more suitable.
64717  *
64718  * As with other axis you can set the position of the axis and its title. For example:
64719  *
64720  *     @example
64721  *     var store = Ext.create('Ext.data.JsonStore', {
64722  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
64723  *         data: [
64724  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
64725  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
64726  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
64727  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
64728  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
64729  *         ]
64730  *     });
64731  *
64732  *     Ext.create('Ext.chart.Chart', {
64733  *         renderTo: Ext.getBody(),
64734  *         width: 500,
64735  *         height: 300,
64736  *         store: store,
64737  *         axes: [{
64738  *             type: 'Numeric',
64739  *             grid: true,
64740  *             position: 'left',
64741  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
64742  *             title: 'Sample Values',
64743  *             grid: {
64744  *                 odd: {
64745  *                     opacity: 1,
64746  *                     fill: '#ddd',
64747  *                     stroke: '#bbb',
64748  *                     'stroke-width': 1
64749  *                 }
64750  *             },
64751  *             minimum: 0,
64752  *             adjustMinimumByMajorUnit: 0
64753  *         }, {
64754  *             type: 'Category',
64755  *             position: 'bottom',
64756  *             fields: ['name'],
64757  *             title: 'Sample Metrics',
64758  *             grid: true,
64759  *             label: {
64760  *                 rotate: {
64761  *                     degrees: 315
64762  *                 }
64763  *             }
64764  *         }],
64765  *         series: [{
64766  *             type: 'area',
64767  *             highlight: false,
64768  *             axis: 'left',
64769  *             xField: 'name',
64770  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
64771  *             style: {
64772  *                 opacity: 0.93
64773  *             }
64774  *         }]
64775  *     });
64776  *
64777  * In this example with set the category axis to the bottom of the surface, bound the axis to
64778  * the `name` property and set as title _Month of the Year_.
64779  */
64780 Ext.define('Ext.chart.axis.Category', {
64781
64782     /* Begin Definitions */
64783
64784     extend: 'Ext.chart.axis.Axis',
64785
64786     alternateClassName: 'Ext.chart.CategoryAxis',
64787
64788     alias: 'axis.category',
64789
64790     /* End Definitions */
64791
64792     /**
64793      * A list of category names to display along this axis.
64794      * @property {String} categoryNames
64795      */
64796     categoryNames: null,
64797
64798     /**
64799      * Indicates whether or not to calculate the number of categories (ticks and
64800      * labels) when there is not enough room to display all labels on the axis.
64801      * If set to true, the axis will determine the number of categories to plot.
64802      * If not, all categories will be plotted.
64803      *
64804      * @property calculateCategoryCount
64805      * @type Boolean
64806      */
64807     calculateCategoryCount: false,
64808
64809     // @private creates an array of labels to be used when rendering.
64810     setLabels: function() {
64811         var store = this.chart.store,
64812             fields = this.fields,
64813             ln = fields.length,
64814             i;
64815
64816         this.labels = [];
64817         store.each(function(record) {
64818             for (i = 0; i < ln; i++) {
64819                 this.labels.push(record.get(fields[i]));
64820             }
64821         }, this);
64822     },
64823
64824     // @private calculates labels positions and marker positions for rendering.
64825     applyData: function() {
64826         this.callParent();
64827         this.setLabels();
64828         var count = this.chart.store.getCount();
64829         return {
64830             from: 0,
64831             to: count,
64832             power: 1,
64833             step: 1,
64834             steps: count - 1
64835         };
64836     }
64837 });
64838
64839 /**
64840  * @class Ext.chart.axis.Gauge
64841  * @extends Ext.chart.axis.Abstract
64842  *
64843  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
64844  * displays numeric data from an interval defined by the `minimum`, `maximum` and
64845  * `step` configuration properties. The placement of the numeric data can be changed
64846  * by altering the `margin` option that is set to `10` by default.
64847  *
64848  * A possible configuration for this axis would look like:
64849  *
64850  *     axes: [{
64851  *         type: 'gauge',
64852  *         position: 'gauge',
64853  *         minimum: 0,
64854  *         maximum: 100,
64855  *         steps: 10,
64856  *         margin: 7
64857  *     }],
64858  */
64859 Ext.define('Ext.chart.axis.Gauge', {
64860
64861     /* Begin Definitions */
64862
64863     extend: 'Ext.chart.axis.Abstract',
64864
64865     /* End Definitions */
64866
64867     /**
64868      * @cfg {Number} minimum (required)
64869      * The minimum value of the interval to be displayed in the axis.
64870      */
64871
64872     /**
64873      * @cfg {Number} maximum (required)
64874      * The maximum value of the interval to be displayed in the axis.
64875      */
64876
64877     /**
64878      * @cfg {Number} steps (required)
64879      * The number of steps and tick marks to add to the interval.
64880      */
64881
64882     /**
64883      * @cfg {Number} [margin=10]
64884      * The offset positioning of the tick marks and labels in pixels.
64885      */
64886
64887     /**
64888      * @cfg {String} title
64889      * The title for the Axis.
64890      */
64891
64892     position: 'gauge',
64893
64894     alias: 'axis.gauge',
64895
64896     drawAxis: function(init) {
64897         var chart = this.chart,
64898             surface = chart.surface,
64899             bbox = chart.chartBBox,
64900             centerX = bbox.x + (bbox.width / 2),
64901             centerY = bbox.y + bbox.height,
64902             margin = this.margin || 10,
64903             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
64904             sprites = [], sprite,
64905             steps = this.steps,
64906             i, pi = Math.PI,
64907             cos = Math.cos,
64908             sin = Math.sin;
64909
64910         if (this.sprites && !chart.resizing) {
64911             this.drawLabel();
64912             return;
64913         }
64914
64915         if (this.margin >= 0) {
64916             if (!this.sprites) {
64917                 //draw circles
64918                 for (i = 0; i <= steps; i++) {
64919                     sprite = surface.add({
64920                         type: 'path',
64921                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64922                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64923                                     'L', centerX + rho * cos(i / steps * pi - pi),
64924                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64925                         stroke: '#ccc'
64926                     });
64927                     sprite.setAttributes({
64928                         hidden: false
64929                     }, true);
64930                     sprites.push(sprite);
64931                 }
64932             } else {
64933                 sprites = this.sprites;
64934                 //draw circles
64935                 for (i = 0; i <= steps; i++) {
64936                     sprites[i].setAttributes({
64937                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64938                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64939                                'L', centerX + rho * cos(i / steps * pi - pi),
64940                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64941                         stroke: '#ccc'
64942                     }, true);
64943                 }
64944             }
64945         }
64946         this.sprites = sprites;
64947         this.drawLabel();
64948         if (this.title) {
64949             this.drawTitle();
64950         }
64951     },
64952
64953     drawTitle: function() {
64954         var me = this,
64955             chart = me.chart,
64956             surface = chart.surface,
64957             bbox = chart.chartBBox,
64958             labelSprite = me.titleSprite,
64959             labelBBox;
64960
64961         if (!labelSprite) {
64962             me.titleSprite = labelSprite = surface.add({
64963                 type: 'text',
64964                 zIndex: 2
64965             });
64966         }
64967         labelSprite.setAttributes(Ext.apply({
64968             text: me.title
64969         }, me.label || {}), true);
64970         labelBBox = labelSprite.getBBox();
64971         labelSprite.setAttributes({
64972             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
64973             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
64974         }, true);
64975     },
64976
64977     /**
64978      * Updates the {@link #title} of this axis.
64979      * @param {String} title
64980      */
64981     setTitle: function(title) {
64982         this.title = title;
64983         this.drawTitle();
64984     },
64985
64986     drawLabel: function() {
64987         var chart = this.chart,
64988             surface = chart.surface,
64989             bbox = chart.chartBBox,
64990             centerX = bbox.x + (bbox.width / 2),
64991             centerY = bbox.y + bbox.height,
64992             margin = this.margin || 10,
64993             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
64994             round = Math.round,
64995             labelArray = [], label,
64996             maxValue = this.maximum || 0,
64997             steps = this.steps, i = 0,
64998             adjY,
64999             pi = Math.PI,
65000             cos = Math.cos,
65001             sin = Math.sin,
65002             labelConf = this.label,
65003             renderer = labelConf.renderer || function(v) { return v; };
65004
65005         if (!this.labelArray) {
65006             //draw scale
65007             for (i = 0; i <= steps; i++) {
65008                 // TODO Adjust for height of text / 2 instead
65009                 adjY = (i === 0 || i === steps) ? 7 : 0;
65010                 label = surface.add({
65011                     type: 'text',
65012                     text: renderer(round(i / steps * maxValue)),
65013                     x: centerX + rho * cos(i / steps * pi - pi),
65014                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
65015                     'text-anchor': 'middle',
65016                     'stroke-width': 0.2,
65017                     zIndex: 10,
65018                     stroke: '#333'
65019                 });
65020                 label.setAttributes({
65021                     hidden: false
65022                 }, true);
65023                 labelArray.push(label);
65024             }
65025         }
65026         else {
65027             labelArray = this.labelArray;
65028             //draw values
65029             for (i = 0; i <= steps; i++) {
65030                 // TODO Adjust for height of text / 2 instead
65031                 adjY = (i === 0 || i === steps) ? 7 : 0;
65032                 labelArray[i].setAttributes({
65033                     text: renderer(round(i / steps * maxValue)),
65034                     x: centerX + rho * cos(i / steps * pi - pi),
65035                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
65036                 }, true);
65037             }
65038         }
65039         this.labelArray = labelArray;
65040     }
65041 });
65042 /**
65043  * @class Ext.chart.axis.Numeric
65044  * @extends Ext.chart.axis.Axis
65045  *
65046  * An axis to handle numeric values. This axis is used for quantitative data as
65047  * opposed to the category axis. You can set mininum and maximum values to the
65048  * axis so that the values are bound to that. If no values are set, then the
65049  * scale will auto-adjust to the values.
65050  *
65051  *     @example
65052  *     var store = Ext.create('Ext.data.JsonStore', {
65053  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65054  *          data: [
65055  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65056  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65057  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65058  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65059  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65060  *          ]
65061  *     });
65062  *
65063  *     Ext.create('Ext.chart.Chart', {
65064  *         renderTo: Ext.getBody(),
65065  *         width: 500,
65066  *         height: 300,
65067  *         store: store,
65068  *         axes: [{
65069  *             type: 'Numeric',
65070  *             grid: true,
65071  *             position: 'left',
65072  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65073  *             title: 'Sample Values',
65074  *             grid: {
65075  *                 odd: {
65076  *                     opacity: 1,
65077  *                     fill: '#ddd',
65078  *                     stroke: '#bbb',
65079  *                     'stroke-width': 1
65080  *                 }
65081  *             },
65082  *             minimum: 0,
65083  *             adjustMinimumByMajorUnit: 0
65084  *         }, {
65085  *             type: 'Category',
65086  *             position: 'bottom',
65087  *             fields: ['name'],
65088  *             title: 'Sample Metrics',
65089  *             grid: true,
65090  *             label: {
65091  *                 rotate: {
65092  *                     degrees: 315
65093  *                 }
65094  *             }
65095  *         }],
65096  *         series: [{
65097  *             type: 'area',
65098  *             highlight: false,
65099  *             axis: 'left',
65100  *             xField: 'name',
65101  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65102  *             style: {
65103  *                 opacity: 0.93
65104  *             }
65105  *         }]
65106  *     });
65107  *
65108  * In this example we create an axis of Numeric type. We set a minimum value so that
65109  * even if all series have values greater than zero, the grid starts at zero. We bind
65110  * the axis onto the left part of the surface by setting `position` to `left`.
65111  * We bind three different store fields to this axis by setting `fields` to an array.
65112  * We set the title of the axis to _Number of Hits_ by using the `title` property.
65113  * We use a `grid` configuration to set odd background rows to a certain style and even rows
65114  * to be transparent/ignored.
65115  */
65116 Ext.define('Ext.chart.axis.Numeric', {
65117
65118     /* Begin Definitions */
65119
65120     extend: 'Ext.chart.axis.Axis',
65121
65122     alternateClassName: 'Ext.chart.NumericAxis',
65123
65124     /* End Definitions */
65125
65126     type: 'numeric',
65127
65128     alias: 'axis.numeric',
65129
65130     constructor: function(config) {
65131         var me = this,
65132             hasLabel = !!(config.label && config.label.renderer),
65133             label;
65134
65135         me.callParent([config]);
65136         label = me.label;
65137         if (me.roundToDecimal === false) {
65138             return;
65139         }
65140         if (!hasLabel) {
65141             label.renderer = function(v) {
65142                 return me.roundToDecimal(v, me.decimals);
65143             };
65144         }
65145     },
65146
65147     roundToDecimal: function(v, dec) {
65148         var val = Math.pow(10, dec || 0);
65149         return Math.floor(v * val) / val;
65150     },
65151
65152     /**
65153      * The minimum value drawn by the axis. If not set explicitly, the axis
65154      * minimum will be calculated automatically.
65155      *
65156      * @property {Number} minimum
65157      */
65158     minimum: NaN,
65159
65160     /**
65161      * The maximum value drawn by the axis. If not set explicitly, the axis
65162      * maximum will be calculated automatically.
65163      *
65164      * @property {Number} maximum
65165      */
65166     maximum: NaN,
65167
65168     /**
65169      * The number of decimals to round the value to.
65170      *
65171      * @property {Number} decimals
65172      */
65173     decimals: 2,
65174
65175     /**
65176      * The scaling algorithm to use on this axis. May be "linear" or
65177      * "logarithmic".  Currently only linear scale is implemented.
65178      *
65179      * @property {String} scale
65180      * @private
65181      */
65182     scale: "linear",
65183
65184     /**
65185      * Indicates the position of the axis relative to the chart
65186      *
65187      * @property {String} position
65188      */
65189     position: 'left',
65190
65191     /**
65192      * Indicates whether to extend maximum beyond data's maximum to the nearest
65193      * majorUnit.
65194      *
65195      * @property {Boolean} adjustMaximumByMajorUnit
65196      */
65197     adjustMaximumByMajorUnit: false,
65198
65199     /**
65200      * Indicates whether to extend the minimum beyond data's minimum to the
65201      * nearest majorUnit.
65202      *
65203      * @property {Boolean} adjustMinimumByMajorUnit
65204      */
65205     adjustMinimumByMajorUnit: false,
65206
65207     // @private apply data.
65208     applyData: function() {
65209         this.callParent();
65210         return this.calcEnds();
65211     }
65212 });
65213
65214 /**
65215  * @class Ext.chart.axis.Radial
65216  * @extends Ext.chart.axis.Abstract
65217  * @ignore
65218  */
65219 Ext.define('Ext.chart.axis.Radial', {
65220
65221     /* Begin Definitions */
65222
65223     extend: 'Ext.chart.axis.Abstract',
65224
65225     /* End Definitions */
65226
65227     position: 'radial',
65228
65229     alias: 'axis.radial',
65230
65231     drawAxis: function(init) {
65232         var chart = this.chart,
65233             surface = chart.surface,
65234             bbox = chart.chartBBox,
65235             store = chart.store,
65236             l = store.getCount(),
65237             centerX = bbox.x + (bbox.width / 2),
65238             centerY = bbox.y + (bbox.height / 2),
65239             rho = Math.min(bbox.width, bbox.height) /2,
65240             sprites = [], sprite,
65241             steps = this.steps,
65242             i, j, pi2 = Math.PI * 2,
65243             cos = Math.cos, sin = Math.sin;
65244
65245         if (this.sprites && !chart.resizing) {
65246             this.drawLabel();
65247             return;
65248         }
65249
65250         if (!this.sprites) {
65251             //draw circles
65252             for (i = 1; i <= steps; i++) {
65253                 sprite = surface.add({
65254                     type: 'circle',
65255                     x: centerX,
65256                     y: centerY,
65257                     radius: Math.max(rho * i / steps, 0),
65258                     stroke: '#ccc'
65259                 });
65260                 sprite.setAttributes({
65261                     hidden: false
65262                 }, true);
65263                 sprites.push(sprite);
65264             }
65265             //draw lines
65266             store.each(function(rec, i) {
65267                 sprite = surface.add({
65268                     type: 'path',
65269                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
65270                     stroke: '#ccc'
65271                 });
65272                 sprite.setAttributes({
65273                     hidden: false
65274                 }, true);
65275                 sprites.push(sprite);
65276             });
65277         } else {
65278             sprites = this.sprites;
65279             //draw circles
65280             for (i = 0; i < steps; i++) {
65281                 sprites[i].setAttributes({
65282                     x: centerX,
65283                     y: centerY,
65284                     radius: Math.max(rho * (i + 1) / steps, 0),
65285                     stroke: '#ccc'
65286                 }, true);
65287             }
65288             //draw lines
65289             store.each(function(rec, j) {
65290                 sprites[i + j].setAttributes({
65291                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
65292                     stroke: '#ccc'
65293                 }, true);
65294             });
65295         }
65296         this.sprites = sprites;
65297
65298         this.drawLabel();
65299     },
65300
65301     drawLabel: function() {
65302         var chart = this.chart,
65303             surface = chart.surface,
65304             bbox = chart.chartBBox,
65305             store = chart.store,
65306             centerX = bbox.x + (bbox.width / 2),
65307             centerY = bbox.y + (bbox.height / 2),
65308             rho = Math.min(bbox.width, bbox.height) /2,
65309             max = Math.max, round = Math.round,
65310             labelArray = [], label,
65311             fields = [], nfields,
65312             categories = [], xField,
65313             aggregate = !this.maximum,
65314             maxValue = this.maximum || 0,
65315             steps = this.steps, i = 0, j, dx, dy,
65316             pi2 = Math.PI * 2,
65317             cos = Math.cos, sin = Math.sin,
65318             display = this.label.display,
65319             draw = display !== 'none',
65320             margin = 10;
65321
65322         if (!draw) {
65323             return;
65324         }
65325
65326         //get all rendered fields
65327         chart.series.each(function(series) {
65328             fields.push(series.yField);
65329             xField = series.xField;
65330         });
65331         
65332         //get maxValue to interpolate
65333         store.each(function(record, i) {
65334             if (aggregate) {
65335                 for (i = 0, nfields = fields.length; i < nfields; i++) {
65336                     maxValue = max(+record.get(fields[i]), maxValue);
65337                 }
65338             }
65339             categories.push(record.get(xField));
65340         });
65341         if (!this.labelArray) {
65342             if (display != 'categories') {
65343                 //draw scale
65344                 for (i = 1; i <= steps; i++) {
65345                     label = surface.add({
65346                         type: 'text',
65347                         text: round(i / steps * maxValue),
65348                         x: centerX,
65349                         y: centerY - rho * i / steps,
65350                         'text-anchor': 'middle',
65351                         'stroke-width': 0.1,
65352                         stroke: '#333'
65353                     });
65354                     label.setAttributes({
65355                         hidden: false
65356                     }, true);
65357                     labelArray.push(label);
65358                 }
65359             }
65360             if (display != 'scale') {
65361                 //draw text
65362                 for (j = 0, steps = categories.length; j < steps; j++) {
65363                     dx = cos(j / steps * pi2) * (rho + margin);
65364                     dy = sin(j / steps * pi2) * (rho + margin);
65365                     label = surface.add({
65366                         type: 'text',
65367                         text: categories[j],
65368                         x: centerX + dx,
65369                         y: centerY + dy,
65370                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65371                     });
65372                     label.setAttributes({
65373                         hidden: false
65374                     }, true);
65375                     labelArray.push(label);
65376                 }
65377             }
65378         }
65379         else {
65380             labelArray = this.labelArray;
65381             if (display != 'categories') {
65382                 //draw values
65383                 for (i = 0; i < steps; i++) {
65384                     labelArray[i].setAttributes({
65385                         text: round((i + 1) / steps * maxValue),
65386                         x: centerX,
65387                         y: centerY - rho * (i + 1) / steps,
65388                         'text-anchor': 'middle',
65389                         'stroke-width': 0.1,
65390                         stroke: '#333'
65391                     }, true);
65392                 }
65393             }
65394             if (display != 'scale') {
65395                 //draw text
65396                 for (j = 0, steps = categories.length; j < steps; j++) {
65397                     dx = cos(j / steps * pi2) * (rho + margin);
65398                     dy = sin(j / steps * pi2) * (rho + margin);
65399                     if (labelArray[i + j]) {
65400                         labelArray[i + j].setAttributes({
65401                             type: 'text',
65402                             text: categories[j],
65403                             x: centerX + dx,
65404                             y: centerY + dy,
65405                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65406                         }, true);
65407                     }
65408                 }
65409             }
65410         }
65411         this.labelArray = labelArray;
65412     }
65413 });
65414 /**
65415  * @author Ed Spencer
65416  *
65417  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
65418  * but offers a set of methods used by both of those subclasses.
65419  * 
65420  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
65421  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
65422  * AbstractStore is and is not.
65423  * 
65424  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
65425  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
65426  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
65427  * 
65428  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
65429  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
65430  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
65431  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
65432  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
65433  * 
65434  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
65435  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
65436  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
65437  * {@link Ext.data.Store#filter filter} methods.
65438  */
65439 Ext.define('Ext.data.AbstractStore', {
65440     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
65441     
65442     mixins: {
65443         observable: 'Ext.util.Observable',
65444         sortable: 'Ext.util.Sortable'
65445     },
65446     
65447     statics: {
65448         create: function(store){
65449             if (!store.isStore) {
65450                 if (!store.type) {
65451                     store.type = 'store';
65452                 }
65453                 store = Ext.createByAlias('store.' + store.type, store);
65454             }
65455             return store;
65456         }    
65457     },
65458     
65459     remoteSort  : false,
65460     remoteFilter: false,
65461
65462     /**
65463      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
65464      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
65465      * see {@link #setProxy} for details.
65466      */
65467
65468     /**
65469      * @cfg {Boolean/Object} autoLoad
65470      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
65471      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
65472      * Defaults to false.
65473      */
65474     autoLoad: false,
65475
65476     /**
65477      * @cfg {Boolean} autoSync
65478      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
65479      */
65480     autoSync: false,
65481
65482     /**
65483      * @property {String} batchUpdateMode
65484      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
65485      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
65486      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
65487      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
65488      */
65489     batchUpdateMode: 'operation',
65490
65491     /**
65492      * @property {Boolean} filterOnLoad
65493      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
65494      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
65495      */
65496     filterOnLoad: true,
65497
65498     /**
65499      * @property {Boolean} sortOnLoad
65500      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
65501      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
65502      */
65503     sortOnLoad: true,
65504
65505     /**
65506      * @property {Boolean} implicitModel
65507      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
65508      * constructor instead of a model constructor or name.
65509      * @private
65510      */
65511     implicitModel: false,
65512
65513     /**
65514      * @property {String} defaultProxyType
65515      * The string type of the Proxy to create if none is specified. This defaults to creating a
65516      * {@link Ext.data.proxy.Memory memory proxy}.
65517      */
65518     defaultProxyType: 'memory',
65519
65520     /**
65521      * @property {Boolean} isDestroyed
65522      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
65523      * as it will not function correctly any more.
65524      */
65525     isDestroyed: false,
65526
65527     isStore: true,
65528
65529     /**
65530      * @cfg {String} storeId
65531      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
65532      * making it easy to reuse elsewhere. Defaults to undefined.
65533      */
65534     
65535     /**
65536      * @cfg {Object[]} fields
65537      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
65538      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
65539      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
65540      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
65541      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
65542      * config.
65543      */
65544
65545     /**
65546      * @cfg {String} model
65547      * Name of the {@link Ext.data.Model Model} associated with this store.
65548      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
65549      */
65550
65551     sortRoot: 'data',
65552     
65553     //documented above
65554     constructor: function(config) {
65555         var me = this,
65556             filters;
65557         
65558         me.addEvents(
65559             /**
65560              * @event add
65561              * Fired when a Model instance has been added to this Store
65562              * @param {Ext.data.Store} store The store
65563              * @param {Ext.data.Model[]} records The Model instances that were added
65564              * @param {Number} index The index at which the instances were inserted
65565              */
65566             'add',
65567
65568             /**
65569              * @event remove
65570              * Fired when a Model instance has been removed from this Store
65571              * @param {Ext.data.Store} store The Store object
65572              * @param {Ext.data.Model} record The record that was removed
65573              * @param {Number} index The index of the record that was removed
65574              */
65575             'remove',
65576             
65577             /**
65578              * @event update
65579              * Fires when a Model instance has been updated
65580              * @param {Ext.data.Store} this
65581              * @param {Ext.data.Model} record The Model instance that was updated
65582              * @param {String} operation The update operation being performed. Value may be one of:
65583              *
65584              *     Ext.data.Model.EDIT
65585              *     Ext.data.Model.REJECT
65586              *     Ext.data.Model.COMMIT
65587              */
65588             'update',
65589
65590             /**
65591              * @event datachanged
65592              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
65593              * records, or updating the data in existing records
65594              * @param {Ext.data.Store} this The data store
65595              */
65596             'datachanged',
65597
65598             /**
65599              * @event beforeload
65600              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
65601              * action will be canceled.
65602              * @param {Ext.data.Store} store This Store
65603              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
65604              * load the Store
65605              */
65606             'beforeload',
65607
65608             /**
65609              * @event load
65610              * Fires whenever the store reads data from a remote data source.
65611              * @param {Ext.data.Store} this
65612              * @param {Ext.data.Model[]} records An array of records
65613              * @param {Boolean} successful True if the operation was successful.
65614              */
65615             'load',
65616             
65617             /**
65618              * @event write
65619              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
65620              * @param {Ext.data.Store} store This Store
65621              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
65622              * the write
65623              */
65624             'write',
65625
65626             /**
65627              * @event beforesync
65628              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
65629              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
65630              */
65631             'beforesync',
65632             /**
65633              * @event clear
65634              * Fired after the {@link #removeAll} method is called.
65635              * @param {Ext.data.Store} this
65636              */
65637             'clear'
65638         );
65639         
65640         Ext.apply(me, config);
65641         // don't use *config* anymore from here on... use *me* instead...
65642
65643         /**
65644          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
65645          * at which point this is cleared.
65646          * @private
65647          * @property {Ext.data.Model[]} removed
65648          */
65649         me.removed = [];
65650
65651         me.mixins.observable.constructor.apply(me, arguments);
65652         me.model = Ext.ModelManager.getModel(me.model);
65653
65654         /**
65655          * @property {Object} modelDefaults
65656          * @private
65657          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
65658          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
65659          * for examples. This should not need to be used by application developers.
65660          */
65661         Ext.applyIf(me, {
65662             modelDefaults: {}
65663         });
65664
65665         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
65666         if (!me.model && me.fields) {
65667             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
65668                 extend: 'Ext.data.Model',
65669                 fields: me.fields,
65670                 proxy: me.proxy || me.defaultProxyType
65671             });
65672
65673             delete me.fields;
65674
65675             me.implicitModel = true;
65676         }
65677         
65678
65679         //ensures that the Proxy is instantiated correctly
65680         me.setProxy(me.proxy || me.model.getProxy());
65681
65682         if (me.id && !me.storeId) {
65683             me.storeId = me.id;
65684             delete me.id;
65685         }
65686
65687         if (me.storeId) {
65688             Ext.data.StoreManager.register(me);
65689         }
65690         
65691         me.mixins.sortable.initSortable.call(me);        
65692         
65693         /**
65694          * @property {Ext.util.MixedCollection} filters
65695          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
65696          */
65697         filters = me.decodeFilters(me.filters);
65698         me.filters = Ext.create('Ext.util.MixedCollection');
65699         me.filters.addAll(filters);
65700     },
65701
65702     /**
65703      * Sets the Store's Proxy by string, config object or Proxy instance
65704      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
65705      * or an Ext.data.proxy.Proxy instance
65706      * @return {Ext.data.proxy.Proxy} The attached Proxy object
65707      */
65708     setProxy: function(proxy) {
65709         var me = this;
65710         
65711         if (proxy instanceof Ext.data.proxy.Proxy) {
65712             proxy.setModel(me.model);
65713         } else {
65714             if (Ext.isString(proxy)) {
65715                 proxy = {
65716                     type: proxy    
65717                 };
65718             }
65719             Ext.applyIf(proxy, {
65720                 model: me.model
65721             });
65722             
65723             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
65724         }
65725         
65726         me.proxy = proxy;
65727         
65728         return me.proxy;
65729     },
65730
65731     /**
65732      * Returns the proxy currently attached to this proxy instance
65733      * @return {Ext.data.proxy.Proxy} The Proxy instance
65734      */
65735     getProxy: function() {
65736         return this.proxy;
65737     },
65738
65739     //saves any phantom records
65740     create: function(data, options) {
65741         var me = this,
65742             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
65743             operation;
65744         
65745         options = options || {};
65746
65747         Ext.applyIf(options, {
65748             action : 'create',
65749             records: [instance]
65750         });
65751
65752         operation = Ext.create('Ext.data.Operation', options);
65753
65754         me.proxy.create(operation, me.onProxyWrite, me);
65755         
65756         return instance;
65757     },
65758
65759     read: function() {
65760         return this.load.apply(this, arguments);
65761     },
65762
65763     onProxyRead: Ext.emptyFn,
65764
65765     update: function(options) {
65766         var me = this,
65767             operation;
65768         options = options || {};
65769
65770         Ext.applyIf(options, {
65771             action : 'update',
65772             records: me.getUpdatedRecords()
65773         });
65774
65775         operation = Ext.create('Ext.data.Operation', options);
65776
65777         return me.proxy.update(operation, me.onProxyWrite, me);
65778     },
65779
65780     /**
65781      * @private
65782      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
65783      * the updates provided by the Proxy
65784      */
65785     onProxyWrite: function(operation) {
65786         var me = this,
65787             success = operation.wasSuccessful(),
65788             records = operation.getRecords();
65789
65790         switch (operation.action) {
65791             case 'create':
65792                 me.onCreateRecords(records, operation, success);
65793                 break;
65794             case 'update':
65795                 me.onUpdateRecords(records, operation, success);
65796                 break;
65797             case 'destroy':
65798                 me.onDestroyRecords(records, operation, success);
65799                 break;
65800         }
65801
65802         if (success) {
65803             me.fireEvent('write', me, operation);
65804             me.fireEvent('datachanged', me);
65805         }
65806         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
65807         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
65808     },
65809
65810
65811     //tells the attached proxy to destroy the given records
65812     destroy: function(options) {
65813         var me = this,
65814             operation;
65815             
65816         options = options || {};
65817
65818         Ext.applyIf(options, {
65819             action : 'destroy',
65820             records: me.getRemovedRecords()
65821         });
65822
65823         operation = Ext.create('Ext.data.Operation', options);
65824
65825         return me.proxy.destroy(operation, me.onProxyWrite, me);
65826     },
65827
65828     /**
65829      * @private
65830      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
65831      * to onProxyWrite.
65832      */
65833     onBatchOperationComplete: function(batch, operation) {
65834         return this.onProxyWrite(operation);
65835     },
65836
65837     /**
65838      * @private
65839      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
65840      * and updates the Store's internal data MixedCollection.
65841      */
65842     onBatchComplete: function(batch, operation) {
65843         var me = this,
65844             operations = batch.operations,
65845             length = operations.length,
65846             i;
65847
65848         me.suspendEvents();
65849
65850         for (i = 0; i < length; i++) {
65851             me.onProxyWrite(operations[i]);
65852         }
65853
65854         me.resumeEvents();
65855
65856         me.fireEvent('datachanged', me);
65857     },
65858
65859     onBatchException: function(batch, operation) {
65860         // //decide what to do... could continue with the next operation
65861         // batch.start();
65862         //
65863         // //or retry the last operation
65864         // batch.retry();
65865     },
65866
65867     /**
65868      * @private
65869      * Filter function for new records.
65870      */
65871     filterNew: function(item) {
65872         // only want phantom records that are valid
65873         return item.phantom === true && item.isValid();
65874     },
65875
65876     /**
65877      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
65878      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
65879      * @return {Ext.data.Model[]} The Model instances
65880      */
65881     getNewRecords: function() {
65882         return [];
65883     },
65884
65885     /**
65886      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
65887      * @return {Ext.data.Model[]} The updated Model instances
65888      */
65889     getUpdatedRecords: function() {
65890         return [];
65891     },
65892
65893     /**
65894      * @private
65895      * Filter function for updated records.
65896      */
65897     filterUpdated: function(item) {
65898         // only want dirty records, not phantoms that are valid
65899         return item.dirty === true && item.phantom !== true && item.isValid();
65900     },
65901
65902     /**
65903      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
65904      * @return {Ext.data.Model[]} The removed Model instances
65905      */
65906     getRemovedRecords: function() {
65907         return this.removed;
65908     },
65909
65910     filter: function(filters, value) {
65911
65912     },
65913
65914     /**
65915      * @private
65916      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
65917      * @param {Object[]} filters The filters array
65918      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
65919      */
65920     decodeFilters: function(filters) {
65921         if (!Ext.isArray(filters)) {
65922             if (filters === undefined) {
65923                 filters = [];
65924             } else {
65925                 filters = [filters];
65926             }
65927         }
65928
65929         var length = filters.length,
65930             Filter = Ext.util.Filter,
65931             config, i;
65932
65933         for (i = 0; i < length; i++) {
65934             config = filters[i];
65935
65936             if (!(config instanceof Filter)) {
65937                 Ext.apply(config, {
65938                     root: 'data'
65939                 });
65940
65941                 //support for 3.x style filters where a function can be defined as 'fn'
65942                 if (config.fn) {
65943                     config.filterFn = config.fn;
65944                 }
65945
65946                 //support a function to be passed as a filter definition
65947                 if (typeof config == 'function') {
65948                     config = {
65949                         filterFn: config
65950                     };
65951                 }
65952
65953                 filters[i] = new Filter(config);
65954             }
65955         }
65956
65957         return filters;
65958     },
65959
65960     clearFilter: function(supressEvent) {
65961
65962     },
65963
65964     isFiltered: function() {
65965
65966     },
65967
65968     filterBy: function(fn, scope) {
65969
65970     },
65971     
65972     /**
65973      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
65974      * and deleted records in the store, updating the Store's internal representation of the records
65975      * as each operation completes.
65976      */
65977     sync: function() {
65978         var me        = this,
65979             options   = {},
65980             toCreate  = me.getNewRecords(),
65981             toUpdate  = me.getUpdatedRecords(),
65982             toDestroy = me.getRemovedRecords(),
65983             needsSync = false;
65984
65985         if (toCreate.length > 0) {
65986             options.create = toCreate;
65987             needsSync = true;
65988         }
65989
65990         if (toUpdate.length > 0) {
65991             options.update = toUpdate;
65992             needsSync = true;
65993         }
65994
65995         if (toDestroy.length > 0) {
65996             options.destroy = toDestroy;
65997             needsSync = true;
65998         }
65999
66000         if (needsSync && me.fireEvent('beforesync', options) !== false) {
66001             me.proxy.batch(options, me.getBatchListeners());
66002         }
66003     },
66004
66005
66006     /**
66007      * @private
66008      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
66009      * This is broken out into a separate function to allow for customisation of the listeners
66010      * @return {Object} The listeners object
66011      */
66012     getBatchListeners: function() {
66013         var me = this,
66014             listeners = {
66015                 scope: me,
66016                 exception: me.onBatchException
66017             };
66018
66019         if (me.batchUpdateMode == 'operation') {
66020             listeners.operationcomplete = me.onBatchOperationComplete;
66021         } else {
66022             listeners.complete = me.onBatchComplete;
66023         }
66024
66025         return listeners;
66026     },
66027
66028     //deprecated, will be removed in 5.0
66029     save: function() {
66030         return this.sync.apply(this, arguments);
66031     },
66032
66033     /**
66034      * Loads the Store using its configured {@link #proxy}.
66035      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
66036      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
66037      */
66038     load: function(options) {
66039         var me = this,
66040             operation;
66041
66042         options = options || {};
66043
66044         Ext.applyIf(options, {
66045             action : 'read',
66046             filters: me.filters.items,
66047             sorters: me.getSorters()
66048         });
66049         
66050         operation = Ext.create('Ext.data.Operation', options);
66051
66052         if (me.fireEvent('beforeload', me, operation) !== false) {
66053             me.loading = true;
66054             me.proxy.read(operation, me.onProxyLoad, me);
66055         }
66056         
66057         return me;
66058     },
66059
66060     /**
66061      * @private
66062      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66063      * @param {Ext.data.Model} record The model instance that was edited
66064      */
66065     afterEdit : function(record) {
66066         var me = this;
66067         
66068         if (me.autoSync) {
66069             me.sync();
66070         }
66071         
66072         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
66073     },
66074
66075     /**
66076      * @private
66077      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
66078      * @param {Ext.data.Model} record The model instance that was edited
66079      */
66080     afterReject : function(record) {
66081         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
66082     },
66083
66084     /**
66085      * @private
66086      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66087      * @param {Ext.data.Model} record The model instance that was edited
66088      */
66089     afterCommit : function(record) {
66090         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
66091     },
66092
66093     clearData: Ext.emptyFn,
66094
66095     destroyStore: function() {
66096         var me = this;
66097         
66098         if (!me.isDestroyed) {
66099             if (me.storeId) {
66100                 Ext.data.StoreManager.unregister(me);
66101             }
66102             me.clearData();
66103             me.data = null;
66104             me.tree = null;
66105             // Ext.destroy(this.proxy);
66106             me.reader = me.writer = null;
66107             me.clearListeners();
66108             me.isDestroyed = true;
66109
66110             if (me.implicitModel) {
66111                 Ext.destroy(me.model);
66112             }
66113         }
66114     },
66115     
66116     doSort: function(sorterFn) {
66117         var me = this;
66118         if (me.remoteSort) {
66119             //the load function will pick up the new sorters and request the sorted data from the proxy
66120             me.load();
66121         } else {
66122             me.data.sortBy(sorterFn);
66123             me.fireEvent('datachanged', me);
66124         }
66125     },
66126
66127     getCount: Ext.emptyFn,
66128
66129     getById: Ext.emptyFn,
66130     
66131     /**
66132      * Removes all records from the store. This method does a "fast remove",
66133      * individual remove events are not called. The {@link #clear} event is
66134      * fired upon completion.
66135      * @method
66136      */
66137     removeAll: Ext.emptyFn,
66138     // individual substores should implement a "fast" remove
66139     // and fire a clear event afterwards
66140
66141     /**
66142      * Returns true if the Store is currently performing a load operation
66143      * @return {Boolean} True if the Store is currently loading
66144      */
66145     isLoading: function() {
66146         return !!this.loading;
66147      }
66148 });
66149
66150 /**
66151  * @class Ext.util.Grouper
66152  * @extends Ext.util.Sorter
66153
66154 Represents a single grouper that can be applied to a Store. The grouper works
66155 in the same fashion as the {@link Ext.util.Sorter}.
66156
66157  * @markdown
66158  */
66159  
66160 Ext.define('Ext.util.Grouper', {
66161
66162     /* Begin Definitions */
66163
66164     extend: 'Ext.util.Sorter',
66165
66166     /* End Definitions */
66167
66168     /**
66169      * Returns the value for grouping to be used.
66170      * @param {Ext.data.Model} instance The Model instance
66171      * @return {String} The group string for this model
66172      */
66173     getGroupString: function(instance) {
66174         return instance.get(this.property);
66175     }
66176 });
66177 /**
66178  * @author Ed Spencer
66179  * @class Ext.data.Store
66180  * @extends Ext.data.AbstractStore
66181  *
66182  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
66183  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
66184  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
66185  *
66186  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
66187  *
66188 <pre><code>
66189 // Set up a {@link Ext.data.Model model} to use in our Store
66190 Ext.define('User', {
66191     extend: 'Ext.data.Model',
66192     fields: [
66193         {name: 'firstName', type: 'string'},
66194         {name: 'lastName',  type: 'string'},
66195         {name: 'age',       type: 'int'},
66196         {name: 'eyeColor',  type: 'string'}
66197     ]
66198 });
66199
66200 var myStore = Ext.create('Ext.data.Store', {
66201     model: 'User',
66202     proxy: {
66203         type: 'ajax',
66204         url : '/users.json',
66205         reader: {
66206             type: 'json',
66207             root: 'users'
66208         }
66209     },
66210     autoLoad: true
66211 });
66212 </code></pre>
66213
66214  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
66215  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
66216  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
66217  *
66218  * <p><u>Inline data</u></p>
66219  *
66220  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
66221  * into Model instances:</p>
66222  *
66223 <pre><code>
66224 Ext.create('Ext.data.Store', {
66225     model: 'User',
66226     data : [
66227         {firstName: 'Ed',    lastName: 'Spencer'},
66228         {firstName: 'Tommy', lastName: 'Maintz'},
66229         {firstName: 'Aaron', lastName: 'Conran'},
66230         {firstName: 'Jamie', lastName: 'Avins'}
66231     ]
66232 });
66233 </code></pre>
66234  *
66235  * <p>Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
66236  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
66237  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
66238  *
66239  * <p>Additional data can also be loaded locally using {@link #add}.</p>
66240  *
66241  * <p><u>Loading Nested Data</u></p>
66242  *
66243  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
66244  * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load a nested dataset
66245  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
66246  * docs for a full explanation:</p>
66247  *
66248 <pre><code>
66249 var store = Ext.create('Ext.data.Store', {
66250     autoLoad: true,
66251     model: "User",
66252     proxy: {
66253         type: 'ajax',
66254         url : 'users.json',
66255         reader: {
66256             type: 'json',
66257             root: 'users'
66258         }
66259     }
66260 });
66261 </code></pre>
66262  *
66263  * <p>Which would consume a response like this:</p>
66264  *
66265 <pre><code>
66266 {
66267     "users": [
66268         {
66269             "id": 1,
66270             "name": "Ed",
66271             "orders": [
66272                 {
66273                     "id": 10,
66274                     "total": 10.76,
66275                     "status": "invoiced"
66276                 },
66277                 {
66278                     "id": 11,
66279                     "total": 13.45,
66280                     "status": "shipped"
66281                 }
66282             ]
66283         }
66284     ]
66285 }
66286 </code></pre>
66287  *
66288  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
66289  *
66290  * <p><u>Filtering and Sorting</u></p>
66291  *
66292  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
66293  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
66294  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
66295  *
66296 <pre><code>
66297 var store = Ext.create('Ext.data.Store', {
66298     model: 'User',
66299     sorters: [
66300         {
66301             property : 'age',
66302             direction: 'DESC'
66303         },
66304         {
66305             property : 'firstName',
66306             direction: 'ASC'
66307         }
66308     ],
66309
66310     filters: [
66311         {
66312             property: 'firstName',
66313             value   : /Ed/
66314         }
66315     ]
66316 });
66317 </code></pre>
66318  *
66319  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
66320  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
66321  * perform these operations instead.</p>
66322  *
66323  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
66324  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
66325  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
66326  *
66327 <pre><code>
66328 store.filter('eyeColor', 'Brown');
66329 </code></pre>
66330  *
66331  * <p>Change the sorting at any time by calling {@link #sort}:</p>
66332  *
66333 <pre><code>
66334 store.sort('height', 'ASC');
66335 </code></pre>
66336  *
66337  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
66338  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
66339  * to the MixedCollection:</p>
66340  *
66341 <pre><code>
66342 store.sorters.add(new Ext.util.Sorter({
66343     property : 'shoeSize',
66344     direction: 'ASC'
66345 }));
66346
66347 store.sort();
66348 </code></pre>
66349  *
66350  * <p><u>Registering with StoreManager</u></p>
66351  *
66352  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
66353  * This makes it easy to reuse the same store in multiple views:</p>
66354  *
66355  <pre><code>
66356 //this store can be used several times
66357 Ext.create('Ext.data.Store', {
66358     model: 'User',
66359     storeId: 'usersStore'
66360 });
66361
66362 new Ext.List({
66363     store: 'usersStore',
66364
66365     //other config goes here
66366 });
66367
66368 new Ext.view.View({
66369     store: 'usersStore',
66370
66371     //other config goes here
66372 });
66373 </code></pre>
66374  *
66375  * <p><u>Further Reading</u></p>
66376  *
66377  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
66378  * pieces and how they fit together, see:</p>
66379  *
66380  * <ul style="list-style-type: disc; padding-left: 25px">
66381  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
66382  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
66383  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
66384  * </ul>
66385  *
66386  */
66387 Ext.define('Ext.data.Store', {
66388     extend: 'Ext.data.AbstractStore',
66389
66390     alias: 'store.store',
66391
66392     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
66393     uses: ['Ext.data.proxy.Memory'],
66394
66395     /**
66396      * @cfg {Boolean} remoteSort
66397      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
66398      */
66399     remoteSort: false,
66400
66401     /**
66402      * @cfg {Boolean} remoteFilter
66403      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
66404      */
66405     remoteFilter: false,
66406
66407     /**
66408      * @cfg {Boolean} remoteGroup
66409      * True if the grouping should apply on the server side, false if it is local only.  If the
66410      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
66411      * helper, automatically sending the grouping information to the server.
66412      */
66413     remoteGroup : false,
66414
66415     /**
66416      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
66417      * object or a Proxy instance - see {@link #setProxy} for details.
66418      */
66419
66420     /**
66421      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
66422      */
66423
66424     /**
66425      * @property {String} groupField
66426      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
66427      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
66428      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
66429      */
66430     groupField: undefined,
66431
66432     /**
66433      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
66434      * @property groupDir
66435      * @type String
66436      */
66437     groupDir: "ASC",
66438
66439     /**
66440      * @cfg {Number} pageSize
66441      * The number of records considered to form a 'page'. This is used to power the built-in
66442      * paging using the nextPage and previousPage functions. Defaults to 25.
66443      */
66444     pageSize: 25,
66445
66446     /**
66447      * The page that the Store has most recently loaded (see {@link #loadPage})
66448      * @property currentPage
66449      * @type Number
66450      */
66451     currentPage: 1,
66452
66453     /**
66454      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
66455      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
66456      * large data sets to be loaded one page at a time but rendered all together.
66457      */
66458     clearOnPageLoad: true,
66459
66460     /**
66461      * @property {Boolean} loading
66462      * True if the Store is currently loading via its Proxy
66463      * @private
66464      */
66465     loading: false,
66466
66467     /**
66468      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
66469      * causing the sorters to be reapplied after filtering. Defaults to true
66470      */
66471     sortOnFilter: true,
66472
66473     /**
66474      * @cfg {Boolean} buffered
66475      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
66476      * tell the store to pre-fetch records ahead of a time.
66477      */
66478     buffered: false,
66479
66480     /**
66481      * @cfg {Number} purgePageCount
66482      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
66483      * This option is only relevant when the {@link #buffered} option is set to true.
66484      */
66485     purgePageCount: 5,
66486
66487     isStore: true,
66488
66489     onClassExtended: function(cls, data) {
66490         var model = data.model;
66491
66492         if (typeof model == 'string') {
66493             var onBeforeClassCreated = data.onBeforeClassCreated;
66494
66495             data.onBeforeClassCreated = function(cls, data) {
66496                 var me = this;
66497
66498                 Ext.require(model, function() {
66499                     onBeforeClassCreated.call(me, cls, data);
66500                 });
66501             };
66502         }
66503     },
66504
66505     /**
66506      * Creates the store.
66507      * @param {Object} config (optional) Config object
66508      */
66509     constructor: function(config) {
66510         // Clone the config so we don't modify the original config object
66511         config = Ext.Object.merge({}, config);
66512
66513         var me = this,
66514             groupers = config.groupers || me.groupers,
66515             groupField = config.groupField || me.groupField,
66516             proxy,
66517             data;
66518
66519         if (config.buffered || me.buffered) {
66520             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
66521                 return record.index;
66522             });
66523             me.pendingRequests = [];
66524             me.pagesRequested = [];
66525
66526             me.sortOnLoad = false;
66527             me.filterOnLoad = false;
66528         }
66529
66530         me.addEvents(
66531             /**
66532              * @event beforeprefetch
66533              * Fires before a prefetch occurs. Return false to cancel.
66534              * @param {Ext.data.Store} this
66535              * @param {Ext.data.Operation} operation The associated operation
66536              */
66537             'beforeprefetch',
66538             /**
66539              * @event groupchange
66540              * Fired whenever the grouping in the grid changes
66541              * @param {Ext.data.Store} store The store
66542              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
66543              */
66544             'groupchange',
66545             /**
66546              * @event load
66547              * Fires whenever records have been prefetched
66548              * @param {Ext.data.Store} this
66549              * @param {Ext.util.Grouper[]} records An array of records
66550              * @param {Boolean} successful True if the operation was successful.
66551              * @param {Ext.data.Operation} operation The associated operation
66552              */
66553             'prefetch'
66554         );
66555         data = config.data || me.data;
66556
66557         /**
66558          * The MixedCollection that holds this store's local cache of records
66559          * @property data
66560          * @type Ext.util.MixedCollection
66561          */
66562         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
66563             return record.internalId;
66564         });
66565
66566         if (data) {
66567             me.inlineData = data;
66568             delete config.data;
66569         }
66570
66571         if (!groupers && groupField) {
66572             groupers = [{
66573                 property : groupField,
66574                 direction: config.groupDir || me.groupDir
66575             }];
66576         }
66577         delete config.groupers;
66578
66579         /**
66580          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
66581          * @property groupers
66582          * @type Ext.util.MixedCollection
66583          */
66584         me.groupers = Ext.create('Ext.util.MixedCollection');
66585         me.groupers.addAll(me.decodeGroupers(groupers));
66586
66587         this.callParent([config]);
66588         // don't use *config* anymore from here on... use *me* instead...
66589
66590         if (me.groupers.items.length) {
66591             me.sort(me.groupers.items, 'prepend', false);
66592         }
66593
66594         proxy = me.proxy;
66595         data = me.inlineData;
66596
66597         if (data) {
66598             if (proxy instanceof Ext.data.proxy.Memory) {
66599                 proxy.data = data;
66600                 me.read();
66601             } else {
66602                 me.add.apply(me, data);
66603             }
66604
66605             me.sort();
66606             delete me.inlineData;
66607         } else if (me.autoLoad) {
66608             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
66609             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
66610             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
66611         }
66612     },
66613
66614     onBeforeSort: function() {
66615         var groupers = this.groupers;
66616         if (groupers.getCount() > 0) {
66617             this.sort(groupers.items, 'prepend', false);
66618         }
66619     },
66620
66621     /**
66622      * @private
66623      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
66624      * @param {Object[]} groupers The groupers array
66625      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
66626      */
66627     decodeGroupers: function(groupers) {
66628         if (!Ext.isArray(groupers)) {
66629             if (groupers === undefined) {
66630                 groupers = [];
66631             } else {
66632                 groupers = [groupers];
66633             }
66634         }
66635
66636         var length  = groupers.length,
66637             Grouper = Ext.util.Grouper,
66638             config, i;
66639
66640         for (i = 0; i < length; i++) {
66641             config = groupers[i];
66642
66643             if (!(config instanceof Grouper)) {
66644                 if (Ext.isString(config)) {
66645                     config = {
66646                         property: config
66647                     };
66648                 }
66649
66650                 Ext.applyIf(config, {
66651                     root     : 'data',
66652                     direction: "ASC"
66653                 });
66654
66655                 //support for 3.x style sorters where a function can be defined as 'fn'
66656                 if (config.fn) {
66657                     config.sorterFn = config.fn;
66658                 }
66659
66660                 //support a function to be passed as a sorter definition
66661                 if (typeof config == 'function') {
66662                     config = {
66663                         sorterFn: config
66664                     };
66665                 }
66666
66667                 groupers[i] = new Grouper(config);
66668             }
66669         }
66670
66671         return groupers;
66672     },
66673
66674     /**
66675      * Group data in the store
66676      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
66677      * or an Array of grouper configurations.
66678      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
66679      */
66680     group: function(groupers, direction) {
66681         var me = this,
66682             hasNew = false,
66683             grouper,
66684             newGroupers;
66685
66686         if (Ext.isArray(groupers)) {
66687             newGroupers = groupers;
66688         } else if (Ext.isObject(groupers)) {
66689             newGroupers = [groupers];
66690         } else if (Ext.isString(groupers)) {
66691             grouper = me.groupers.get(groupers);
66692
66693             if (!grouper) {
66694                 grouper = {
66695                     property : groupers,
66696                     direction: direction
66697                 };
66698                 newGroupers = [grouper];
66699             } else if (direction === undefined) {
66700                 grouper.toggle();
66701             } else {
66702                 grouper.setDirection(direction);
66703             }
66704         }
66705
66706         if (newGroupers && newGroupers.length) {
66707             hasNew = true;
66708             newGroupers = me.decodeGroupers(newGroupers);
66709             me.groupers.clear();
66710             me.groupers.addAll(newGroupers);
66711         }
66712
66713         if (me.remoteGroup) {
66714             me.load({
66715                 scope: me,
66716                 callback: me.fireGroupChange
66717             });
66718         } else {
66719             // need to explicitly force a sort if we have groupers
66720             me.sort(null, null, null, hasNew);
66721             me.fireGroupChange();
66722         }
66723     },
66724
66725     /**
66726      * Clear any groupers in the store
66727      */
66728     clearGrouping: function(){
66729         var me = this;
66730         // Clear any groupers we pushed on to the sorters
66731         me.groupers.each(function(grouper){
66732             me.sorters.remove(grouper);
66733         });
66734         me.groupers.clear();
66735         if (me.remoteGroup) {
66736             me.load({
66737                 scope: me,
66738                 callback: me.fireGroupChange
66739             });
66740         } else {
66741             me.sort();
66742             me.fireEvent('groupchange', me, me.groupers);
66743         }
66744     },
66745
66746     /**
66747      * Checks if the store is currently grouped
66748      * @return {Boolean} True if the store is grouped.
66749      */
66750     isGrouped: function() {
66751         return this.groupers.getCount() > 0;
66752     },
66753
66754     /**
66755      * Fires the groupchange event. Abstracted out so we can use it
66756      * as a callback
66757      * @private
66758      */
66759     fireGroupChange: function(){
66760         this.fireEvent('groupchange', this, this.groupers);
66761     },
66762
66763     /**
66764      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
66765      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
66766 <pre><code>
66767 var myStore = Ext.create('Ext.data.Store', {
66768     groupField: 'color',
66769     groupDir  : 'DESC'
66770 });
66771
66772 myStore.getGroups(); //returns:
66773 [
66774     {
66775         name: 'yellow',
66776         children: [
66777             //all records where the color field is 'yellow'
66778         ]
66779     },
66780     {
66781         name: 'red',
66782         children: [
66783             //all records where the color field is 'red'
66784         ]
66785     }
66786 ]
66787 </code></pre>
66788      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
66789      * @return {Object/Object[]} The grouped data
66790      */
66791     getGroups: function(requestGroupString) {
66792         var records = this.data.items,
66793             length = records.length,
66794             groups = [],
66795             pointers = {},
66796             record,
66797             groupStr,
66798             group,
66799             i;
66800
66801         for (i = 0; i < length; i++) {
66802             record = records[i];
66803             groupStr = this.getGroupString(record);
66804             group = pointers[groupStr];
66805
66806             if (group === undefined) {
66807                 group = {
66808                     name: groupStr,
66809                     children: []
66810                 };
66811
66812                 groups.push(group);
66813                 pointers[groupStr] = group;
66814             }
66815
66816             group.children.push(record);
66817         }
66818
66819         return requestGroupString ? pointers[requestGroupString] : groups;
66820     },
66821
66822     /**
66823      * @private
66824      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
66825      * matching a certain group.
66826      */
66827     getGroupsForGrouper: function(records, grouper) {
66828         var length = records.length,
66829             groups = [],
66830             oldValue,
66831             newValue,
66832             record,
66833             group,
66834             i;
66835
66836         for (i = 0; i < length; i++) {
66837             record = records[i];
66838             newValue = grouper.getGroupString(record);
66839
66840             if (newValue !== oldValue) {
66841                 group = {
66842                     name: newValue,
66843                     grouper: grouper,
66844                     records: []
66845                 };
66846                 groups.push(group);
66847             }
66848
66849             group.records.push(record);
66850
66851             oldValue = newValue;
66852         }
66853
66854         return groups;
66855     },
66856
66857     /**
66858      * @private
66859      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
66860      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
66861      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
66862      * @param {Ext.data.Model[]} records The set or subset of records to group
66863      * @param {Number} grouperIndex The grouper index to retrieve
66864      * @return {Object[]} The grouped records
66865      */
66866     getGroupsForGrouperIndex: function(records, grouperIndex) {
66867         var me = this,
66868             groupers = me.groupers,
66869             grouper = groupers.getAt(grouperIndex),
66870             groups = me.getGroupsForGrouper(records, grouper),
66871             length = groups.length,
66872             i;
66873
66874         if (grouperIndex + 1 < groupers.length) {
66875             for (i = 0; i < length; i++) {
66876                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
66877             }
66878         }
66879
66880         for (i = 0; i < length; i++) {
66881             groups[i].depth = grouperIndex;
66882         }
66883
66884         return groups;
66885     },
66886
66887     /**
66888      * @private
66889      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
66890      * this case grouping by genre and then author in a fictional books dataset):</p>
66891 <pre><code>
66892 [
66893     {
66894         name: 'Fantasy',
66895         depth: 0,
66896         records: [
66897             //book1, book2, book3, book4
66898         ],
66899         children: [
66900             {
66901                 name: 'Rowling',
66902                 depth: 1,
66903                 records: [
66904                     //book1, book2
66905                 ]
66906             },
66907             {
66908                 name: 'Tolkein',
66909                 depth: 1,
66910                 records: [
66911                     //book3, book4
66912                 ]
66913             }
66914         ]
66915     }
66916 ]
66917 </code></pre>
66918      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
66919      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
66920      * (defaults to true)
66921      * @return {Object[]} The group data
66922      */
66923     getGroupData: function(sort) {
66924         var me = this;
66925         if (sort !== false) {
66926             me.sort();
66927         }
66928
66929         return me.getGroupsForGrouperIndex(me.data.items, 0);
66930     },
66931
66932     /**
66933      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
66934      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
66935      * group by the first letter of a model's 'name' field, use the following code:</p>
66936 <pre><code>
66937 Ext.create('Ext.data.Store', {
66938     groupDir: 'ASC',
66939     getGroupString: function(instance) {
66940         return instance.get('name')[0];
66941     }
66942 });
66943 </code></pre>
66944      * @param {Ext.data.Model} instance The model instance
66945      * @return {String} The string to compare when forming groups
66946      */
66947     getGroupString: function(instance) {
66948         var group = this.groupers.first();
66949         if (group) {
66950             return instance.get(group.property);
66951         }
66952         return '';
66953     },
66954     /**
66955      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
66956      * See also <code>{@link #add}</code>.
66957      * @param {Number} index The start index at which to insert the passed Records.
66958      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
66959      */
66960     insert: function(index, records) {
66961         var me = this,
66962             sync = false,
66963             i,
66964             record,
66965             len;
66966
66967         records = [].concat(records);
66968         for (i = 0, len = records.length; i < len; i++) {
66969             record = me.createModel(records[i]);
66970             record.set(me.modelDefaults);
66971             // reassign the model in the array in case it wasn't created yet
66972             records[i] = record;
66973
66974             me.data.insert(index + i, record);
66975             record.join(me);
66976
66977             sync = sync || record.phantom === true;
66978         }
66979
66980         if (me.snapshot) {
66981             me.snapshot.addAll(records);
66982         }
66983
66984         me.fireEvent('add', me, records, index);
66985         me.fireEvent('datachanged', me);
66986         if (me.autoSync && sync) {
66987             me.sync();
66988         }
66989     },
66990
66991     /**
66992      * Adds Model instance to the Store. This method accepts either:
66993      *
66994      * - An array of Model instances or Model configuration objects.
66995      * - Any number of Model instance or Model configuration object arguments.
66996      *
66997      * The new Model instances will be added at the end of the existing collection.
66998      *
66999      * Sample usage:
67000      *
67001      *     myStore.add({some: 'data'}, {some: 'other data'});
67002      *
67003      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
67004      * or Model configuration objects, or variable number of Model instance or config arguments.
67005      * @return {Ext.data.Model[]} The model instances that were added
67006      */
67007     add: function(records) {
67008         //accept both a single-argument array of records, or any number of record arguments
67009         if (!Ext.isArray(records)) {
67010             records = Array.prototype.slice.apply(arguments);
67011         }
67012
67013         var me = this,
67014             i = 0,
67015             length = records.length,
67016             record;
67017
67018         for (; i < length; i++) {
67019             record = me.createModel(records[i]);
67020             // reassign the model in the array in case it wasn't created yet
67021             records[i] = record;
67022         }
67023
67024         me.insert(me.data.length, records);
67025
67026         return records;
67027     },
67028
67029     /**
67030      * Converts a literal to a model, if it's not a model already
67031      * @private
67032      * @param record {Ext.data.Model/Object} The record to create
67033      * @return {Ext.data.Model}
67034      */
67035     createModel: function(record) {
67036         if (!record.isModel) {
67037             record = Ext.ModelManager.create(record, this.model);
67038         }
67039
67040         return record;
67041     },
67042
67043     /**
67044      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
67045      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
67046      * Returning <tt>false</tt> aborts and exits the iteration.
67047      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
67048      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
67049      */
67050     each: function(fn, scope) {
67051         this.data.each(fn, scope);
67052     },
67053
67054     /**
67055      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
67056      * 'datachanged' event after removal.
67057      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
67058      */
67059     remove: function(records, /* private */ isMove) {
67060         if (!Ext.isArray(records)) {
67061             records = [records];
67062         }
67063
67064         /*
67065          * Pass the isMove parameter if we know we're going to be re-inserting this record
67066          */
67067         isMove = isMove === true;
67068         var me = this,
67069             sync = false,
67070             i = 0,
67071             length = records.length,
67072             isPhantom,
67073             index,
67074             record;
67075
67076         for (; i < length; i++) {
67077             record = records[i];
67078             index = me.data.indexOf(record);
67079
67080             if (me.snapshot) {
67081                 me.snapshot.remove(record);
67082             }
67083
67084             if (index > -1) {
67085                 isPhantom = record.phantom === true;
67086                 if (!isMove && !isPhantom) {
67087                     // don't push phantom records onto removed
67088                     me.removed.push(record);
67089                 }
67090
67091                 record.unjoin(me);
67092                 me.data.remove(record);
67093                 sync = sync || !isPhantom;
67094
67095                 me.fireEvent('remove', me, record, index);
67096             }
67097         }
67098
67099         me.fireEvent('datachanged', me);
67100         if (!isMove && me.autoSync && sync) {
67101             me.sync();
67102         }
67103     },
67104
67105     /**
67106      * Removes the model instance at the given index
67107      * @param {Number} index The record index
67108      */
67109     removeAt: function(index) {
67110         var record = this.getAt(index);
67111
67112         if (record) {
67113             this.remove(record);
67114         }
67115     },
67116
67117     /**
67118      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
67119      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
67120      * instances into the Store and calling an optional callback if required. Example usage:</p>
67121      *
67122 <pre><code>
67123 store.load({
67124     scope   : this,
67125     callback: function(records, operation, success) {
67126         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
67127         console.log(records);
67128     }
67129 });
67130 </code></pre>
67131      *
67132      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
67133      *
67134 <pre><code>
67135 store.load(function(records, operation, success) {
67136     console.log('loaded records');
67137 });
67138 </code></pre>
67139      *
67140      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67141      */
67142     load: function(options) {
67143         var me = this;
67144
67145         options = options || {};
67146
67147         if (Ext.isFunction(options)) {
67148             options = {
67149                 callback: options
67150             };
67151         }
67152
67153         Ext.applyIf(options, {
67154             groupers: me.groupers.items,
67155             page: me.currentPage,
67156             start: (me.currentPage - 1) * me.pageSize,
67157             limit: me.pageSize,
67158             addRecords: false
67159         });
67160
67161         return me.callParent([options]);
67162     },
67163
67164     /**
67165      * @private
67166      * Called internally when a Proxy has completed a load request
67167      */
67168     onProxyLoad: function(operation) {
67169         var me = this,
67170             resultSet = operation.getResultSet(),
67171             records = operation.getRecords(),
67172             successful = operation.wasSuccessful();
67173
67174         if (resultSet) {
67175             me.totalCount = resultSet.total;
67176         }
67177
67178         if (successful) {
67179             me.loadRecords(records, operation);
67180         }
67181
67182         me.loading = false;
67183         me.fireEvent('load', me, records, successful);
67184
67185         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
67186         //People are definitely using this so can't deprecate safely until 2.x
67187         me.fireEvent('read', me, records, operation.wasSuccessful());
67188
67189         //this is a callback that would have been passed to the 'read' function and is optional
67190         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67191     },
67192
67193     /**
67194      * Create any new records when a write is returned from the server.
67195      * @private
67196      * @param {Ext.data.Model[]} records The array of new records
67197      * @param {Ext.data.Operation} operation The operation that just completed
67198      * @param {Boolean} success True if the operation was successful
67199      */
67200     onCreateRecords: function(records, operation, success) {
67201         if (success) {
67202             var i = 0,
67203                 data = this.data,
67204                 snapshot = this.snapshot,
67205                 length = records.length,
67206                 originalRecords = operation.records,
67207                 record,
67208                 original,
67209                 index;
67210
67211             /*
67212              * Loop over each record returned from the server. Assume they are
67213              * returned in order of how they were sent. If we find a matching
67214              * record, replace it with the newly created one.
67215              */
67216             for (; i < length; ++i) {
67217                 record = records[i];
67218                 original = originalRecords[i];
67219                 if (original) {
67220                     index = data.indexOf(original);
67221                     if (index > -1) {
67222                         data.removeAt(index);
67223                         data.insert(index, record);
67224                     }
67225                     if (snapshot) {
67226                         index = snapshot.indexOf(original);
67227                         if (index > -1) {
67228                             snapshot.removeAt(index);
67229                             snapshot.insert(index, record);
67230                         }
67231                     }
67232                     record.phantom = false;
67233                     record.join(this);
67234                 }
67235             }
67236         }
67237     },
67238
67239     /**
67240      * Update any records when a write is returned from the server.
67241      * @private
67242      * @param {Ext.data.Model[]} records The array of updated records
67243      * @param {Ext.data.Operation} operation The operation that just completed
67244      * @param {Boolean} success True if the operation was successful
67245      */
67246     onUpdateRecords: function(records, operation, success){
67247         if (success) {
67248             var i = 0,
67249                 length = records.length,
67250                 data = this.data,
67251                 snapshot = this.snapshot,
67252                 record;
67253
67254             for (; i < length; ++i) {
67255                 record = records[i];
67256                 data.replace(record);
67257                 if (snapshot) {
67258                     snapshot.replace(record);
67259                 }
67260                 record.join(this);
67261             }
67262         }
67263     },
67264
67265     /**
67266      * Remove any records when a write is returned from the server.
67267      * @private
67268      * @param {Ext.data.Model[]} records The array of removed records
67269      * @param {Ext.data.Operation} operation The operation that just completed
67270      * @param {Boolean} success True if the operation was successful
67271      */
67272     onDestroyRecords: function(records, operation, success){
67273         if (success) {
67274             var me = this,
67275                 i = 0,
67276                 length = records.length,
67277                 data = me.data,
67278                 snapshot = me.snapshot,
67279                 record;
67280
67281             for (; i < length; ++i) {
67282                 record = records[i];
67283                 record.unjoin(me);
67284                 data.remove(record);
67285                 if (snapshot) {
67286                     snapshot.remove(record);
67287                 }
67288             }
67289             me.removed = [];
67290         }
67291     },
67292
67293     //inherit docs
67294     getNewRecords: function() {
67295         return this.data.filterBy(this.filterNew).items;
67296     },
67297
67298     //inherit docs
67299     getUpdatedRecords: function() {
67300         return this.data.filterBy(this.filterUpdated).items;
67301     },
67302
67303     /**
67304      * Filters the loaded set of records by a given set of filters.
67305      *
67306      * Filtering by single field:
67307      *
67308      *     store.filter("email", /\.com$/);
67309      *
67310      * Using multiple filters:
67311      *
67312      *     store.filter([
67313      *         {property: "email", value: /\.com$/},
67314      *         {filterFn: function(item) { return item.get("age") > 10; }}
67315      *     ]);
67316      *
67317      * Using Ext.util.Filter instances instead of config objects
67318      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
67319      *
67320      *     store.filter([
67321      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
67322      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
67323      *     ]);
67324      *
67325      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
67326      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
67327      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
67328      * pass in a property string
67329      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
67330      */
67331     filter: function(filters, value) {
67332         if (Ext.isString(filters)) {
67333             filters = {
67334                 property: filters,
67335                 value: value
67336             };
67337         }
67338
67339         var me = this,
67340             decoded = me.decodeFilters(filters),
67341             i = 0,
67342             doLocalSort = me.sortOnFilter && !me.remoteSort,
67343             length = decoded.length;
67344
67345         for (; i < length; i++) {
67346             me.filters.replace(decoded[i]);
67347         }
67348
67349         if (me.remoteFilter) {
67350             //the load function will pick up the new filters and request the filtered data from the proxy
67351             me.load();
67352         } else {
67353             /**
67354              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
67355              * records when a filter is removed or changed
67356              * @property snapshot
67357              * @type Ext.util.MixedCollection
67358              */
67359             if (me.filters.getCount()) {
67360                 me.snapshot = me.snapshot || me.data.clone();
67361                 me.data = me.data.filter(me.filters.items);
67362
67363                 if (doLocalSort) {
67364                     me.sort();
67365                 }
67366                 // fire datachanged event if it hasn't already been fired by doSort
67367                 if (!doLocalSort || me.sorters.length < 1) {
67368                     me.fireEvent('datachanged', me);
67369                 }
67370             }
67371         }
67372     },
67373
67374     /**
67375      * Revert to a view of the Record cache with no filtering applied.
67376      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
67377      * {@link #datachanged} event.
67378      */
67379     clearFilter: function(suppressEvent) {
67380         var me = this;
67381
67382         me.filters.clear();
67383
67384         if (me.remoteFilter) {
67385             me.load();
67386         } else if (me.isFiltered()) {
67387             me.data = me.snapshot.clone();
67388             delete me.snapshot;
67389
67390             if (suppressEvent !== true) {
67391                 me.fireEvent('datachanged', me);
67392             }
67393         }
67394     },
67395
67396     /**
67397      * Returns true if this store is currently filtered
67398      * @return {Boolean}
67399      */
67400     isFiltered: function() {
67401         var snapshot = this.snapshot;
67402         return !! snapshot && snapshot !== this.data;
67403     },
67404
67405     /**
67406      * Filter by a function. The specified function will be called for each
67407      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
67408      * otherwise it is filtered out.
67409      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67410      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67411      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67412      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67413      * </ul>
67414      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67415      */
67416     filterBy: function(fn, scope) {
67417         var me = this;
67418
67419         me.snapshot = me.snapshot || me.data.clone();
67420         me.data = me.queryBy(fn, scope || me);
67421         me.fireEvent('datachanged', me);
67422     },
67423
67424     /**
67425      * Query the cached records in this Store using a filtering function. The specified function
67426      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
67427      * included in the results.
67428      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67429      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67430      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67431      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67432      * </ul>
67433      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67434      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
67435      **/
67436     queryBy: function(fn, scope) {
67437         var me = this,
67438         data = me.snapshot || me.data;
67439         return data.filterBy(fn, scope || me);
67440     },
67441
67442     /**
67443      * Loads an array of data straight into the Store.
67444      * 
67445      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
67446      * processed by a reader). If your data requires processing to decode the data structure, use a
67447      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
67448      * 
67449      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
67450      * into model instances.
67451      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67452      * to remove the old ones first.
67453      */
67454     loadData: function(data, append) {
67455         var model = this.model,
67456             length = data.length,
67457             newData = [],
67458             i,
67459             record;
67460
67461         //make sure each data element is an Ext.data.Model instance
67462         for (i = 0; i < length; i++) {
67463             record = data[i];
67464
67465             if (!(record instanceof Ext.data.Model)) {
67466                 record = Ext.ModelManager.create(record, model);
67467             }
67468             newData.push(record);
67469         }
67470
67471         this.loadRecords(newData, {addRecords: append});
67472     },
67473
67474
67475     /**
67476      * Loads data via the bound Proxy's reader
67477      *
67478      * Use this method if you are attempting to load data and want to utilize the configured data reader.
67479      *
67480      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
67481      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67482      * to remove the old ones first.
67483      */
67484     loadRawData : function(data, append) {
67485          var me      = this,
67486              result  = me.proxy.reader.read(data),
67487              records = result.records;
67488
67489          if (result.success) {
67490              me.loadRecords(records, { addRecords: append });
67491              me.fireEvent('load', me, records, true);
67492          }
67493      },
67494
67495
67496     /**
67497      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
67498      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
67499      * @param {Ext.data.Model[]} records The array of records to load
67500      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
67501      */
67502     loadRecords: function(records, options) {
67503         var me     = this,
67504             i      = 0,
67505             length = records.length;
67506
67507         options = options || {};
67508
67509
67510         if (!options.addRecords) {
67511             delete me.snapshot;
67512             me.clearData();
67513         }
67514
67515         me.data.addAll(records);
67516
67517         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
67518         for (; i < length; i++) {
67519             if (options.start !== undefined) {
67520                 records[i].index = options.start + i;
67521
67522             }
67523             records[i].join(me);
67524         }
67525
67526         /*
67527          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
67528          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
67529          * datachanged event is fired by the call to this.add, above.
67530          */
67531         me.suspendEvents();
67532
67533         if (me.filterOnLoad && !me.remoteFilter) {
67534             me.filter();
67535         }
67536
67537         if (me.sortOnLoad && !me.remoteSort) {
67538             me.sort();
67539         }
67540
67541         me.resumeEvents();
67542         me.fireEvent('datachanged', me, records);
67543     },
67544
67545     // PAGING METHODS
67546     /**
67547      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
67548      * load operation, passing in calculated 'start' and 'limit' params
67549      * @param {Number} page The number of the page to load
67550      * @param {Object} options See options for {@link #load}
67551      */
67552     loadPage: function(page, options) {
67553         var me = this;
67554         options = Ext.apply({}, options);
67555
67556         me.currentPage = page;
67557
67558         me.read(Ext.applyIf(options, {
67559             page: page,
67560             start: (page - 1) * me.pageSize,
67561             limit: me.pageSize,
67562             addRecords: !me.clearOnPageLoad
67563         }));
67564     },
67565
67566     /**
67567      * Loads the next 'page' in the current data set
67568      * @param {Object} options See options for {@link #load}
67569      */
67570     nextPage: function(options) {
67571         this.loadPage(this.currentPage + 1, options);
67572     },
67573
67574     /**
67575      * Loads the previous 'page' in the current data set
67576      * @param {Object} options See options for {@link #load}
67577      */
67578     previousPage: function(options) {
67579         this.loadPage(this.currentPage - 1, options);
67580     },
67581
67582     // private
67583     clearData: function() {
67584         var me = this;
67585         me.data.each(function(record) {
67586             record.unjoin(me);
67587         });
67588
67589         me.data.clear();
67590     },
67591
67592     // Buffering
67593     /**
67594      * Prefetches data into the store using its configured {@link #proxy}.
67595      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67596      * See {@link #load}
67597      */
67598     prefetch: function(options) {
67599         var me = this,
67600             operation,
67601             requestId = me.getRequestId();
67602
67603         options = options || {};
67604
67605         Ext.applyIf(options, {
67606             action : 'read',
67607             filters: me.filters.items,
67608             sorters: me.sorters.items,
67609             requestId: requestId
67610         });
67611         me.pendingRequests.push(requestId);
67612
67613         operation = Ext.create('Ext.data.Operation', options);
67614
67615         // HACK to implement loadMask support.
67616         //if (operation.blocking) {
67617         //    me.fireEvent('beforeload', me, operation);
67618         //}
67619         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
67620             me.loading = true;
67621             me.proxy.read(operation, me.onProxyPrefetch, me);
67622         }
67623
67624         return me;
67625     },
67626
67627     /**
67628      * Prefetches a page of data.
67629      * @param {Number} page The page to prefetch
67630      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67631      * See {@link #load}
67632      */
67633     prefetchPage: function(page, options) {
67634         var me = this,
67635             pageSize = me.pageSize,
67636             start = (page - 1) * me.pageSize,
67637             end = start + pageSize;
67638
67639         // Currently not requesting this page and range isn't already satisified
67640         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
67641             options = options || {};
67642             me.pagesRequested.push(page);
67643             Ext.applyIf(options, {
67644                 page : page,
67645                 start: start,
67646                 limit: pageSize,
67647                 callback: me.onWaitForGuarantee,
67648                 scope: me
67649             });
67650
67651             me.prefetch(options);
67652         }
67653
67654     },
67655
67656     /**
67657      * Returns a unique requestId to track requests.
67658      * @private
67659      */
67660     getRequestId: function() {
67661         this.requestSeed = this.requestSeed || 1;
67662         return this.requestSeed++;
67663     },
67664
67665     /**
67666      * Called after the configured proxy completes a prefetch operation.
67667      * @private
67668      * @param {Ext.data.Operation} operation The operation that completed
67669      */
67670     onProxyPrefetch: function(operation) {
67671         var me         = this,
67672             resultSet  = operation.getResultSet(),
67673             records    = operation.getRecords(),
67674
67675             successful = operation.wasSuccessful();
67676
67677         if (resultSet) {
67678             me.totalCount = resultSet.total;
67679             me.fireEvent('totalcountchange', me.totalCount);
67680         }
67681
67682         if (successful) {
67683             me.cacheRecords(records, operation);
67684         }
67685         Ext.Array.remove(me.pendingRequests, operation.requestId);
67686         if (operation.page) {
67687             Ext.Array.remove(me.pagesRequested, operation.page);
67688         }
67689
67690         me.loading = false;
67691         me.fireEvent('prefetch', me, records, successful, operation);
67692
67693         // HACK to support loadMask
67694         if (operation.blocking) {
67695             me.fireEvent('load', me, records, successful);
67696         }
67697
67698         //this is a callback that would have been passed to the 'read' function and is optional
67699         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67700     },
67701
67702     /**
67703      * Caches the records in the prefetch and stripes them with their server-side
67704      * index.
67705      * @private
67706      * @param {Ext.data.Model[]} records The records to cache
67707      * @param {Ext.data.Operation} The associated operation
67708      */
67709     cacheRecords: function(records, operation) {
67710         var me     = this,
67711             i      = 0,
67712             length = records.length,
67713             start  = operation ? operation.start : 0;
67714
67715         if (!Ext.isDefined(me.totalCount)) {
67716             me.totalCount = records.length;
67717             me.fireEvent('totalcountchange', me.totalCount);
67718         }
67719
67720         for (; i < length; i++) {
67721             // this is the true index, not the viewIndex
67722             records[i].index = start + i;
67723         }
67724
67725         me.prefetchData.addAll(records);
67726         if (me.purgePageCount) {
67727             me.purgeRecords();
67728         }
67729
67730     },
67731
67732
67733     /**
67734      * Purge the least recently used records in the prefetch if the purgeCount
67735      * has been exceeded.
67736      */
67737     purgeRecords: function() {
67738         var me = this,
67739             prefetchCount = me.prefetchData.getCount(),
67740             purgeCount = me.purgePageCount * me.pageSize,
67741             numRecordsToPurge = prefetchCount - purgeCount - 1,
67742             i = 0;
67743
67744         for (; i <= numRecordsToPurge; i++) {
67745             me.prefetchData.removeAt(0);
67746         }
67747     },
67748
67749     /**
67750      * Determines if the range has already been satisfied in the prefetchData.
67751      * @private
67752      * @param {Number} start The start index
67753      * @param {Number} end The end index in the range
67754      */
67755     rangeSatisfied: function(start, end) {
67756         var me = this,
67757             i = start,
67758             satisfied = true;
67759
67760         for (; i < end; i++) {
67761             if (!me.prefetchData.getByKey(i)) {
67762                 satisfied = false;
67763                 break;
67764             }
67765         }
67766         return satisfied;
67767     },
67768
67769     /**
67770      * Determines the page from a record index
67771      * @param {Number} index The record index
67772      * @return {Number} The page the record belongs to
67773      */
67774     getPageFromRecordIndex: function(index) {
67775         return Math.floor(index / this.pageSize) + 1;
67776     },
67777
67778     /**
67779      * Handles a guaranteed range being loaded
67780      * @private
67781      */
67782     onGuaranteedRange: function() {
67783         var me = this,
67784             totalCount = me.getTotalCount(),
67785             start = me.requestStart,
67786             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
67787             range = [],
67788             record,
67789             i = start;
67790
67791         end = Math.max(0, end);
67792
67793
67794         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
67795             me.guaranteedStart = start;
67796             me.guaranteedEnd = end;
67797
67798             for (; i <= end; i++) {
67799                 record = me.prefetchData.getByKey(i);
67800                 if (record) {
67801                     range.push(record);
67802                 }
67803             }
67804             me.fireEvent('guaranteedrange', range, start, end);
67805             if (me.cb) {
67806                 me.cb.call(me.scope || me, range);
67807             }
67808         }
67809
67810         me.unmask();
67811     },
67812
67813     // hack to support loadmask
67814     mask: function() {
67815         this.masked = true;
67816         this.fireEvent('beforeload');
67817     },
67818
67819     // hack to support loadmask
67820     unmask: function() {
67821         if (this.masked) {
67822             this.fireEvent('load');
67823         }
67824     },
67825
67826     /**
67827      * Returns the number of pending requests out.
67828      */
67829     hasPendingRequests: function() {
67830         return this.pendingRequests.length;
67831     },
67832
67833
67834     // wait until all requests finish, until guaranteeing the range.
67835     onWaitForGuarantee: function() {
67836         if (!this.hasPendingRequests()) {
67837             this.onGuaranteedRange();
67838         }
67839     },
67840
67841     /**
67842      * Guarantee a specific range, this will load the store with a range (that
67843      * must be the pageSize or smaller) and take care of any loading that may
67844      * be necessary.
67845      */
67846     guaranteeRange: function(start, end, cb, scope) {
67847
67848         end = (end > this.totalCount) ? this.totalCount - 1 : end;
67849
67850         var me = this,
67851             i = start,
67852             prefetchData = me.prefetchData,
67853             range = [],
67854             startLoaded = !!prefetchData.getByKey(start),
67855             endLoaded = !!prefetchData.getByKey(end),
67856             startPage = me.getPageFromRecordIndex(start),
67857             endPage = me.getPageFromRecordIndex(end);
67858
67859         me.cb = cb;
67860         me.scope = scope;
67861
67862         me.requestStart = start;
67863         me.requestEnd = end;
67864         // neither beginning or end are loaded
67865         if (!startLoaded || !endLoaded) {
67866             // same page, lets load it
67867             if (startPage === endPage) {
67868                 me.mask();
67869                 me.prefetchPage(startPage, {
67870                     //blocking: true,
67871                     callback: me.onWaitForGuarantee,
67872                     scope: me
67873                 });
67874             // need to load two pages
67875             } else {
67876                 me.mask();
67877                 me.prefetchPage(startPage, {
67878                     //blocking: true,
67879                     callback: me.onWaitForGuarantee,
67880                     scope: me
67881                 });
67882                 me.prefetchPage(endPage, {
67883                     //blocking: true,
67884                     callback: me.onWaitForGuarantee,
67885                     scope: me
67886                 });
67887             }
67888         // Request was already satisfied via the prefetch
67889         } else {
67890             me.onGuaranteedRange();
67891         }
67892     },
67893
67894     // because prefetchData is stored by index
67895     // this invalidates all of the prefetchedData
67896     sort: function() {
67897         var me = this,
67898             prefetchData = me.prefetchData,
67899             sorters,
67900             start,
67901             end,
67902             range;
67903
67904         if (me.buffered) {
67905             if (me.remoteSort) {
67906                 prefetchData.clear();
67907                 me.callParent(arguments);
67908             } else {
67909                 sorters = me.getSorters();
67910                 start = me.guaranteedStart;
67911                 end = me.guaranteedEnd;
67912
67913                 if (sorters.length) {
67914                     prefetchData.sort(sorters);
67915                     range = prefetchData.getRange();
67916                     prefetchData.clear();
67917                     me.cacheRecords(range);
67918                     delete me.guaranteedStart;
67919                     delete me.guaranteedEnd;
67920                     me.guaranteeRange(start, end);
67921                 }
67922                 me.callParent(arguments);
67923             }
67924         } else {
67925             me.callParent(arguments);
67926         }
67927     },
67928
67929     // overriden to provide striping of the indexes as sorting occurs.
67930     // this cannot be done inside of sort because datachanged has already
67931     // fired and will trigger a repaint of the bound view.
67932     doSort: function(sorterFn) {
67933         var me = this;
67934         if (me.remoteSort) {
67935             //the load function will pick up the new sorters and request the sorted data from the proxy
67936             me.load();
67937         } else {
67938             me.data.sortBy(sorterFn);
67939             if (!me.buffered) {
67940                 var range = me.getRange(),
67941                     ln = range.length,
67942                     i  = 0;
67943                 for (; i < ln; i++) {
67944                     range[i].index = i;
67945                 }
67946             }
67947             me.fireEvent('datachanged', me);
67948         }
67949     },
67950
67951     /**
67952      * Finds the index of the first matching Record in this store by a specific field value.
67953      * @param {String} fieldName The name of the Record field to test.
67954      * @param {String/RegExp} value Either a string that the field value
67955      * should begin with, or a RegExp to test against the field.
67956      * @param {Number} startIndex (optional) The index to start searching at
67957      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67958      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67959      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67960      * @return {Number} The matched index or -1
67961      */
67962     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
67963         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
67964         return fn ? this.data.findIndexBy(fn, null, start) : -1;
67965     },
67966
67967     /**
67968      * Finds the first matching Record in this store by a specific field value.
67969      * @param {String} fieldName The name of the Record field to test.
67970      * @param {String/RegExp} value Either a string that the field value
67971      * should begin with, or a RegExp to test against the field.
67972      * @param {Number} startIndex (optional) The index to start searching at
67973      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67974      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67975      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67976      * @return {Ext.data.Model} The matched record or null
67977      */
67978     findRecord: function() {
67979         var me = this,
67980             index = me.find.apply(me, arguments);
67981         return index !== -1 ? me.getAt(index) : null;
67982     },
67983
67984     /**
67985      * @private
67986      * Returns a filter function used to test a the given property's value. Defers most of the work to
67987      * Ext.util.MixedCollection's createValueMatcher function
67988      * @param {String} property The property to create the filter function for
67989      * @param {String/RegExp} value The string/regex to compare the property value to
67990      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
67991      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
67992      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
67993      * Ignored if anyMatch is true.
67994      */
67995     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
67996         if (Ext.isEmpty(value)) {
67997             return false;
67998         }
67999         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
68000         return function(r) {
68001             return value.test(r.data[property]);
68002         };
68003     },
68004
68005     /**
68006      * Finds the index of the first matching Record in this store by a specific field value.
68007      * @param {String} fieldName The name of the Record field to test.
68008      * @param {Object} value The value to match the field against.
68009      * @param {Number} startIndex (optional) The index to start searching at
68010      * @return {Number} The matched index or -1
68011      */
68012     findExact: function(property, value, start) {
68013         return this.data.findIndexBy(function(rec) {
68014             return rec.get(property) == value;
68015         },
68016         this, start);
68017     },
68018
68019     /**
68020      * Find the index of the first matching Record in this Store by a function.
68021      * If the function returns <tt>true</tt> it is considered a match.
68022      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68023      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68024      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68025      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68026      * </ul>
68027      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68028      * @param {Number} startIndex (optional) The index to start searching at
68029      * @return {Number} The matched index or -1
68030      */
68031     findBy: function(fn, scope, start) {
68032         return this.data.findIndexBy(fn, scope, start);
68033     },
68034
68035     /**
68036      * Collects unique values for a particular dataIndex from this store.
68037      * @param {String} dataIndex The property to collect
68038      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
68039      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
68040      * @return {Object[]} An array of the unique values
68041      **/
68042     collect: function(dataIndex, allowNull, bypassFilter) {
68043         var me = this,
68044             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
68045
68046         return data.collect(dataIndex, 'data', allowNull);
68047     },
68048
68049     /**
68050      * Gets the number of cached records.
68051      * <p>If using paging, this may not be the total size of the dataset. If the data object
68052      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
68053      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
68054      * @return {Number} The number of Records in the Store's cache.
68055      */
68056     getCount: function() {
68057         return this.data.length || 0;
68058     },
68059
68060     /**
68061      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
68062      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
68063      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
68064      * could be loaded into the Store if the Store contained all data
68065      * @return {Number} The total number of Model instances available via the Proxy
68066      */
68067     getTotalCount: function() {
68068         return this.totalCount;
68069     },
68070
68071     /**
68072      * Get the Record at the specified index.
68073      * @param {Number} index The index of the Record to find.
68074      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
68075      */
68076     getAt: function(index) {
68077         return this.data.getAt(index);
68078     },
68079
68080     /**
68081      * Returns a range of Records between specified indices.
68082      * @param {Number} [startIndex=0] The starting index
68083      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
68084      * @return {Ext.data.Model[]} An array of Records
68085      */
68086     getRange: function(start, end) {
68087         return this.data.getRange(start, end);
68088     },
68089
68090     /**
68091      * Get the Record with the specified id.
68092      * @param {String} id The id of the Record to find.
68093      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
68094      */
68095     getById: function(id) {
68096         return (this.snapshot || this.data).findBy(function(record) {
68097             return record.getId() === id;
68098         });
68099     },
68100
68101     /**
68102      * Get the index within the cache of the passed Record.
68103      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68104      * @return {Number} The index of the passed Record. Returns -1 if not found.
68105      */
68106     indexOf: function(record) {
68107         return this.data.indexOf(record);
68108     },
68109
68110
68111     /**
68112      * Get the index within the entire dataset. From 0 to the totalCount.
68113      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68114      * @return {Number} The index of the passed Record. Returns -1 if not found.
68115      */
68116     indexOfTotal: function(record) {
68117         var index = record.index;
68118         if (index || index === 0) {
68119             return index;
68120         }
68121         return this.indexOf(record);
68122     },
68123
68124     /**
68125      * Get the index within the cache of the Record with the passed id.
68126      * @param {String} id The id of the Record to find.
68127      * @return {Number} The index of the Record. Returns -1 if not found.
68128      */
68129     indexOfId: function(id) {
68130         return this.indexOf(this.getById(id));
68131     },
68132
68133     /**
68134      * Remove all items from the store.
68135      * @param {Boolean} silent Prevent the `clear` event from being fired.
68136      */
68137     removeAll: function(silent) {
68138         var me = this;
68139
68140         me.clearData();
68141         if (me.snapshot) {
68142             me.snapshot.clear();
68143         }
68144         if (silent !== true) {
68145             me.fireEvent('clear', me);
68146         }
68147     },
68148
68149     /*
68150      * Aggregation methods
68151      */
68152
68153     /**
68154      * Convenience function for getting the first model instance in the store
68155      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68156      * in the store. The value returned will be an object literal with the key being the group
68157      * name and the first record being the value. The grouped parameter is only honored if
68158      * the store has a groupField.
68159      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
68160      */
68161     first: function(grouped) {
68162         var me = this;
68163
68164         if (grouped && me.isGrouped()) {
68165             return me.aggregate(function(records) {
68166                 return records.length ? records[0] : undefined;
68167             }, me, true);
68168         } else {
68169             return me.data.first();
68170         }
68171     },
68172
68173     /**
68174      * Convenience function for getting the last model instance in the store
68175      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68176      * in the store. The value returned will be an object literal with the key being the group
68177      * name and the last record being the value. The grouped parameter is only honored if
68178      * the store has a groupField.
68179      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
68180      */
68181     last: function(grouped) {
68182         var me = this;
68183
68184         if (grouped && me.isGrouped()) {
68185             return me.aggregate(function(records) {
68186                 var len = records.length;
68187                 return len ? records[len - 1] : undefined;
68188             }, me, true);
68189         } else {
68190             return me.data.last();
68191         }
68192     },
68193
68194     /**
68195      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
68196      * and <tt>end</tt> and returns the result.
68197      * @param {String} field A field in each record
68198      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68199      * in the store. The value returned will be an object literal with the key being the group
68200      * name and the sum for that group being the value. The grouped parameter is only honored if
68201      * the store has a groupField.
68202      * @return {Number} The sum
68203      */
68204     sum: function(field, grouped) {
68205         var me = this;
68206
68207         if (grouped && me.isGrouped()) {
68208             return me.aggregate(me.getSum, me, true, [field]);
68209         } else {
68210             return me.getSum(me.data.items, field);
68211         }
68212     },
68213
68214     // @private, see sum
68215     getSum: function(records, field) {
68216         var total = 0,
68217             i = 0,
68218             len = records.length;
68219
68220         for (; i < len; ++i) {
68221             total += records[i].get(field);
68222         }
68223
68224         return total;
68225     },
68226
68227     /**
68228      * Gets the count of items in the store.
68229      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68230      * in the store. The value returned will be an object literal with the key being the group
68231      * name and the count for each group being the value. The grouped parameter is only honored if
68232      * the store has a groupField.
68233      * @return {Number} the count
68234      */
68235     count: function(grouped) {
68236         var me = this;
68237
68238         if (grouped && me.isGrouped()) {
68239             return me.aggregate(function(records) {
68240                 return records.length;
68241             }, me, true);
68242         } else {
68243             return me.getCount();
68244         }
68245     },
68246
68247     /**
68248      * Gets the minimum value in the store.
68249      * @param {String} field The field in each record
68250      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68251      * in the store. The value returned will be an object literal with the key being the group
68252      * name and the minimum in the group being the value. The grouped parameter is only honored if
68253      * the store has a groupField.
68254      * @return {Object} The minimum value, if no items exist, undefined.
68255      */
68256     min: function(field, grouped) {
68257         var me = this;
68258
68259         if (grouped && me.isGrouped()) {
68260             return me.aggregate(me.getMin, me, true, [field]);
68261         } else {
68262             return me.getMin(me.data.items, field);
68263         }
68264     },
68265
68266     // @private, see min
68267     getMin: function(records, field){
68268         var i = 1,
68269             len = records.length,
68270             value, min;
68271
68272         if (len > 0) {
68273             min = records[0].get(field);
68274         }
68275
68276         for (; i < len; ++i) {
68277             value = records[i].get(field);
68278             if (value < min) {
68279                 min = value;
68280             }
68281         }
68282         return min;
68283     },
68284
68285     /**
68286      * Gets the maximum value in the store.
68287      * @param {String} field The field in each record
68288      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68289      * in the store. The value returned will be an object literal with the key being the group
68290      * name and the maximum in the group being the value. The grouped parameter is only honored if
68291      * the store has a groupField.
68292      * @return {Object} The maximum value, if no items exist, undefined.
68293      */
68294     max: function(field, grouped) {
68295         var me = this;
68296
68297         if (grouped && me.isGrouped()) {
68298             return me.aggregate(me.getMax, me, true, [field]);
68299         } else {
68300             return me.getMax(me.data.items, field);
68301         }
68302     },
68303
68304     // @private, see max
68305     getMax: function(records, field) {
68306         var i = 1,
68307             len = records.length,
68308             value,
68309             max;
68310
68311         if (len > 0) {
68312             max = records[0].get(field);
68313         }
68314
68315         for (; i < len; ++i) {
68316             value = records[i].get(field);
68317             if (value > max) {
68318                 max = value;
68319             }
68320         }
68321         return max;
68322     },
68323
68324     /**
68325      * Gets the average value in the store.
68326      * @param {String} field The field in each record
68327      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68328      * in the store. The value returned will be an object literal with the key being the group
68329      * name and the group average being the value. The grouped parameter is only honored if
68330      * the store has a groupField.
68331      * @return {Object} The average value, if no items exist, 0.
68332      */
68333     average: function(field, grouped) {
68334         var me = this;
68335         if (grouped && me.isGrouped()) {
68336             return me.aggregate(me.getAverage, me, true, [field]);
68337         } else {
68338             return me.getAverage(me.data.items, field);
68339         }
68340     },
68341
68342     // @private, see average
68343     getAverage: function(records, field) {
68344         var i = 0,
68345             len = records.length,
68346             sum = 0;
68347
68348         if (records.length > 0) {
68349             for (; i < len; ++i) {
68350                 sum += records[i].get(field);
68351             }
68352             return sum / len;
68353         }
68354         return 0;
68355     },
68356
68357     /**
68358      * Runs the aggregate function for all the records in the store.
68359      * @param {Function} fn The function to execute. The function is called with a single parameter,
68360      * an array of records for that group.
68361      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
68362      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68363      * in the store. The value returned will be an object literal with the key being the group
68364      * name and the group average being the value. The grouped parameter is only honored if
68365      * the store has a groupField.
68366      * @param {Array} args (optional) Any arguments to append to the function call
68367      * @return {Object} An object literal with the group names and their appropriate values.
68368      */
68369     aggregate: function(fn, scope, grouped, args) {
68370         args = args || [];
68371         if (grouped && this.isGrouped()) {
68372             var groups = this.getGroups(),
68373                 i = 0,
68374                 len = groups.length,
68375                 out = {},
68376                 group;
68377
68378             for (; i < len; ++i) {
68379                 group = groups[i];
68380                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
68381             }
68382             return out;
68383         } else {
68384             return fn.apply(scope || this, [this.data.items].concat(args));
68385         }
68386     }
68387 }, function() {
68388     // A dummy empty store with a fieldless Model defined in it.
68389     // Just for binding to Views which are instantiated with no Store defined.
68390     // They will be able to run and render fine, and be bound to a generated Store later.
68391     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
68392 });
68393
68394 /**
68395  * @author Ed Spencer
68396  * @class Ext.data.JsonStore
68397  * @extends Ext.data.Store
68398  * @ignore
68399  *
68400  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
68401  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
68402  *
68403  * <p>A store configuration would be something like:</p>
68404  *
68405 <pre><code>
68406 var store = new Ext.data.JsonStore({
68407     // store configs
68408     autoDestroy: true,
68409     storeId: 'myStore',
68410
68411     proxy: {
68412         type: 'ajax',
68413         url: 'get-images.php',
68414         reader: {
68415             type: 'json',
68416             root: 'images',
68417             idProperty: 'name'
68418         }
68419     },
68420
68421     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
68422     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
68423 });
68424 </code></pre>
68425  *
68426  * <p>This store is configured to consume a returned object of the form:<pre><code>
68427 {
68428     images: [
68429         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
68430         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
68431     ]
68432 }
68433 </code></pre>
68434  *
68435  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
68436  *
68437  * @xtype jsonstore
68438  */
68439 Ext.define('Ext.data.JsonStore',  {
68440     extend: 'Ext.data.Store',
68441     alias: 'store.json',
68442
68443     /**
68444      * @cfg {Ext.data.DataReader} reader @hide
68445      */
68446     constructor: function(config) {
68447         config = config || {};
68448
68449         Ext.applyIf(config, {
68450             proxy: {
68451                 type  : 'ajax',
68452                 reader: 'json',
68453                 writer: 'json'
68454             }
68455         });
68456
68457         this.callParent([config]);
68458     }
68459 });
68460
68461 /**
68462  * @class Ext.chart.axis.Time
68463  * @extends Ext.chart.axis.Numeric
68464  *
68465  * A type of axis whose units are measured in time values. Use this axis
68466  * for listing dates that you will want to group or dynamically change.
68467  * If you just want to display dates as categories then use the
68468  * Category class for axis instead.
68469  *
68470  * For example:
68471  *
68472  *     axes: [{
68473  *         type: 'Time',
68474  *         position: 'bottom',
68475  *         fields: 'date',
68476  *         title: 'Day',
68477  *         dateFormat: 'M d',
68478  *
68479  *         constrain: true,
68480  *         fromDate: new Date('1/1/11'),
68481  *         toDate: new Date('1/7/11')
68482  *     }]
68483  *
68484  * In this example we're creating a time axis that has as title *Day*.
68485  * The field the axis is bound to is `date`.
68486  * The date format to use to display the text for the axis labels is `M d`
68487  * which is a three letter month abbreviation followed by the day number.
68488  * The time axis will show values for dates between `fromDate` and `toDate`.
68489  * Since `constrain` is set to true all other values for other dates not between
68490  * the fromDate and toDate will not be displayed.
68491  *
68492  */
68493 Ext.define('Ext.chart.axis.Time', {
68494
68495     /* Begin Definitions */
68496
68497     extend: 'Ext.chart.axis.Numeric',
68498
68499     alternateClassName: 'Ext.chart.TimeAxis',
68500
68501     alias: 'axis.time',
68502
68503     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
68504
68505     /* End Definitions */
68506
68507     /**
68508      * @cfg {String/Boolean} dateFormat
68509      * Indicates the format the date will be rendered on.
68510      * For example: 'M d' will render the dates as 'Jan 30', etc.
68511      * For a list of possible format strings see {@link Ext.Date Date}
68512      */
68513     dateFormat: false,
68514
68515     /**
68516      * @cfg {Date} fromDate The starting date for the time axis.
68517      */
68518     fromDate: false,
68519
68520     /**
68521      * @cfg {Date} toDate The ending date for the time axis.
68522      */
68523     toDate: false,
68524
68525     /**
68526      * @cfg {Array/Boolean} step
68527      * An array with two components: The first is the unit of the step (day, month, year, etc).
68528      * The second one is the number of units for the step (1, 2, etc.).
68529      * Defaults to `[Ext.Date.DAY, 1]`.
68530      */
68531     step: [Ext.Date.DAY, 1],
68532     
68533     /**
68534      * @cfg {Boolean} constrain
68535      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
68536      * If false, the time axis will adapt to the new values by adding/removing steps.
68537      */
68538     constrain: false,
68539
68540     // Avoid roundtoDecimal call in Numeric Axis's constructor
68541     roundToDecimal: false,
68542     
68543     constructor: function (config) {
68544         var me = this, label, f, df;
68545         me.callParent([config]);
68546         label = me.label || {};
68547         df = this.dateFormat;
68548         if (df) {
68549             if (label.renderer) {
68550                 f = label.renderer;
68551                 label.renderer = function(v) {
68552                     v = f(v);
68553                     return Ext.Date.format(new Date(f(v)), df);
68554                 };
68555             } else {
68556                 label.renderer = function(v) {
68557                     return Ext.Date.format(new Date(v >> 0), df);
68558                 };
68559             }
68560         }
68561     },
68562
68563     doConstrain: function () {
68564         var me = this,
68565             store = me.chart.store,
68566             data = [],
68567             series = me.chart.series.items,
68568             math = Math,
68569             mmax = math.max,
68570             mmin = math.min,
68571             fields = me.fields,
68572             ln = fields.length,
68573             range = me.getRange(),
68574             min = range.min, max = range.max, i, l, excludes = [],
68575             value, values, rec, data = [];
68576         for (i = 0, l = series.length; i < l; i++) {
68577             excludes[i] = series[i].__excludes;
68578         }
68579         store.each(function(record) {
68580             for (i = 0; i < ln; i++) {
68581                 if (excludes[i]) {
68582                     continue;
68583                 }
68584                 value = record.get(fields[i]);
68585                 if (+value < +min) return;
68586                 if (+value > +max) return;
68587             }
68588             data.push(record);
68589         })
68590         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
68591     },
68592
68593     // Before rendering, set current default step count to be number of records.
68594     processView: function () {
68595         var me = this;
68596         if (me.fromDate) {
68597             me.minimum = +me.fromDate;
68598         }
68599         if (me.toDate) {
68600             me.maximum = +me.toDate;
68601         }
68602         if (me.constrain) {
68603             me.doConstrain();
68604         }
68605      },
68606
68607     // @private modifies the store and creates the labels for the axes.
68608     calcEnds: function() {
68609         var me = this, range, step = me.step;
68610         if (step) {
68611             range = me.getRange();
68612             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
68613             if (me.minimum) {
68614                 range.from = me.minimum;
68615             }
68616             if (me.maximum) {
68617                 range.to = me.maximum;
68618             }
68619             range.step = (range.to - range.from) / range.steps;
68620             return range;
68621         } else {
68622             return me.callParent(arguments);
68623         }
68624     }
68625  });
68626
68627
68628 /**
68629  * @class Ext.chart.series.Series
68630  *
68631  * Series is the abstract class containing the common logic to all chart series. Series includes
68632  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
68633  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
68634  *
68635  * ## Listeners
68636  *
68637  * The series class supports listeners via the Observable syntax. Some of these listeners are:
68638  *
68639  *  - `itemmouseup` When the user interacts with a marker.
68640  *  - `itemmousedown` When the user interacts with a marker.
68641  *  - `itemmousemove` When the user iteracts with a marker.
68642  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
68643  *
68644  * For example:
68645  *
68646  *     series: [{
68647  *             type: 'column',
68648  *             axis: 'left',
68649  *             listeners: {
68650  *                     'afterrender': function() {
68651  *                             console('afterrender');
68652  *                     }
68653  *             },
68654  *             xField: 'category',
68655  *             yField: 'data1'
68656  *     }]
68657  */
68658 Ext.define('Ext.chart.series.Series', {
68659
68660     /* Begin Definitions */
68661
68662     mixins: {
68663         observable: 'Ext.util.Observable',
68664         labels: 'Ext.chart.Label',
68665         highlights: 'Ext.chart.Highlight',
68666         tips: 'Ext.chart.Tip',
68667         callouts: 'Ext.chart.Callout'
68668     },
68669
68670     /* End Definitions */
68671
68672     /**
68673      * @cfg {Boolean/Object} highlight
68674      * If set to `true` it will highlight the markers or the series when hovering
68675      * with the mouse. This parameter can also be an object with the same style
68676      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
68677      * styles to markers and series.
68678      */
68679
68680     /**
68681      * @cfg {Object} tips
68682      * Add tooltips to the visualization's markers. The options for the tips are the
68683      * same configuration used with {@link Ext.tip.ToolTip}. For example:
68684      *
68685      *     tips: {
68686      *       trackMouse: true,
68687      *       width: 140,
68688      *       height: 28,
68689      *       renderer: function(storeItem, item) {
68690      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
68691      *       }
68692      *     },
68693      */
68694
68695     /**
68696      * @cfg {String} type
68697      * The type of series. Set in subclasses.
68698      */
68699     type: null,
68700
68701     /**
68702      * @cfg {String} title
68703      * The human-readable name of the series.
68704      */
68705     title: null,
68706
68707     /**
68708      * @cfg {Boolean} showInLegend
68709      * Whether to show this series in the legend.
68710      */
68711     showInLegend: true,
68712
68713     /**
68714      * @cfg {Function} renderer
68715      * A function that can be overridden to set custom styling properties to each rendered element.
68716      * Passes in (sprite, record, attributes, index, store) to the function.
68717      */
68718     renderer: function(sprite, record, attributes, index, store) {
68719         return attributes;
68720     },
68721
68722     /**
68723      * @cfg {Array} shadowAttributes
68724      * An array with shadow attributes
68725      */
68726     shadowAttributes: null,
68727
68728     //@private triggerdrawlistener flag
68729     triggerAfterDraw: false,
68730
68731     /**
68732      * @cfg {Object} listeners
68733      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
68734      *
68735      *  - itemmouseover
68736      *  - itemmouseout
68737      *  - itemmousedown
68738      *  - itemmouseup
68739      */
68740
68741     constructor: function(config) {
68742         var me = this;
68743         if (config) {
68744             Ext.apply(me, config);
68745         }
68746
68747         me.shadowGroups = [];
68748
68749         me.mixins.labels.constructor.call(me, config);
68750         me.mixins.highlights.constructor.call(me, config);
68751         me.mixins.tips.constructor.call(me, config);
68752         me.mixins.callouts.constructor.call(me, config);
68753
68754         me.addEvents({
68755             scope: me,
68756             itemmouseover: true,
68757             itemmouseout: true,
68758             itemmousedown: true,
68759             itemmouseup: true,
68760             mouseleave: true,
68761             afterdraw: true,
68762
68763             /**
68764              * @event titlechange
68765              * Fires when the series title is changed via {@link #setTitle}.
68766              * @param {String} title The new title value
68767              * @param {Number} index The index in the collection of titles
68768              */
68769             titlechange: true
68770         });
68771
68772         me.mixins.observable.constructor.call(me, config);
68773
68774         me.on({
68775             scope: me,
68776             itemmouseover: me.onItemMouseOver,
68777             itemmouseout: me.onItemMouseOut,
68778             mouseleave: me.onMouseLeave
68779         });
68780     },
68781     
68782     /**
68783      * Iterate over each of the records for this series. The default implementation simply iterates
68784      * through the entire data store, but individual series implementations can override this to
68785      * provide custom handling, e.g. adding/removing records.
68786      * @param {Function} fn The function to execute for each record.
68787      * @param {Object} scope Scope for the fn.
68788      */
68789     eachRecord: function(fn, scope) {
68790         var chart = this.chart;
68791         (chart.substore || chart.store).each(fn, scope);
68792     },
68793
68794     /**
68795      * Return the number of records being displayed in this series. Defaults to the number of
68796      * records in the store; individual series implementations can override to provide custom handling.
68797      */
68798     getRecordCount: function() {
68799         var chart = this.chart,
68800             store = chart.substore || chart.store;
68801         return store ? store.getCount() : 0;
68802     },
68803
68804     /**
68805      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
68806      * @param index
68807      */
68808     isExcluded: function(index) {
68809         var excludes = this.__excludes;
68810         return !!(excludes && excludes[index]);
68811     },
68812
68813     // @private set the bbox and clipBox for the series
68814     setBBox: function(noGutter) {
68815         var me = this,
68816             chart = me.chart,
68817             chartBBox = chart.chartBBox,
68818             gutterX = noGutter ? 0 : chart.maxGutter[0],
68819             gutterY = noGutter ? 0 : chart.maxGutter[1],
68820             clipBox, bbox;
68821
68822         clipBox = {
68823             x: chartBBox.x,
68824             y: chartBBox.y,
68825             width: chartBBox.width,
68826             height: chartBBox.height
68827         };
68828         me.clipBox = clipBox;
68829
68830         bbox = {
68831             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
68832             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
68833             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
68834             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
68835         };
68836         me.bbox = bbox;
68837     },
68838
68839     // @private set the animation for the sprite
68840     onAnimate: function(sprite, attr) {
68841         var me = this;
68842         sprite.stopAnimation();
68843         if (me.triggerAfterDraw) {
68844             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
68845         } else {
68846             me.triggerAfterDraw = true;
68847             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
68848                 listeners: {
68849                     'afteranimate': function() {
68850                         me.triggerAfterDraw = false;
68851                         me.fireEvent('afterrender');
68852                     }
68853                 }
68854             }));
68855         }
68856     },
68857
68858     // @private return the gutter.
68859     getGutters: function() {
68860         return [0, 0];
68861     },
68862
68863     // @private wrapper for the itemmouseover event.
68864     onItemMouseOver: function(item) {
68865         var me = this;
68866         if (item.series === me) {
68867             if (me.highlight) {
68868                 me.highlightItem(item);
68869             }
68870             if (me.tooltip) {
68871                 me.showTip(item);
68872             }
68873         }
68874     },
68875
68876     // @private wrapper for the itemmouseout event.
68877     onItemMouseOut: function(item) {
68878         var me = this;
68879         if (item.series === me) {
68880             me.unHighlightItem();
68881             if (me.tooltip) {
68882                 me.hideTip(item);
68883             }
68884         }
68885     },
68886
68887     // @private wrapper for the mouseleave event.
68888     onMouseLeave: function() {
68889         var me = this;
68890         me.unHighlightItem();
68891         if (me.tooltip) {
68892             me.hideTip();
68893         }
68894     },
68895
68896     /**
68897      * For a given x/y point relative to the Surface, find a corresponding item from this
68898      * series, if any.
68899      * @param {Number} x
68900      * @param {Number} y
68901      * @return {Object} An object describing the item, or null if there is no matching item.
68902      * The exact contents of this object will vary by series type, but should always contain the following:
68903      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
68904      * @return {Object} return.value the value(s) of the item's data point
68905      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
68906      * for this data item, which can be used as e.g. a tooltip anchor point.
68907      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
68908      */
68909     getItemForPoint: function(x, y) {
68910         //if there are no items to query just return null.
68911         if (!this.items || !this.items.length || this.seriesIsHidden) {
68912             return null;
68913         }
68914         var me = this,
68915             items = me.items,
68916             bbox = me.bbox,
68917             item, i, ln;
68918         // Check bounds
68919         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
68920             return null;
68921         }
68922         for (i = 0, ln = items.length; i < ln; i++) {
68923             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
68924                 return items[i];
68925             }
68926         }
68927
68928         return null;
68929     },
68930
68931     isItemInPoint: function(x, y, item, i) {
68932         return false;
68933     },
68934
68935     /**
68936      * Hides all the elements in the series.
68937      */
68938     hideAll: function() {
68939         var me = this,
68940             items = me.items,
68941             item, len, i, j, l, sprite, shadows;
68942
68943         me.seriesIsHidden = true;
68944         me._prevShowMarkers = me.showMarkers;
68945
68946         me.showMarkers = false;
68947         //hide all labels
68948         me.hideLabels(0);
68949         //hide all sprites
68950         for (i = 0, len = items.length; i < len; i++) {
68951             item = items[i];
68952             sprite = item.sprite;
68953             if (sprite) {
68954                 sprite.setAttributes({
68955                     hidden: true
68956                 }, true);
68957             }
68958
68959             if (sprite && sprite.shadows) {
68960                 shadows = sprite.shadows;
68961                 for (j = 0, l = shadows.length; j < l; ++j) {
68962                     shadows[j].setAttributes({
68963                         hidden: true
68964                     }, true);
68965                 }
68966             }
68967         }
68968     },
68969
68970     /**
68971      * Shows all the elements in the series.
68972      */
68973     showAll: function() {
68974         var me = this,
68975             prevAnimate = me.chart.animate;
68976         me.chart.animate = false;
68977         me.seriesIsHidden = false;
68978         me.showMarkers = me._prevShowMarkers;
68979         me.drawSeries();
68980         me.chart.animate = prevAnimate;
68981     },
68982
68983     /**
68984      * Returns a string with the color to be used for the series legend item.
68985      */
68986     getLegendColor: function(index) {
68987         var me = this, fill, stroke;
68988         if (me.seriesStyle) {
68989             fill = me.seriesStyle.fill;
68990             stroke = me.seriesStyle.stroke;
68991             if (fill && fill != 'none') {
68992                 return fill;
68993             }
68994             return stroke;
68995         }
68996         return '#000';
68997     },
68998
68999     /**
69000      * Checks whether the data field should be visible in the legend
69001      * @private
69002      * @param {Number} index The index of the current item
69003      */
69004     visibleInLegend: function(index){
69005         var excludes = this.__excludes;
69006         if (excludes) {
69007             return !excludes[index];
69008         }
69009         return !this.seriesIsHidden;
69010     },
69011
69012     /**
69013      * Changes the value of the {@link #title} for the series.
69014      * Arguments can take two forms:
69015      * <ul>
69016      * <li>A single String value: this will be used as the new single title for the series (applies
69017      * to series with only one yField)</li>
69018      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
69019      * </ul>
69020      * @param {Number} index
69021      * @param {String} title
69022      */
69023     setTitle: function(index, title) {
69024         var me = this,
69025             oldTitle = me.title;
69026
69027         if (Ext.isString(index)) {
69028             title = index;
69029             index = 0;
69030         }
69031
69032         if (Ext.isArray(oldTitle)) {
69033             oldTitle[index] = title;
69034         } else {
69035             me.title = title;
69036         }
69037
69038         me.fireEvent('titlechange', title, index);
69039     }
69040 });
69041
69042 /**
69043  * @class Ext.chart.series.Cartesian
69044  * @extends Ext.chart.series.Series
69045  *
69046  * Common base class for series implementations which plot values using x/y coordinates.
69047  */
69048 Ext.define('Ext.chart.series.Cartesian', {
69049
69050     /* Begin Definitions */
69051
69052     extend: 'Ext.chart.series.Series',
69053
69054     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
69055
69056     /* End Definitions */
69057
69058     /**
69059      * The field used to access the x axis value from the items from the data
69060      * source.
69061      *
69062      * @cfg xField
69063      * @type String
69064      */
69065     xField: null,
69066
69067     /**
69068      * The field used to access the y-axis value from the items from the data
69069      * source.
69070      *
69071      * @cfg yField
69072      * @type String
69073      */
69074     yField: null,
69075
69076     /**
69077      * @cfg {String} axis
69078      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
69079      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
69080      * relative scale will be used.
69081      */
69082     axis: 'left',
69083
69084     getLegendLabels: function() {
69085         var me = this,
69086             labels = [],
69087             combinations = me.combinations;
69088
69089         Ext.each([].concat(me.yField), function(yField, i) {
69090             var title = me.title;
69091             // Use the 'title' config if present, otherwise use the raw yField name
69092             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
69093         });
69094
69095         // Handle yFields combined via legend drag-drop
69096         if (combinations) {
69097             Ext.each(combinations, function(combo) {
69098                 var label0 = labels[combo[0]],
69099                     label1 = labels[combo[1]];
69100                 labels[combo[1]] = label0 + ' & ' + label1;
69101                 labels.splice(combo[0], 1);
69102             });
69103         }
69104
69105         return labels;
69106     },
69107
69108     /**
69109      * @protected Iterates over a given record's values for each of this series's yFields,
69110      * executing a given function for each value. Any yFields that have been combined
69111      * via legend drag-drop will be treated as a single value.
69112      * @param {Ext.data.Model} record
69113      * @param {Function} fn
69114      * @param {Object} scope
69115      */
69116     eachYValue: function(record, fn, scope) {
69117         Ext.each(this.getYValueAccessors(), function(accessor, i) {
69118             fn.call(scope, accessor(record), i);
69119         });
69120     },
69121
69122     /**
69123      * @protected Returns the number of yField values, taking into account fields combined
69124      * via legend drag-drop.
69125      * @return {Number}
69126      */
69127     getYValueCount: function() {
69128         return this.getYValueAccessors().length;
69129     },
69130
69131     combine: function(index1, index2) {
69132         var me = this,
69133             accessors = me.getYValueAccessors(),
69134             accessor1 = accessors[index1],
69135             accessor2 = accessors[index2];
69136
69137         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
69138         accessors[index2] = function(record) {
69139             return accessor1(record) + accessor2(record);
69140         };
69141         accessors.splice(index1, 1);
69142
69143         me.callParent([index1, index2]);
69144     },
69145
69146     clearCombinations: function() {
69147         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
69148         delete this.yValueAccessors;
69149         this.callParent();
69150     },
69151
69152     /**
69153      * @protected Returns an array of functions, each of which returns the value of the yField
69154      * corresponding to function's index in the array, for a given record (each function takes the
69155      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
69156      * this list of accessors will be kept in sync with those combinations.
69157      * @return {Array} array of accessor functions
69158      */
69159     getYValueAccessors: function() {
69160         var me = this,
69161             accessors = me.yValueAccessors;
69162         if (!accessors) {
69163             accessors = me.yValueAccessors = [];
69164             Ext.each([].concat(me.yField), function(yField) {
69165                 accessors.push(function(record) {
69166                     return record.get(yField);
69167                 });
69168             });
69169         }
69170         return accessors;
69171     },
69172
69173     /**
69174      * Calculate the min and max values for this series's xField.
69175      * @return {Array} [min, max]
69176      */
69177     getMinMaxXValues: function() {
69178         var me = this,
69179             min, max,
69180             xField = me.xField;
69181
69182         if (me.getRecordCount() > 0) {
69183             min = Infinity;
69184             max = -min;
69185             me.eachRecord(function(record) {
69186                 var xValue = record.get(xField);
69187                 if (xValue > max) {
69188                     max = xValue;
69189                 }
69190                 if (xValue < min) {
69191                     min = xValue;
69192                 }
69193             });
69194         } else {
69195             min = max = 0;
69196         }
69197         return [min, max];
69198     },
69199
69200     /**
69201      * Calculate the min and max values for this series's yField(s). Takes into account yField
69202      * combinations, exclusions, and stacking.
69203      * @return {Array} [min, max]
69204      */
69205     getMinMaxYValues: function() {
69206         var me = this,
69207             stacked = me.stacked,
69208             min, max,
69209             positiveTotal, negativeTotal;
69210
69211         function eachYValueStacked(yValue, i) {
69212             if (!me.isExcluded(i)) {
69213                 if (yValue < 0) {
69214                     negativeTotal += yValue;
69215                 } else {
69216                     positiveTotal += yValue;
69217                 }
69218             }
69219         }
69220
69221         function eachYValue(yValue, i) {
69222             if (!me.isExcluded(i)) {
69223                 if (yValue > max) {
69224                     max = yValue;
69225                 }
69226                 if (yValue < min) {
69227                     min = yValue;
69228                 }
69229             }
69230         }
69231
69232         if (me.getRecordCount() > 0) {
69233             min = Infinity;
69234             max = -min;
69235             me.eachRecord(function(record) {
69236                 if (stacked) {
69237                     positiveTotal = 0;
69238                     negativeTotal = 0;
69239                     me.eachYValue(record, eachYValueStacked);
69240                     if (positiveTotal > max) {
69241                         max = positiveTotal;
69242                     }
69243                     if (negativeTotal < min) {
69244                         min = negativeTotal;
69245                     }
69246                 } else {
69247                     me.eachYValue(record, eachYValue);
69248                 }
69249             });
69250         } else {
69251             min = max = 0;
69252         }
69253         return [min, max];
69254     },
69255
69256     getAxesForXAndYFields: function() {
69257         var me = this,
69258             axes = me.chart.axes,
69259             axis = [].concat(me.axis),
69260             xAxis, yAxis;
69261
69262         if (Ext.Array.indexOf(axis, 'top') > -1) {
69263             xAxis = 'top';
69264         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
69265             xAxis = 'bottom';
69266         } else {
69267             if (axes.get('top')) {
69268                 xAxis = 'top';
69269             } else if (axes.get('bottom')) {
69270                 xAxis = 'bottom';
69271             }
69272         }
69273
69274         if (Ext.Array.indexOf(axis, 'left') > -1) {
69275             yAxis = 'left';
69276         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
69277             yAxis = 'right';
69278         } else {
69279             if (axes.get('left')) {
69280                 yAxis = 'left';
69281             } else if (axes.get('right')) {
69282                 yAxis = 'right';
69283             }
69284         }
69285
69286         return {
69287             xAxis: xAxis,
69288             yAxis: yAxis
69289         };
69290     }
69291
69292
69293 });
69294
69295 /**
69296  * @class Ext.chart.series.Area
69297  * @extends Ext.chart.series.Cartesian
69298  *
69299  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
69300  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
69301  * documentation for more information. A typical configuration object for the area series could be:
69302  *
69303  *     @example
69304  *     var store = Ext.create('Ext.data.JsonStore', {
69305  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69306  *         data: [
69307  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
69308  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
69309  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
69310  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
69311  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
69312  *         ]
69313  *     });
69314  *
69315  *     Ext.create('Ext.chart.Chart', {
69316  *         renderTo: Ext.getBody(),
69317  *         width: 500,
69318  *         height: 300,
69319  *         store: store,
69320  *         axes: [
69321  *             {
69322  *                 type: 'Numeric',
69323  *                 grid: true,
69324  *                 position: 'left',
69325  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
69326  *                 title: 'Sample Values',
69327  *                 grid: {
69328  *                     odd: {
69329  *                         opacity: 1,
69330  *                         fill: '#ddd',
69331  *                         stroke: '#bbb',
69332  *                         'stroke-width': 1
69333  *                     }
69334  *                 },
69335  *                 minimum: 0,
69336  *                 adjustMinimumByMajorUnit: 0
69337  *             },
69338  *             {
69339  *                 type: 'Category',
69340  *                 position: 'bottom',
69341  *                 fields: ['name'],
69342  *                 title: 'Sample Metrics',
69343  *                 grid: true,
69344  *                 label: {
69345  *                     rotate: {
69346  *                         degrees: 315
69347  *                     }
69348  *                 }
69349  *             }
69350  *         ],
69351  *         series: [{
69352  *             type: 'area',
69353  *             highlight: false,
69354  *             axis: 'left',
69355  *             xField: 'name',
69356  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
69357  *             style: {
69358  *                 opacity: 0.93
69359  *             }
69360  *         }]
69361  *     });
69362  *
69363  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
69364  * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
69365  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
69366  * to the style object.
69367  *
69368  * @xtype area
69369  */
69370 Ext.define('Ext.chart.series.Area', {
69371
69372     /* Begin Definitions */
69373
69374     extend: 'Ext.chart.series.Cartesian',
69375
69376     alias: 'series.area',
69377
69378     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
69379
69380     /* End Definitions */
69381
69382     type: 'area',
69383
69384     // @private Area charts are alyways stacked
69385     stacked: true,
69386
69387     /**
69388      * @cfg {Object} style
69389      * Append styling properties to this object for it to override theme properties.
69390      */
69391     style: {},
69392
69393     constructor: function(config) {
69394         this.callParent(arguments);
69395         var me = this,
69396             surface = me.chart.surface,
69397             i, l;
69398         Ext.apply(me, config, {
69399             __excludes: [],
69400             highlightCfg: {
69401                 lineWidth: 3,
69402                 stroke: '#55c',
69403                 opacity: 0.8,
69404                 color: '#f00'
69405             }
69406         });
69407         if (me.highlight) {
69408             me.highlightSprite = surface.add({
69409                 type: 'path',
69410                 path: ['M', 0, 0],
69411                 zIndex: 1000,
69412                 opacity: 0.3,
69413                 lineWidth: 5,
69414                 hidden: true,
69415                 stroke: '#444'
69416             });
69417         }
69418         me.group = surface.getGroup(me.seriesId);
69419     },
69420
69421     // @private Shrinks dataSets down to a smaller size
69422     shrink: function(xValues, yValues, size) {
69423         var len = xValues.length,
69424             ratio = Math.floor(len / size),
69425             i, j,
69426             xSum = 0,
69427             yCompLen = this.areas.length,
69428             ySum = [],
69429             xRes = [],
69430             yRes = [];
69431         //initialize array
69432         for (j = 0; j < yCompLen; ++j) {
69433             ySum[j] = 0;
69434         }
69435         for (i = 0; i < len; ++i) {
69436             xSum += xValues[i];
69437             for (j = 0; j < yCompLen; ++j) {
69438                 ySum[j] += yValues[i][j];
69439             }
69440             if (i % ratio == 0) {
69441                 //push averages
69442                 xRes.push(xSum/ratio);
69443                 for (j = 0; j < yCompLen; ++j) {
69444                     ySum[j] /= ratio;
69445                 }
69446                 yRes.push(ySum);
69447                 //reset sum accumulators
69448                 xSum = 0;
69449                 for (j = 0, ySum = []; j < yCompLen; ++j) {
69450                     ySum[j] = 0;
69451                 }
69452             }
69453         }
69454         return {
69455             x: xRes,
69456             y: yRes
69457         };
69458     },
69459
69460     // @private Get chart and data boundaries
69461     getBounds: function() {
69462         var me = this,
69463             chart = me.chart,
69464             store = chart.getChartStore(),
69465             areas = [].concat(me.yField),
69466             areasLen = areas.length,
69467             xValues = [],
69468             yValues = [],
69469             infinity = Infinity,
69470             minX = infinity,
69471             minY = infinity,
69472             maxX = -infinity,
69473             maxY = -infinity,
69474             math = Math,
69475             mmin = math.min,
69476             mmax = math.max,
69477             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
69478
69479         me.setBBox();
69480         bbox = me.bbox;
69481
69482         // Run through the axis
69483         if (me.axis) {
69484             axis = chart.axes.get(me.axis);
69485             if (axis) {
69486                 out = axis.calcEnds();
69487                 minY = out.from || axis.prevMin;
69488                 maxY = mmax(out.to || axis.prevMax, 0);
69489             }
69490         }
69491
69492         if (me.yField && !Ext.isNumber(minY)) {
69493             axis = Ext.create('Ext.chart.axis.Axis', {
69494                 chart: chart,
69495                 fields: [].concat(me.yField)
69496             });
69497             out = axis.calcEnds();
69498             minY = out.from || axis.prevMin;
69499             maxY = mmax(out.to || axis.prevMax, 0);
69500         }
69501
69502         if (!Ext.isNumber(minY)) {
69503             minY = 0;
69504         }
69505         if (!Ext.isNumber(maxY)) {
69506             maxY = 0;
69507         }
69508
69509         store.each(function(record, i) {
69510             xValue = record.get(me.xField);
69511             yValue = [];
69512             if (typeof xValue != 'number') {
69513                 xValue = i;
69514             }
69515             xValues.push(xValue);
69516             acumY = 0;
69517             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
69518                 areaElem = record.get(areas[areaIndex]);
69519                 if (typeof areaElem == 'number') {
69520                     minY = mmin(minY, areaElem);
69521                     yValue.push(areaElem);
69522                     acumY += areaElem;
69523                 }
69524             }
69525             minX = mmin(minX, xValue);
69526             maxX = mmax(maxX, xValue);
69527             maxY = mmax(maxY, acumY);
69528             yValues.push(yValue);
69529         }, me);
69530
69531         xScale = bbox.width / ((maxX - minX) || 1);
69532         yScale = bbox.height / ((maxY - minY) || 1);
69533
69534         ln = xValues.length;
69535         if ((ln > bbox.width) && me.areas) {
69536             sumValues = me.shrink(xValues, yValues, bbox.width);
69537             xValues = sumValues.x;
69538             yValues = sumValues.y;
69539         }
69540
69541         return {
69542             bbox: bbox,
69543             minX: minX,
69544             minY: minY,
69545             xValues: xValues,
69546             yValues: yValues,
69547             xScale: xScale,
69548             yScale: yScale,
69549             areasLen: areasLen
69550         };
69551     },
69552
69553     // @private Build an array of paths for the chart
69554     getPaths: function() {
69555         var me = this,
69556             chart = me.chart,
69557             store = chart.getChartStore(),
69558             first = true,
69559             bounds = me.getBounds(),
69560             bbox = bounds.bbox,
69561             items = me.items = [],
69562             componentPaths = [],
69563             componentPath,
69564             paths = [],
69565             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
69566
69567         ln = bounds.xValues.length;
69568         // Start the path
69569         for (i = 0; i < ln; i++) {
69570             xValue = bounds.xValues[i];
69571             yValue = bounds.yValues[i];
69572             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
69573             acumY = 0;
69574             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69575                 // Excluded series
69576                 if (me.__excludes[areaIndex]) {
69577                     continue;
69578                 }
69579                 if (!componentPaths[areaIndex]) {
69580                     componentPaths[areaIndex] = [];
69581                 }
69582                 areaElem = yValue[areaIndex];
69583                 acumY += areaElem;
69584                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
69585                 if (!paths[areaIndex]) {
69586                     paths[areaIndex] = ['M', x, y];
69587                     componentPaths[areaIndex].push(['L', x, y]);
69588                 } else {
69589                     paths[areaIndex].push('L', x, y);
69590                     componentPaths[areaIndex].push(['L', x, y]);
69591                 }
69592                 if (!items[areaIndex]) {
69593                     items[areaIndex] = {
69594                         pointsUp: [],
69595                         pointsDown: [],
69596                         series: me
69597                     };
69598                 }
69599                 items[areaIndex].pointsUp.push([x, y]);
69600             }
69601         }
69602
69603         // Close the paths
69604         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69605             // Excluded series
69606             if (me.__excludes[areaIndex]) {
69607                 continue;
69608             }
69609             path = paths[areaIndex];
69610             // Close bottom path to the axis
69611             if (areaIndex == 0 || first) {
69612                 first = false;
69613                 path.push('L', x, bbox.y + bbox.height,
69614                           'L', bbox.x, bbox.y + bbox.height,
69615                           'Z');
69616             }
69617             // Close other paths to the one before them
69618             else {
69619                 componentPath = componentPaths[prevAreaIndex];
69620                 componentPath.reverse();
69621                 path.push('L', x, componentPath[0][2]);
69622                 for (i = 0; i < ln; i++) {
69623                     path.push(componentPath[i][0],
69624                               componentPath[i][1],
69625                               componentPath[i][2]);
69626                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
69627                 }
69628                 path.push('L', bbox.x, path[2], 'Z');
69629             }
69630             prevAreaIndex = areaIndex;
69631         }
69632         return {
69633             paths: paths,
69634             areasLen: bounds.areasLen
69635         };
69636     },
69637
69638     /**
69639      * Draws the series for the current chart.
69640      */
69641     drawSeries: function() {
69642         var me = this,
69643             chart = me.chart,
69644             store = chart.getChartStore(),
69645             surface = chart.surface,
69646             animate = chart.animate,
69647             group = me.group,
69648             endLineStyle = Ext.apply(me.seriesStyle, me.style),
69649             colorArrayStyle = me.colorArrayStyle,
69650             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
69651             areaIndex, areaElem, paths, path, rendererAttributes;
69652
69653         me.unHighlightItem();
69654         me.cleanHighlights();
69655
69656         if (!store || !store.getCount()) {
69657             return;
69658         }
69659
69660         paths = me.getPaths();
69661
69662         if (!me.areas) {
69663             me.areas = [];
69664         }
69665
69666         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
69667             // Excluded series
69668             if (me.__excludes[areaIndex]) {
69669                 continue;
69670             }
69671             if (!me.areas[areaIndex]) {
69672                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
69673                     type: 'path',
69674                     group: group,
69675                     // 'clip-rect': me.clipBox,
69676                     path: paths.paths[areaIndex],
69677                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
69678                     fill: colorArrayStyle[areaIndex % colorArrayLength]
69679                 }, endLineStyle || {}));
69680             }
69681             areaElem = me.areas[areaIndex];
69682             path = paths.paths[areaIndex];
69683             if (animate) {
69684                 //Add renderer to line. There is not a unique record associated with this.
69685                 rendererAttributes = me.renderer(areaElem, false, {
69686                     path: path,
69687                     // 'clip-rect': me.clipBox,
69688                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69689                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69690                 }, areaIndex, store);
69691                 //fill should not be used here but when drawing the special fill path object
69692                 me.animation = me.onAnimate(areaElem, {
69693                     to: rendererAttributes
69694                 });
69695             } else {
69696                 rendererAttributes = me.renderer(areaElem, false, {
69697                     path: path,
69698                     // 'clip-rect': me.clipBox,
69699                     hidden: false,
69700                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69701                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69702                 }, areaIndex, store);
69703                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
69704             }
69705         }
69706         me.renderLabels();
69707         me.renderCallouts();
69708     },
69709
69710     // @private
69711     onAnimate: function(sprite, attr) {
69712         sprite.show();
69713         return this.callParent(arguments);
69714     },
69715
69716     // @private
69717     onCreateLabel: function(storeItem, item, i, display) {
69718         var me = this,
69719             group = me.labelsGroup,
69720             config = me.label,
69721             bbox = me.bbox,
69722             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
69723
69724         return me.chart.surface.add(Ext.apply({
69725             'type': 'text',
69726             'text-anchor': 'middle',
69727             'group': group,
69728             'x': item.point[0],
69729             'y': bbox.y + bbox.height / 2
69730         }, endLabelStyle || {}));
69731     },
69732
69733     // @private
69734     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
69735         var me = this,
69736             chart = me.chart,
69737             resizing = chart.resizing,
69738             config = me.label,
69739             format = config.renderer,
69740             field = config.field,
69741             bbox = me.bbox,
69742             x = item.point[0],
69743             y = item.point[1],
69744             bb, width, height;
69745
69746         label.setAttributes({
69747             text: format(storeItem.get(field[index])),
69748             hidden: true
69749         }, true);
69750
69751         bb = label.getBBox();
69752         width = bb.width / 2;
69753         height = bb.height / 2;
69754
69755         x = x - width < bbox.x? bbox.x + width : x;
69756         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
69757         y = y - height < bbox.y? bbox.y + height : y;
69758         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
69759
69760         if (me.chart.animate && !me.chart.resizing) {
69761             label.show(true);
69762             me.onAnimate(label, {
69763                 to: {
69764                     x: x,
69765                     y: y
69766                 }
69767             });
69768         } else {
69769             label.setAttributes({
69770                 x: x,
69771                 y: y
69772             }, true);
69773             if (resizing) {
69774                 me.animation.on('afteranimate', function() {
69775                     label.show(true);
69776                 });
69777             } else {
69778                 label.show(true);
69779             }
69780         }
69781     },
69782
69783     // @private
69784     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
69785         var me = this,
69786             chart = me.chart,
69787             surface = chart.surface,
69788             resizing = chart.resizing,
69789             config = me.callouts,
69790             items = me.items,
69791             prev = (i == 0) ? false : items[i -1].point,
69792             next = (i == items.length -1) ? false : items[i +1].point,
69793             cur = item.point,
69794             dir, norm, normal, a, aprev, anext,
69795             bbox = callout.label.getBBox(),
69796             offsetFromViz = 30,
69797             offsetToSide = 10,
69798             offsetBox = 3,
69799             boxx, boxy, boxw, boxh,
69800             p, clipRect = me.clipRect,
69801             x, y;
69802
69803         //get the right two points
69804         if (!prev) {
69805             prev = cur;
69806         }
69807         if (!next) {
69808             next = cur;
69809         }
69810         a = (next[1] - prev[1]) / (next[0] - prev[0]);
69811         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
69812         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
69813
69814         norm = Math.sqrt(1 + a * a);
69815         dir = [1 / norm, a / norm];
69816         normal = [-dir[1], dir[0]];
69817
69818         //keep the label always on the outer part of the "elbow"
69819         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
69820             normal[0] *= -1;
69821             normal[1] *= -1;
69822         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
69823             normal[0] *= -1;
69824             normal[1] *= -1;
69825         }
69826
69827         //position
69828         x = cur[0] + normal[0] * offsetFromViz;
69829         y = cur[1] + normal[1] * offsetFromViz;
69830
69831         //box position and dimensions
69832         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69833         boxy = y - bbox.height /2 - offsetBox;
69834         boxw = bbox.width + 2 * offsetBox;
69835         boxh = bbox.height + 2 * offsetBox;
69836
69837         //now check if we're out of bounds and invert the normal vector correspondingly
69838         //this may add new overlaps between labels (but labels won't be out of bounds).
69839         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
69840             normal[0] *= -1;
69841         }
69842         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
69843             normal[1] *= -1;
69844         }
69845
69846         //update positions
69847         x = cur[0] + normal[0] * offsetFromViz;
69848         y = cur[1] + normal[1] * offsetFromViz;
69849
69850         //update box position and dimensions
69851         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69852         boxy = y - bbox.height /2 - offsetBox;
69853         boxw = bbox.width + 2 * offsetBox;
69854         boxh = bbox.height + 2 * offsetBox;
69855
69856         //set the line from the middle of the pie to the box.
69857         callout.lines.setAttributes({
69858             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
69859         }, true);
69860         //set box position
69861         callout.box.setAttributes({
69862             x: boxx,
69863             y: boxy,
69864             width: boxw,
69865             height: boxh
69866         }, true);
69867         //set text position
69868         callout.label.setAttributes({
69869             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
69870             y: y
69871         }, true);
69872         for (p in callout) {
69873             callout[p].show(true);
69874         }
69875     },
69876
69877     isItemInPoint: function(x, y, item, i) {
69878         var me = this,
69879             pointsUp = item.pointsUp,
69880             pointsDown = item.pointsDown,
69881             abs = Math.abs,
69882             dist = Infinity, p, pln, point;
69883
69884         for (p = 0, pln = pointsUp.length; p < pln; p++) {
69885             point = [pointsUp[p][0], pointsUp[p][1]];
69886             if (dist > abs(x - point[0])) {
69887                 dist = abs(x - point[0]);
69888             } else {
69889                 point = pointsUp[p -1];
69890                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
69891                     item.storeIndex = p -1;
69892                     item.storeField = me.yField[i];
69893                     item.storeItem = me.chart.store.getAt(p -1);
69894                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
69895                     return true;
69896                 } else {
69897                     break;
69898                 }
69899             }
69900         }
69901         return false;
69902     },
69903
69904     /**
69905      * Highlight this entire series.
69906      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69907      */
69908     highlightSeries: function() {
69909         var area, to, fillColor;
69910         if (this._index !== undefined) {
69911             area = this.areas[this._index];
69912             if (area.__highlightAnim) {
69913                 area.__highlightAnim.paused = true;
69914             }
69915             area.__highlighted = true;
69916             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
69917             area.__prevFill = area.__prevFill || area.attr.fill;
69918             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
69919             fillColor = Ext.draw.Color.fromString(area.__prevFill);
69920             to = {
69921                 lineWidth: (area.__prevLineWidth || 0) + 2
69922             };
69923             if (fillColor) {
69924                 to.fill = fillColor.getLighter(0.2).toString();
69925             }
69926             else {
69927                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
69928             }
69929             if (this.chart.animate) {
69930                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
69931                     target: area,
69932                     to: to
69933                 }, this.chart.animate));
69934             }
69935             else {
69936                 area.setAttributes(to, true);
69937             }
69938         }
69939     },
69940
69941     /**
69942      * UnHighlight this entire series.
69943      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69944      */
69945     unHighlightSeries: function() {
69946         var area;
69947         if (this._index !== undefined) {
69948             area = this.areas[this._index];
69949             if (area.__highlightAnim) {
69950                 area.__highlightAnim.paused = true;
69951             }
69952             if (area.__highlighted) {
69953                 area.__highlighted = false;
69954                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
69955                     target: area,
69956                     to: {
69957                         fill: area.__prevFill,
69958                         opacity: area.__prevOpacity,
69959                         lineWidth: area.__prevLineWidth
69960                     }
69961                 });
69962             }
69963         }
69964     },
69965
69966     /**
69967      * Highlight the specified item. If no item is provided the whole series will be highlighted.
69968      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69969      */
69970     highlightItem: function(item) {
69971         var me = this,
69972             points, path;
69973         if (!item) {
69974             this.highlightSeries();
69975             return;
69976         }
69977         points = item._points;
69978         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
69979                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
69980         me.highlightSprite.setAttributes({
69981             path: path,
69982             hidden: false
69983         }, true);
69984     },
69985
69986     /**
69987      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
69988      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
69989      */
69990     unHighlightItem: function(item) {
69991         if (!item) {
69992             this.unHighlightSeries();
69993         }
69994
69995         if (this.highlightSprite) {
69996             this.highlightSprite.hide(true);
69997         }
69998     },
69999
70000     // @private
70001     hideAll: function() {
70002         if (!isNaN(this._index)) {
70003             this.__excludes[this._index] = true;
70004             this.areas[this._index].hide(true);
70005             this.drawSeries();
70006         }
70007     },
70008
70009     // @private
70010     showAll: function() {
70011         if (!isNaN(this._index)) {
70012             this.__excludes[this._index] = false;
70013             this.areas[this._index].show(true);
70014             this.drawSeries();
70015         }
70016     },
70017
70018     /**
70019      * Returns the color of the series (to be displayed as color for the series legend item).
70020      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70021      */
70022     getLegendColor: function(index) {
70023         var me = this;
70024         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70025     }
70026 });
70027 /**
70028  * @class Ext.chart.series.Area
70029  * @extends Ext.chart.series.Cartesian
70030  *
70031  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
70032  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
70033  * documentation for more information. A typical configuration object for the area series could be:
70034  *
70035  *     @example
70036  *     var store = Ext.create('Ext.data.JsonStore', {
70037  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70038  *         data: [
70039  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70040  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70041  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70042  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70043  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70044  *         ]
70045  *     });
70046  *
70047  *     Ext.create('Ext.chart.Chart', {
70048  *         renderTo: Ext.getBody(),
70049  *         width: 500,
70050  *         height: 300,
70051  *         store: store,
70052  *         axes: [
70053  *             {
70054  *                 type: 'Numeric',
70055  *                 grid: true,
70056  *                 position: 'left',
70057  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70058  *                 title: 'Sample Values',
70059  *                 grid: {
70060  *                     odd: {
70061  *                         opacity: 1,
70062  *                         fill: '#ddd',
70063  *                         stroke: '#bbb',
70064  *                         'stroke-width': 1
70065  *                     }
70066  *                 },
70067  *                 minimum: 0,
70068  *                 adjustMinimumByMajorUnit: 0
70069  *             },
70070  *             {
70071  *                 type: 'Category',
70072  *                 position: 'bottom',
70073  *                 fields: ['name'],
70074  *                 title: 'Sample Metrics',
70075  *                 grid: true,
70076  *                 label: {
70077  *                     rotate: {
70078  *                         degrees: 315
70079  *                     }
70080  *                 }
70081  *             }
70082  *         ],
70083  *         series: [{
70084  *             type: 'area',
70085  *             highlight: false,
70086  *             axis: 'left',
70087  *             xField: 'name',
70088  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70089  *             style: {
70090  *                 opacity: 0.93
70091  *             }
70092  *         }]
70093  *     });
70094  *
70095  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70096  * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
70097  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70098  * to the style object.
70099  *
70100  * @xtype area
70101  */
70102 Ext.define('Ext.chart.series.Area', {
70103
70104     /* Begin Definitions */
70105
70106     extend: 'Ext.chart.series.Cartesian',
70107
70108     alias: 'series.area',
70109
70110     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70111
70112     /* End Definitions */
70113
70114     type: 'area',
70115
70116     // @private Area charts are alyways stacked
70117     stacked: true,
70118
70119     /**
70120      * @cfg {Object} style
70121      * Append styling properties to this object for it to override theme properties.
70122      */
70123     style: {},
70124
70125     constructor: function(config) {
70126         this.callParent(arguments);
70127         var me = this,
70128             surface = me.chart.surface,
70129             i, l;
70130         Ext.apply(me, config, {
70131             __excludes: [],
70132             highlightCfg: {
70133                 lineWidth: 3,
70134                 stroke: '#55c',
70135                 opacity: 0.8,
70136                 color: '#f00'
70137             }
70138         });
70139         if (me.highlight) {
70140             me.highlightSprite = surface.add({
70141                 type: 'path',
70142                 path: ['M', 0, 0],
70143                 zIndex: 1000,
70144                 opacity: 0.3,
70145                 lineWidth: 5,
70146                 hidden: true,
70147                 stroke: '#444'
70148             });
70149         }
70150         me.group = surface.getGroup(me.seriesId);
70151     },
70152
70153     // @private Shrinks dataSets down to a smaller size
70154     shrink: function(xValues, yValues, size) {
70155         var len = xValues.length,
70156             ratio = Math.floor(len / size),
70157             i, j,
70158             xSum = 0,
70159             yCompLen = this.areas.length,
70160             ySum = [],
70161             xRes = [],
70162             yRes = [];
70163         //initialize array
70164         for (j = 0; j < yCompLen; ++j) {
70165             ySum[j] = 0;
70166         }
70167         for (i = 0; i < len; ++i) {
70168             xSum += xValues[i];
70169             for (j = 0; j < yCompLen; ++j) {
70170                 ySum[j] += yValues[i][j];
70171             }
70172             if (i % ratio == 0) {
70173                 //push averages
70174                 xRes.push(xSum/ratio);
70175                 for (j = 0; j < yCompLen; ++j) {
70176                     ySum[j] /= ratio;
70177                 }
70178                 yRes.push(ySum);
70179                 //reset sum accumulators
70180                 xSum = 0;
70181                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70182                     ySum[j] = 0;
70183                 }
70184             }
70185         }
70186         return {
70187             x: xRes,
70188             y: yRes
70189         };
70190     },
70191
70192     // @private Get chart and data boundaries
70193     getBounds: function() {
70194         var me = this,
70195             chart = me.chart,
70196             store = chart.getChartStore(),
70197             areas = [].concat(me.yField),
70198             areasLen = areas.length,
70199             xValues = [],
70200             yValues = [],
70201             infinity = Infinity,
70202             minX = infinity,
70203             minY = infinity,
70204             maxX = -infinity,
70205             maxY = -infinity,
70206             math = Math,
70207             mmin = math.min,
70208             mmax = math.max,
70209             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70210
70211         me.setBBox();
70212         bbox = me.bbox;
70213
70214         // Run through the axis
70215         if (me.axis) {
70216             axis = chart.axes.get(me.axis);
70217             if (axis) {
70218                 out = axis.calcEnds();
70219                 minY = out.from || axis.prevMin;
70220                 maxY = mmax(out.to || axis.prevMax, 0);
70221             }
70222         }
70223
70224         if (me.yField && !Ext.isNumber(minY)) {
70225             axis = Ext.create('Ext.chart.axis.Axis', {
70226                 chart: chart,
70227                 fields: [].concat(me.yField)
70228             });
70229             out = axis.calcEnds();
70230             minY = out.from || axis.prevMin;
70231             maxY = mmax(out.to || axis.prevMax, 0);
70232         }
70233
70234         if (!Ext.isNumber(minY)) {
70235             minY = 0;
70236         }
70237         if (!Ext.isNumber(maxY)) {
70238             maxY = 0;
70239         }
70240
70241         store.each(function(record, i) {
70242             xValue = record.get(me.xField);
70243             yValue = [];
70244             if (typeof xValue != 'number') {
70245                 xValue = i;
70246             }
70247             xValues.push(xValue);
70248             acumY = 0;
70249             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70250                 areaElem = record.get(areas[areaIndex]);
70251                 if (typeof areaElem == 'number') {
70252                     minY = mmin(minY, areaElem);
70253                     yValue.push(areaElem);
70254                     acumY += areaElem;
70255                 }
70256             }
70257             minX = mmin(minX, xValue);
70258             maxX = mmax(maxX, xValue);
70259             maxY = mmax(maxY, acumY);
70260             yValues.push(yValue);
70261         }, me);
70262
70263         xScale = bbox.width / ((maxX - minX) || 1);
70264         yScale = bbox.height / ((maxY - minY) || 1);
70265
70266         ln = xValues.length;
70267         if ((ln > bbox.width) && me.areas) {
70268             sumValues = me.shrink(xValues, yValues, bbox.width);
70269             xValues = sumValues.x;
70270             yValues = sumValues.y;
70271         }
70272
70273         return {
70274             bbox: bbox,
70275             minX: minX,
70276             minY: minY,
70277             xValues: xValues,
70278             yValues: yValues,
70279             xScale: xScale,
70280             yScale: yScale,
70281             areasLen: areasLen
70282         };
70283     },
70284
70285     // @private Build an array of paths for the chart
70286     getPaths: function() {
70287         var me = this,
70288             chart = me.chart,
70289             store = chart.getChartStore(),
70290             first = true,
70291             bounds = me.getBounds(),
70292             bbox = bounds.bbox,
70293             items = me.items = [],
70294             componentPaths = [],
70295             componentPath,
70296             paths = [],
70297             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70298
70299         ln = bounds.xValues.length;
70300         // Start the path
70301         for (i = 0; i < ln; i++) {
70302             xValue = bounds.xValues[i];
70303             yValue = bounds.yValues[i];
70304             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70305             acumY = 0;
70306             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70307                 // Excluded series
70308                 if (me.__excludes[areaIndex]) {
70309                     continue;
70310                 }
70311                 if (!componentPaths[areaIndex]) {
70312                     componentPaths[areaIndex] = [];
70313                 }
70314                 areaElem = yValue[areaIndex];
70315                 acumY += areaElem;
70316                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70317                 if (!paths[areaIndex]) {
70318                     paths[areaIndex] = ['M', x, y];
70319                     componentPaths[areaIndex].push(['L', x, y]);
70320                 } else {
70321                     paths[areaIndex].push('L', x, y);
70322                     componentPaths[areaIndex].push(['L', x, y]);
70323                 }
70324                 if (!items[areaIndex]) {
70325                     items[areaIndex] = {
70326                         pointsUp: [],
70327                         pointsDown: [],
70328                         series: me
70329                     };
70330                 }
70331                 items[areaIndex].pointsUp.push([x, y]);
70332             }
70333         }
70334
70335         // Close the paths
70336         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70337             // Excluded series
70338             if (me.__excludes[areaIndex]) {
70339                 continue;
70340             }
70341             path = paths[areaIndex];
70342             // Close bottom path to the axis
70343             if (areaIndex == 0 || first) {
70344                 first = false;
70345                 path.push('L', x, bbox.y + bbox.height,
70346                           'L', bbox.x, bbox.y + bbox.height,
70347                           'Z');
70348             }
70349             // Close other paths to the one before them
70350             else {
70351                 componentPath = componentPaths[prevAreaIndex];
70352                 componentPath.reverse();
70353                 path.push('L', x, componentPath[0][2]);
70354                 for (i = 0; i < ln; i++) {
70355                     path.push(componentPath[i][0],
70356                               componentPath[i][1],
70357                               componentPath[i][2]);
70358                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
70359                 }
70360                 path.push('L', bbox.x, path[2], 'Z');
70361             }
70362             prevAreaIndex = areaIndex;
70363         }
70364         return {
70365             paths: paths,
70366             areasLen: bounds.areasLen
70367         };
70368     },
70369
70370     /**
70371      * Draws the series for the current chart.
70372      */
70373     drawSeries: function() {
70374         var me = this,
70375             chart = me.chart,
70376             store = chart.getChartStore(),
70377             surface = chart.surface,
70378             animate = chart.animate,
70379             group = me.group,
70380             endLineStyle = Ext.apply(me.seriesStyle, me.style),
70381             colorArrayStyle = me.colorArrayStyle,
70382             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
70383             areaIndex, areaElem, paths, path, rendererAttributes;
70384
70385         me.unHighlightItem();
70386         me.cleanHighlights();
70387
70388         if (!store || !store.getCount()) {
70389             return;
70390         }
70391
70392         paths = me.getPaths();
70393
70394         if (!me.areas) {
70395             me.areas = [];
70396         }
70397
70398         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
70399             // Excluded series
70400             if (me.__excludes[areaIndex]) {
70401                 continue;
70402             }
70403             if (!me.areas[areaIndex]) {
70404                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
70405                     type: 'path',
70406                     group: group,
70407                     // 'clip-rect': me.clipBox,
70408                     path: paths.paths[areaIndex],
70409                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
70410                     fill: colorArrayStyle[areaIndex % colorArrayLength]
70411                 }, endLineStyle || {}));
70412             }
70413             areaElem = me.areas[areaIndex];
70414             path = paths.paths[areaIndex];
70415             if (animate) {
70416                 //Add renderer to line. There is not a unique record associated with this.
70417                 rendererAttributes = me.renderer(areaElem, false, {
70418                     path: path,
70419                     // 'clip-rect': me.clipBox,
70420                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70421                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70422                 }, areaIndex, store);
70423                 //fill should not be used here but when drawing the special fill path object
70424                 me.animation = me.onAnimate(areaElem, {
70425                     to: rendererAttributes
70426                 });
70427             } else {
70428                 rendererAttributes = me.renderer(areaElem, false, {
70429                     path: path,
70430                     // 'clip-rect': me.clipBox,
70431                     hidden: false,
70432                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70433                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70434                 }, areaIndex, store);
70435                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
70436             }
70437         }
70438         me.renderLabels();
70439         me.renderCallouts();
70440     },
70441
70442     // @private
70443     onAnimate: function(sprite, attr) {
70444         sprite.show();
70445         return this.callParent(arguments);
70446     },
70447
70448     // @private
70449     onCreateLabel: function(storeItem, item, i, display) {
70450         var me = this,
70451             group = me.labelsGroup,
70452             config = me.label,
70453             bbox = me.bbox,
70454             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
70455
70456         return me.chart.surface.add(Ext.apply({
70457             'type': 'text',
70458             'text-anchor': 'middle',
70459             'group': group,
70460             'x': item.point[0],
70461             'y': bbox.y + bbox.height / 2
70462         }, endLabelStyle || {}));
70463     },
70464
70465     // @private
70466     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
70467         var me = this,
70468             chart = me.chart,
70469             resizing = chart.resizing,
70470             config = me.label,
70471             format = config.renderer,
70472             field = config.field,
70473             bbox = me.bbox,
70474             x = item.point[0],
70475             y = item.point[1],
70476             bb, width, height;
70477
70478         label.setAttributes({
70479             text: format(storeItem.get(field[index])),
70480             hidden: true
70481         }, true);
70482
70483         bb = label.getBBox();
70484         width = bb.width / 2;
70485         height = bb.height / 2;
70486
70487         x = x - width < bbox.x? bbox.x + width : x;
70488         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70489         y = y - height < bbox.y? bbox.y + height : y;
70490         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70491
70492         if (me.chart.animate && !me.chart.resizing) {
70493             label.show(true);
70494             me.onAnimate(label, {
70495                 to: {
70496                     x: x,
70497                     y: y
70498                 }
70499             });
70500         } else {
70501             label.setAttributes({
70502                 x: x,
70503                 y: y
70504             }, true);
70505             if (resizing) {
70506                 me.animation.on('afteranimate', function() {
70507                     label.show(true);
70508                 });
70509             } else {
70510                 label.show(true);
70511             }
70512         }
70513     },
70514
70515     // @private
70516     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
70517         var me = this,
70518             chart = me.chart,
70519             surface = chart.surface,
70520             resizing = chart.resizing,
70521             config = me.callouts,
70522             items = me.items,
70523             prev = (i == 0) ? false : items[i -1].point,
70524             next = (i == items.length -1) ? false : items[i +1].point,
70525             cur = item.point,
70526             dir, norm, normal, a, aprev, anext,
70527             bbox = callout.label.getBBox(),
70528             offsetFromViz = 30,
70529             offsetToSide = 10,
70530             offsetBox = 3,
70531             boxx, boxy, boxw, boxh,
70532             p, clipRect = me.clipRect,
70533             x, y;
70534
70535         //get the right two points
70536         if (!prev) {
70537             prev = cur;
70538         }
70539         if (!next) {
70540             next = cur;
70541         }
70542         a = (next[1] - prev[1]) / (next[0] - prev[0]);
70543         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
70544         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
70545
70546         norm = Math.sqrt(1 + a * a);
70547         dir = [1 / norm, a / norm];
70548         normal = [-dir[1], dir[0]];
70549
70550         //keep the label always on the outer part of the "elbow"
70551         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
70552             normal[0] *= -1;
70553             normal[1] *= -1;
70554         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
70555             normal[0] *= -1;
70556             normal[1] *= -1;
70557         }
70558
70559         //position
70560         x = cur[0] + normal[0] * offsetFromViz;
70561         y = cur[1] + normal[1] * offsetFromViz;
70562
70563         //box position and dimensions
70564         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70565         boxy = y - bbox.height /2 - offsetBox;
70566         boxw = bbox.width + 2 * offsetBox;
70567         boxh = bbox.height + 2 * offsetBox;
70568
70569         //now check if we're out of bounds and invert the normal vector correspondingly
70570         //this may add new overlaps between labels (but labels won't be out of bounds).
70571         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70572             normal[0] *= -1;
70573         }
70574         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70575             normal[1] *= -1;
70576         }
70577
70578         //update positions
70579         x = cur[0] + normal[0] * offsetFromViz;
70580         y = cur[1] + normal[1] * offsetFromViz;
70581
70582         //update box position and dimensions
70583         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70584         boxy = y - bbox.height /2 - offsetBox;
70585         boxw = bbox.width + 2 * offsetBox;
70586         boxh = bbox.height + 2 * offsetBox;
70587
70588         //set the line from the middle of the pie to the box.
70589         callout.lines.setAttributes({
70590             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70591         }, true);
70592         //set box position
70593         callout.box.setAttributes({
70594             x: boxx,
70595             y: boxy,
70596             width: boxw,
70597             height: boxh
70598         }, true);
70599         //set text position
70600         callout.label.setAttributes({
70601             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70602             y: y
70603         }, true);
70604         for (p in callout) {
70605             callout[p].show(true);
70606         }
70607     },
70608
70609     isItemInPoint: function(x, y, item, i) {
70610         var me = this,
70611             pointsUp = item.pointsUp,
70612             pointsDown = item.pointsDown,
70613             abs = Math.abs,
70614             dist = Infinity, p, pln, point;
70615
70616         for (p = 0, pln = pointsUp.length; p < pln; p++) {
70617             point = [pointsUp[p][0], pointsUp[p][1]];
70618             if (dist > abs(x - point[0])) {
70619                 dist = abs(x - point[0]);
70620             } else {
70621                 point = pointsUp[p -1];
70622                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
70623                     item.storeIndex = p -1;
70624                     item.storeField = me.yField[i];
70625                     item.storeItem = me.chart.store.getAt(p -1);
70626                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
70627                     return true;
70628                 } else {
70629                     break;
70630                 }
70631             }
70632         }
70633         return false;
70634     },
70635
70636     /**
70637      * Highlight this entire series.
70638      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70639      */
70640     highlightSeries: function() {
70641         var area, to, fillColor;
70642         if (this._index !== undefined) {
70643             area = this.areas[this._index];
70644             if (area.__highlightAnim) {
70645                 area.__highlightAnim.paused = true;
70646             }
70647             area.__highlighted = true;
70648             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
70649             area.__prevFill = area.__prevFill || area.attr.fill;
70650             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
70651             fillColor = Ext.draw.Color.fromString(area.__prevFill);
70652             to = {
70653                 lineWidth: (area.__prevLineWidth || 0) + 2
70654             };
70655             if (fillColor) {
70656                 to.fill = fillColor.getLighter(0.2).toString();
70657             }
70658             else {
70659                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
70660             }
70661             if (this.chart.animate) {
70662                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
70663                     target: area,
70664                     to: to
70665                 }, this.chart.animate));
70666             }
70667             else {
70668                 area.setAttributes(to, true);
70669             }
70670         }
70671     },
70672
70673     /**
70674      * UnHighlight this entire series.
70675      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70676      */
70677     unHighlightSeries: function() {
70678         var area;
70679         if (this._index !== undefined) {
70680             area = this.areas[this._index];
70681             if (area.__highlightAnim) {
70682                 area.__highlightAnim.paused = true;
70683             }
70684             if (area.__highlighted) {
70685                 area.__highlighted = false;
70686                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
70687                     target: area,
70688                     to: {
70689                         fill: area.__prevFill,
70690                         opacity: area.__prevOpacity,
70691                         lineWidth: area.__prevLineWidth
70692                     }
70693                 });
70694             }
70695         }
70696     },
70697
70698     /**
70699      * Highlight the specified item. If no item is provided the whole series will be highlighted.
70700      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70701      */
70702     highlightItem: function(item) {
70703         var me = this,
70704             points, path;
70705         if (!item) {
70706             this.highlightSeries();
70707             return;
70708         }
70709         points = item._points;
70710         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
70711                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
70712         me.highlightSprite.setAttributes({
70713             path: path,
70714             hidden: false
70715         }, true);
70716     },
70717
70718     /**
70719      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70720      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70721      */
70722     unHighlightItem: function(item) {
70723         if (!item) {
70724             this.unHighlightSeries();
70725         }
70726
70727         if (this.highlightSprite) {
70728             this.highlightSprite.hide(true);
70729         }
70730     },
70731
70732     // @private
70733     hideAll: function() {
70734         if (!isNaN(this._index)) {
70735             this.__excludes[this._index] = true;
70736             this.areas[this._index].hide(true);
70737             this.drawSeries();
70738         }
70739     },
70740
70741     // @private
70742     showAll: function() {
70743         if (!isNaN(this._index)) {
70744             this.__excludes[this._index] = false;
70745             this.areas[this._index].show(true);
70746             this.drawSeries();
70747         }
70748     },
70749
70750     /**
70751      * Returns the color of the series (to be displayed as color for the series legend item).
70752      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70753      */
70754     getLegendColor: function(index) {
70755         var me = this;
70756         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70757     }
70758 });
70759
70760 /**
70761  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
70762  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
70763  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
70764  * A typical configuration object for the bar series could be:
70765  *
70766  *     @example
70767  *     var store = Ext.create('Ext.data.JsonStore', {
70768  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70769  *         data: [
70770  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70771  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70772  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70773  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70774  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70775  *         ]
70776  *     });
70777  *
70778  *     Ext.create('Ext.chart.Chart', {
70779  *         renderTo: Ext.getBody(),
70780  *         width: 500,
70781  *         height: 300,
70782  *         animate: true,
70783  *         store: store,
70784  *         axes: [{
70785  *             type: 'Numeric',
70786  *             position: 'bottom',
70787  *             fields: ['data1'],
70788  *             label: {
70789  *                 renderer: Ext.util.Format.numberRenderer('0,0')
70790  *             },
70791  *             title: 'Sample Values',
70792  *             grid: true,
70793  *             minimum: 0
70794  *         }, {
70795  *             type: 'Category',
70796  *             position: 'left',
70797  *             fields: ['name'],
70798  *             title: 'Sample Metrics'
70799  *         }],
70800  *         series: [{
70801  *             type: 'bar',
70802  *             axis: 'bottom',
70803  *             highlight: true,
70804  *             tips: {
70805  *               trackMouse: true,
70806  *               width: 140,
70807  *               height: 28,
70808  *               renderer: function(storeItem, item) {
70809  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
70810  *               }
70811  *             },
70812  *             label: {
70813  *               display: 'insideEnd',
70814  *                 field: 'data1',
70815  *                 renderer: Ext.util.Format.numberRenderer('0'),
70816  *                 orientation: 'horizontal',
70817  *                 color: '#333',
70818  *                 'text-anchor': 'middle'
70819  *             },
70820  *             xField: 'name',
70821  *             yField: ['data1']
70822  *         }]
70823  *     });
70824  *
70825  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
70826  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
70827  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
70828  * to display the information found in the `data1` property of each element store, to render a formated text with the
70829  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
70830  * other styles like `color`, `text-anchor`, etc.
70831  */
70832 Ext.define('Ext.chart.series.Bar', {
70833
70834     /* Begin Definitions */
70835
70836     extend: 'Ext.chart.series.Cartesian',
70837
70838     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
70839
70840     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
70841
70842     /* End Definitions */
70843
70844     type: 'bar',
70845
70846     alias: 'series.bar',
70847     /**
70848      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
70849      */
70850     column: false,
70851
70852     /**
70853      * @cfg style Style properties that will override the theming series styles.
70854      */
70855     style: {},
70856
70857     /**
70858      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
70859      */
70860     gutter: 38.2,
70861
70862     /**
70863      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
70864      */
70865     groupGutter: 38.2,
70866
70867     /**
70868      * @cfg {Number} xPadding Padding between the left/right axes and the bars
70869      */
70870     xPadding: 0,
70871
70872     /**
70873      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
70874      */
70875     yPadding: 10,
70876
70877     constructor: function(config) {
70878         this.callParent(arguments);
70879         var me = this,
70880             surface = me.chart.surface,
70881             shadow = me.chart.shadow,
70882             i, l;
70883         Ext.apply(me, config, {
70884             highlightCfg: {
70885                 lineWidth: 3,
70886                 stroke: '#55c',
70887                 opacity: 0.8,
70888                 color: '#f00'
70889             },
70890
70891             shadowAttributes: [{
70892                 "stroke-width": 6,
70893                 "stroke-opacity": 0.05,
70894                 stroke: 'rgb(200, 200, 200)',
70895                 translate: {
70896                     x: 1.2,
70897                     y: 1.2
70898                 }
70899             }, {
70900                 "stroke-width": 4,
70901                 "stroke-opacity": 0.1,
70902                 stroke: 'rgb(150, 150, 150)',
70903                 translate: {
70904                     x: 0.9,
70905                     y: 0.9
70906                 }
70907             }, {
70908                 "stroke-width": 2,
70909                 "stroke-opacity": 0.15,
70910                 stroke: 'rgb(100, 100, 100)',
70911                 translate: {
70912                     x: 0.6,
70913                     y: 0.6
70914                 }
70915             }]
70916         });
70917         me.group = surface.getGroup(me.seriesId + '-bars');
70918         if (shadow) {
70919             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
70920                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
70921             }
70922         }
70923     },
70924
70925     // @private sets the bar girth.
70926     getBarGirth: function() {
70927         var me = this,
70928             store = me.chart.getChartStore(),
70929             column = me.column,
70930             ln = store.getCount(),
70931             gutter = me.gutter / 100;
70932
70933         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
70934     },
70935
70936     // @private returns the gutters.
70937     getGutters: function() {
70938         var me = this,
70939             column = me.column,
70940             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
70941         return me.column ? [gutter, 0] : [0, gutter];
70942     },
70943
70944     // @private Get chart and data boundaries
70945     getBounds: function() {
70946         var me = this,
70947             chart = me.chart,
70948             store = chart.getChartStore(),
70949             bars = [].concat(me.yField),
70950             barsLen = bars.length,
70951             groupBarsLen = barsLen,
70952             groupGutter = me.groupGutter / 100,
70953             column = me.column,
70954             xPadding = me.xPadding,
70955             yPadding = me.yPadding,
70956             stacked = me.stacked,
70957             barWidth = me.getBarGirth(),
70958             math = Math,
70959             mmax = math.max,
70960             mabs = math.abs,
70961             groupBarWidth, bbox, minY, maxY, axis, out,
70962             scale, zero, total, rec, j, plus, minus;
70963
70964         me.setBBox(true);
70965         bbox = me.bbox;
70966
70967         //Skip excluded series
70968         if (me.__excludes) {
70969             for (j = 0, total = me.__excludes.length; j < total; j++) {
70970                 if (me.__excludes[j]) {
70971                     groupBarsLen--;
70972                 }
70973             }
70974         }
70975
70976         if (me.axis) {
70977             axis = chart.axes.get(me.axis);
70978             if (axis) {
70979                 out = axis.calcEnds();
70980                 minY = out.from;
70981                 maxY = out.to;
70982             }
70983         }
70984
70985         if (me.yField && !Ext.isNumber(minY)) {
70986             axis = Ext.create('Ext.chart.axis.Axis', {
70987                 chart: chart,
70988                 fields: [].concat(me.yField)
70989             });
70990             out = axis.calcEnds();
70991             minY = out.from;
70992             maxY = out.to;
70993         }
70994
70995         if (!Ext.isNumber(minY)) {
70996             minY = 0;
70997         }
70998         if (!Ext.isNumber(maxY)) {
70999             maxY = 0;
71000         }
71001         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
71002         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
71003         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
71004
71005         if (stacked) {
71006             total = [[], []];
71007             store.each(function(record, i) {
71008                 total[0][i] = total[0][i] || 0;
71009                 total[1][i] = total[1][i] || 0;
71010                 for (j = 0; j < barsLen; j++) {
71011                     if (me.__excludes && me.__excludes[j]) {
71012                         continue;
71013                     }
71014                     rec = record.get(bars[j]);
71015                     total[+(rec > 0)][i] += mabs(rec);
71016                 }
71017             });
71018             total[+(maxY > 0)].push(mabs(maxY));
71019             total[+(minY > 0)].push(mabs(minY));
71020             minus = mmax.apply(math, total[0]);
71021             plus = mmax.apply(math, total[1]);
71022             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
71023             zero = zero + minus * scale * (column ? -1 : 1);
71024         }
71025         else if (minY / maxY < 0) {
71026             zero = zero - minY * scale * (column ? -1 : 1);
71027         }
71028         return {
71029             bars: bars,
71030             bbox: bbox,
71031             barsLen: barsLen,
71032             groupBarsLen: groupBarsLen,
71033             barWidth: barWidth,
71034             groupBarWidth: groupBarWidth,
71035             scale: scale,
71036             zero: zero,
71037             xPadding: xPadding,
71038             yPadding: yPadding,
71039             signed: minY / maxY < 0,
71040             minY: minY,
71041             maxY: maxY
71042         };
71043     },
71044
71045     // @private Build an array of paths for the chart
71046     getPaths: function() {
71047         var me = this,
71048             chart = me.chart,
71049             store = chart.getChartStore(),
71050             bounds = me.bounds = me.getBounds(),
71051             items = me.items = [],
71052             gutter = me.gutter / 100,
71053             groupGutter = me.groupGutter / 100,
71054             animate = chart.animate,
71055             column = me.column,
71056             group = me.group,
71057             enableShadows = chart.shadow,
71058             shadowGroups = me.shadowGroups,
71059             shadowAttributes = me.shadowAttributes,
71060             shadowGroupsLn = shadowGroups.length,
71061             bbox = bounds.bbox,
71062             xPadding = me.xPadding,
71063             yPadding = me.yPadding,
71064             stacked = me.stacked,
71065             barsLen = bounds.barsLen,
71066             colors = me.colorArrayStyle,
71067             colorLength = colors && colors.length || 0,
71068             math = Math,
71069             mmax = math.max,
71070             mmin = math.min,
71071             mabs = math.abs,
71072             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
71073             shadowIndex, shadow, sprite, offset, floorY;
71074
71075         store.each(function(record, i, total) {
71076             bottom = bounds.zero;
71077             top = bounds.zero;
71078             totalDim = 0;
71079             totalNegDim = 0;
71080             hasShadow = false;
71081             for (j = 0, counter = 0; j < barsLen; j++) {
71082                 // Excluded series
71083                 if (me.__excludes && me.__excludes[j]) {
71084                     continue;
71085                 }
71086                 yValue = record.get(bounds.bars[j]);
71087                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
71088                 barAttr = {
71089                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
71090                 };
71091                 if (column) {
71092                     Ext.apply(barAttr, {
71093                         height: height,
71094                         width: mmax(bounds.groupBarWidth, 0),
71095                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
71096                         y: bottom - height
71097                     });
71098                 }
71099                 else {
71100                     // draw in reverse order
71101                     offset = (total - 1) - i;
71102                     Ext.apply(barAttr, {
71103                         height: mmax(bounds.groupBarWidth, 0),
71104                         width: height + (bottom == bounds.zero),
71105                         x: bottom + (bottom != bounds.zero),
71106                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
71107                     });
71108                 }
71109                 if (height < 0) {
71110                     if (column) {
71111                         barAttr.y = top;
71112                         barAttr.height = mabs(height);
71113                     } else {
71114                         barAttr.x = top + height;
71115                         barAttr.width = mabs(height);
71116                     }
71117                 }
71118                 if (stacked) {
71119                     if (height < 0) {
71120                         top += height * (column ? -1 : 1);
71121                     } else {
71122                         bottom += height * (column ? -1 : 1);
71123                     }
71124                     totalDim += mabs(height);
71125                     if (height < 0) {
71126                         totalNegDim += mabs(height);
71127                     }
71128                 }
71129                 barAttr.x = Math.floor(barAttr.x) + 1;
71130                 floorY = Math.floor(barAttr.y);
71131                 if (!Ext.isIE9 && barAttr.y > floorY) {
71132                     floorY--;
71133                 }
71134                 barAttr.y = floorY;
71135                 barAttr.width = Math.floor(barAttr.width);
71136                 barAttr.height = Math.floor(barAttr.height);
71137                 items.push({
71138                     series: me,
71139                     storeItem: record,
71140                     value: [record.get(me.xField), yValue],
71141                     attr: barAttr,
71142                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
71143                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
71144                 });
71145                 // When resizing, reset before animating
71146                 if (animate && chart.resizing) {
71147                     attrs = column ? {
71148                         x: barAttr.x,
71149                         y: bounds.zero,
71150                         width: barAttr.width,
71151                         height: 0
71152                     } : {
71153                         x: bounds.zero,
71154                         y: barAttr.y,
71155                         width: 0,
71156                         height: barAttr.height
71157                     };
71158                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
71159                         hasShadow = true;
71160                         //update shadows
71161                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71162                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
71163                             if (shadow) {
71164                                 shadow.setAttributes(attrs, true);
71165                             }
71166                         }
71167                     }
71168                     //update sprite position and width/height
71169                     sprite = group.getAt(i * barsLen + j);
71170                     if (sprite) {
71171                         sprite.setAttributes(attrs, true);
71172                     }
71173                 }
71174                 counter++;
71175             }
71176             if (stacked && items.length) {
71177                 items[i * counter].totalDim = totalDim;
71178                 items[i * counter].totalNegDim = totalNegDim;
71179             }
71180         }, me);
71181     },
71182
71183     // @private render/setAttributes on the shadows
71184     renderShadows: function(i, barAttr, baseAttrs, bounds) {
71185         var me = this,
71186             chart = me.chart,
71187             surface = chart.surface,
71188             animate = chart.animate,
71189             stacked = me.stacked,
71190             shadowGroups = me.shadowGroups,
71191             shadowAttributes = me.shadowAttributes,
71192             shadowGroupsLn = shadowGroups.length,
71193             store = chart.getChartStore(),
71194             column = me.column,
71195             items = me.items,
71196             shadows = [],
71197             zero = bounds.zero,
71198             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
71199
71200         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
71201             j = i / bounds.groupBarsLen;
71202             //create shadows
71203             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71204                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
71205                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
71206                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
71207                 if (!shadow) {
71208                     shadow = surface.add(Ext.apply({
71209                         type: 'rect',
71210                         group: shadowGroups[shadowIndex]
71211                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
71212                 }
71213                 if (stacked) {
71214                     totalDim = items[i].totalDim;
71215                     totalNegDim = items[i].totalNegDim;
71216                     if (column) {
71217                         shadowBarAttr.y = zero - totalNegDim;
71218                         shadowBarAttr.height = totalDim;
71219                     }
71220                     else {
71221                         shadowBarAttr.x = zero - totalNegDim;
71222                         shadowBarAttr.width = totalDim;
71223                     }
71224                 }
71225                 if (animate) {
71226                     if (!stacked) {
71227                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
71228                         me.onAnimate(shadow, { to: rendererAttributes });
71229                     }
71230                     else {
71231                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
71232                         shadow.setAttributes(rendererAttributes, true);
71233                     }
71234                 }
71235                 else {
71236                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
71237                     shadow.setAttributes(rendererAttributes, true);
71238                 }
71239                 shadows.push(shadow);
71240             }
71241         }
71242         return shadows;
71243     },
71244
71245     /**
71246      * Draws the series for the current chart.
71247      */
71248     drawSeries: function() {
71249         var me = this,
71250             chart = me.chart,
71251             store = chart.getChartStore(),
71252             surface = chart.surface,
71253             animate = chart.animate,
71254             stacked = me.stacked,
71255             column = me.column,
71256             enableShadows = chart.shadow,
71257             shadowGroups = me.shadowGroups,
71258             shadowGroupsLn = shadowGroups.length,
71259             group = me.group,
71260             seriesStyle = me.seriesStyle,
71261             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
71262             bounds, endSeriesStyle, barAttr, attrs, anim;
71263
71264         if (!store || !store.getCount()) {
71265             return;
71266         }
71267
71268         //fill colors are taken from the colors array.
71269         delete seriesStyle.fill;
71270         endSeriesStyle = Ext.apply(seriesStyle, this.style);
71271         me.unHighlightItem();
71272         me.cleanHighlights();
71273
71274         me.getPaths();
71275         bounds = me.bounds;
71276         items = me.items;
71277
71278         baseAttrs = column ? {
71279             y: bounds.zero,
71280             height: 0
71281         } : {
71282             x: bounds.zero,
71283             width: 0
71284         };
71285         ln = items.length;
71286         // Create new or reuse sprites and animate/display
71287         for (i = 0; i < ln; i++) {
71288             sprite = group.getAt(i);
71289             barAttr = items[i].attr;
71290
71291             if (enableShadows) {
71292                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
71293             }
71294
71295             // Create a new sprite if needed (no height)
71296             if (!sprite) {
71297                 attrs = Ext.apply({}, baseAttrs, barAttr);
71298                 attrs = Ext.apply(attrs, endSeriesStyle || {});
71299                 sprite = surface.add(Ext.apply({}, {
71300                     type: 'rect',
71301                     group: group
71302                 }, attrs));
71303             }
71304             if (animate) {
71305                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
71306                 sprite._to = rendererAttributes;
71307                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
71308                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
71309                     j = i / bounds.barsLen;
71310                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71311                         anim.on('afteranimate', function() {
71312                             this.show(true);
71313                         }, shadowGroups[shadowIndex].getAt(j));
71314                     }
71315                 }
71316             }
71317             else {
71318                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
71319                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
71320             }
71321             items[i].sprite = sprite;
71322         }
71323
71324         // Hide unused sprites
71325         ln = group.getCount();
71326         for (j = i; j < ln; j++) {
71327             group.getAt(j).hide(true);
71328         }
71329         // Hide unused shadows
71330         if (enableShadows) {
71331             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71332                 shadowGroup = shadowGroups[shadowIndex];
71333                 ln = shadowGroup.getCount();
71334                 for (j = i; j < ln; j++) {
71335                     shadowGroup.getAt(j).hide(true);
71336                 }
71337             }
71338         }
71339         me.renderLabels();
71340     },
71341
71342     // @private handled when creating a label.
71343     onCreateLabel: function(storeItem, item, i, display) {
71344         var me = this,
71345             surface = me.chart.surface,
71346             group = me.labelsGroup,
71347             config = me.label,
71348             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
71349             sprite;
71350         return surface.add(Ext.apply({
71351             type: 'text',
71352             group: group
71353         }, endLabelStyle || {}));
71354     },
71355
71356     // @private callback used when placing a label.
71357     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
71358         // Determine the label's final position. Starts with the configured preferred value but
71359         // may get flipped from inside to outside or vice-versa depending on space.
71360         var me = this,
71361             opt = me.bounds,
71362             groupBarWidth = opt.groupBarWidth,
71363             column = me.column,
71364             chart = me.chart,
71365             chartBBox = chart.chartBBox,
71366             resizing = chart.resizing,
71367             xValue = item.value[0],
71368             yValue = item.value[1],
71369             attr = item.attr,
71370             config = me.label,
71371             rotate = config.orientation == 'vertical',
71372             field = [].concat(config.field),
71373             format = config.renderer,
71374             text = format(storeItem.get(field[index])),
71375             size = me.getLabelSize(text),
71376             width = size.width,
71377             height = size.height,
71378             zero = opt.zero,
71379             outside = 'outside',
71380             insideStart = 'insideStart',
71381             insideEnd = 'insideEnd',
71382             offsetX = 10,
71383             offsetY = 6,
71384             signed = opt.signed,
71385             x, y, finalAttr;
71386
71387         label.setAttributes({
71388             text: text
71389         });
71390
71391         label.isOutside = false;
71392         if (column) {
71393             if (display == outside) {
71394                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
71395                     display = insideEnd;
71396                 }
71397             } else {
71398                 if (height + offsetY > attr.height) {
71399                     display = outside;
71400                     label.isOutside = true;
71401                 }
71402             }
71403             x = attr.x + groupBarWidth / 2;
71404             y = display == insideStart ?
71405                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
71406                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
71407                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
71408         }
71409         else {
71410             if (display == outside) {
71411                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
71412                     display = insideEnd;
71413                 }
71414             }
71415             else {
71416                 if (width + offsetX > attr.width) {
71417                     display = outside;
71418                     label.isOutside = true;
71419                 }
71420             }
71421             x = display == insideStart ?
71422                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
71423                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
71424                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
71425             y = attr.y + groupBarWidth / 2;
71426         }
71427         //set position
71428         finalAttr = {
71429             x: x,
71430             y: y
71431         };
71432         //rotate
71433         if (rotate) {
71434             finalAttr.rotate = {
71435                 x: x,
71436                 y: y,
71437                 degrees: 270
71438             };
71439         }
71440         //check for resizing
71441         if (animate && resizing) {
71442             if (column) {
71443                 x = attr.x + attr.width / 2;
71444                 y = zero;
71445             } else {
71446                 x = zero;
71447                 y = attr.y + attr.height / 2;
71448             }
71449             label.setAttributes({
71450                 x: x,
71451                 y: y
71452             }, true);
71453             if (rotate) {
71454                 label.setAttributes({
71455                     rotate: {
71456                         x: x,
71457                         y: y,
71458                         degrees: 270
71459                     }
71460                 }, true);
71461             }
71462         }
71463         //handle animation
71464         if (animate) {
71465             me.onAnimate(label, { to: finalAttr });
71466         }
71467         else {
71468             label.setAttributes(Ext.apply(finalAttr, {
71469                 hidden: false
71470             }), true);
71471         }
71472     },
71473
71474     /* @private
71475      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
71476      * changing visible sprites.
71477      * @param value
71478      */
71479     getLabelSize: function(value) {
71480         var tester = this.testerLabel,
71481             config = this.label,
71482             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
71483             rotated = config.orientation === 'vertical',
71484             bbox, w, h,
71485             undef;
71486         if (!tester) {
71487             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
71488                 type: 'text',
71489                 opacity: 0
71490             }, endLabelStyle));
71491         }
71492         tester.setAttributes({
71493             text: value
71494         }, true);
71495
71496         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
71497         bbox = tester.getBBox();
71498         w = bbox.width;
71499         h = bbox.height;
71500         return {
71501             width: rotated ? h : w,
71502             height: rotated ? w : h
71503         };
71504     },
71505
71506     // @private used to animate label, markers and other sprites.
71507     onAnimate: function(sprite, attr) {
71508         sprite.show();
71509         return this.callParent(arguments);
71510     },
71511
71512     isItemInPoint: function(x, y, item) {
71513         var bbox = item.sprite.getBBox();
71514         return bbox.x <= x && bbox.y <= y
71515             && (bbox.x + bbox.width) >= x
71516             && (bbox.y + bbox.height) >= y;
71517     },
71518
71519     // @private hide all markers
71520     hideAll: function() {
71521         var axes = this.chart.axes;
71522         if (!isNaN(this._index)) {
71523             if (!this.__excludes) {
71524                 this.__excludes = [];
71525             }
71526             this.__excludes[this._index] = true;
71527             this.drawSeries();
71528             axes.each(function(axis) {
71529                 axis.drawAxis();
71530             });
71531         }
71532     },
71533
71534     // @private show all markers
71535     showAll: function() {
71536         var axes = this.chart.axes;
71537         if (!isNaN(this._index)) {
71538             if (!this.__excludes) {
71539                 this.__excludes = [];
71540             }
71541             this.__excludes[this._index] = false;
71542             this.drawSeries();
71543             axes.each(function(axis) {
71544                 axis.drawAxis();
71545             });
71546         }
71547     },
71548
71549     /**
71550      * Returns a string with the color to be used for the series legend item.
71551      * @param index
71552      */
71553     getLegendColor: function(index) {
71554         var me = this,
71555             colorLength = me.colorArrayStyle.length;
71556
71557         if (me.style && me.style.fill) {
71558             return me.style.fill;
71559         } else {
71560             return me.colorArrayStyle[index % colorLength];
71561         }
71562     },
71563
71564     highlightItem: function(item) {
71565         this.callParent(arguments);
71566         this.renderLabels();
71567     },
71568
71569     unHighlightItem: function() {
71570         this.callParent(arguments);
71571         this.renderLabels();
71572     },
71573
71574     cleanHighlights: function() {
71575         this.callParent(arguments);
71576         this.renderLabels();
71577     }
71578 });
71579 /**
71580  * @class Ext.chart.series.Column
71581  * @extends Ext.chart.series.Bar
71582  *
71583  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
71584  * visualization technique to display quantitative information for different categories that can
71585  * show some progression (or regression) in the data set. As with all other series, the Column Series
71586  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
71587  * information. A typical configuration object for the column series could be:
71588  *
71589  *     @example
71590  *     var store = Ext.create('Ext.data.JsonStore', {
71591  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
71592  *         data: [
71593  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
71594  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
71595  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
71596  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
71597  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
71598  *         ]
71599  *     });
71600  *
71601  *     Ext.create('Ext.chart.Chart', {
71602  *         renderTo: Ext.getBody(),
71603  *         width: 500,
71604  *         height: 300,
71605  *         animate: true,
71606  *         store: store,
71607  *         axes: [
71608  *             {
71609  *                 type: 'Numeric',
71610  *                 position: 'left',
71611  *                 fields: ['data1'],
71612  *                 label: {
71613  *                     renderer: Ext.util.Format.numberRenderer('0,0')
71614  *                 },
71615  *                 title: 'Sample Values',
71616  *                 grid: true,
71617  *                 minimum: 0
71618  *             },
71619  *             {
71620  *                 type: 'Category',
71621  *                 position: 'bottom',
71622  *                 fields: ['name'],
71623  *                 title: 'Sample Metrics'
71624  *             }
71625  *         ],
71626  *         series: [
71627  *             {
71628  *                 type: 'column',
71629  *                 axis: 'left',
71630  *                 highlight: true,
71631  *                 tips: {
71632  *                   trackMouse: true,
71633  *                   width: 140,
71634  *                   height: 28,
71635  *                   renderer: function(storeItem, item) {
71636  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
71637  *                   }
71638  *                 },
71639  *                 label: {
71640  *                   display: 'insideEnd',
71641  *                   'text-anchor': 'middle',
71642  *                     field: 'data1',
71643  *                     renderer: Ext.util.Format.numberRenderer('0'),
71644  *                     orientation: 'vertical',
71645  *                     color: '#333'
71646  *                 },
71647  *                 xField: 'name',
71648  *                 yField: 'data1'
71649  *             }
71650  *         ]
71651  *     });
71652  *
71653  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
71654  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
71655  * field to the data store `name` property and the `yField` as the data1 property of a store element.
71656  */
71657 Ext.define('Ext.chart.series.Column', {
71658
71659     /* Begin Definitions */
71660
71661     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
71662
71663     extend: 'Ext.chart.series.Bar',
71664
71665     /* End Definitions */
71666
71667     type: 'column',
71668     alias: 'series.column',
71669
71670     column: true,
71671
71672     /**
71673      * @cfg {Number} xPadding
71674      * Padding between the left/right axes and the bars
71675      */
71676     xPadding: 10,
71677
71678     /**
71679      * @cfg {Number} yPadding
71680      * Padding between the top/bottom axes and the bars
71681      */
71682     yPadding: 0
71683 });
71684 /**
71685  * @class Ext.chart.series.Gauge
71686  * @extends Ext.chart.series.Series
71687  * 
71688  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
71689  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
71690  * visualization and using the `setValue` method to adjust the value you want.
71691  *
71692  * A chart/series configuration for the Gauge visualization could look like this:
71693  * 
71694  *     {
71695  *         xtype: 'chart',
71696  *         store: store,
71697  *         axes: [{
71698  *             type: 'gauge',
71699  *             position: 'gauge',
71700  *             minimum: 0,
71701  *             maximum: 100,
71702  *             steps: 10,
71703  *             margin: -10
71704  *         }],
71705  *         series: [{
71706  *             type: 'gauge',
71707  *             field: 'data1',
71708  *             donut: false,
71709  *             colorSet: ['#F49D10', '#ddd']
71710  *         }]
71711  *     }
71712  * 
71713  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
71714  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
71715  * the visual display and the color set to be used with the visualization.
71716  * 
71717  * @xtype gauge
71718  */
71719 Ext.define('Ext.chart.series.Gauge', {
71720
71721     /* Begin Definitions */
71722
71723     extend: 'Ext.chart.series.Series',
71724
71725     /* End Definitions */
71726
71727     type: "gauge",
71728     alias: 'series.gauge',
71729
71730     rad: Math.PI / 180,
71731
71732     /**
71733      * @cfg {Number} highlightDuration
71734      * The duration for the pie slice highlight effect.
71735      */
71736     highlightDuration: 150,
71737
71738     /**
71739      * @cfg {String} angleField (required)
71740      * The store record field name to be used for the pie angles.
71741      * The values bound to this field name must be positive real numbers.
71742      */
71743     angleField: false,
71744
71745     /**
71746      * @cfg {Boolean} needle
71747      * Use the Gauge Series as an area series or add a needle to it. Default's false.
71748      */
71749     needle: false,
71750     
71751     /**
71752      * @cfg {Boolean/Number} donut
71753      * Use the entire disk or just a fraction of it for the gauge. Default's false.
71754      */
71755     donut: false,
71756
71757     /**
71758      * @cfg {Boolean} showInLegend
71759      * Whether to add the pie chart elements as legend items. Default's false.
71760      */
71761     showInLegend: false,
71762
71763     /**
71764      * @cfg {Object} style
71765      * An object containing styles for overriding series styles from Theming.
71766      */
71767     style: {},
71768     
71769     constructor: function(config) {
71770         this.callParent(arguments);
71771         var me = this,
71772             chart = me.chart,
71773             surface = chart.surface,
71774             store = chart.store,
71775             shadow = chart.shadow, i, l, cfg;
71776         Ext.apply(me, config, {
71777             shadowAttributes: [{
71778                 "stroke-width": 6,
71779                 "stroke-opacity": 1,
71780                 stroke: 'rgb(200, 200, 200)',
71781                 translate: {
71782                     x: 1.2,
71783                     y: 2
71784                 }
71785             },
71786             {
71787                 "stroke-width": 4,
71788                 "stroke-opacity": 1,
71789                 stroke: 'rgb(150, 150, 150)',
71790                 translate: {
71791                     x: 0.9,
71792                     y: 1.5
71793                 }
71794             },
71795             {
71796                 "stroke-width": 2,
71797                 "stroke-opacity": 1,
71798                 stroke: 'rgb(100, 100, 100)',
71799                 translate: {
71800                     x: 0.6,
71801                     y: 1
71802                 }
71803             }]
71804         });
71805         me.group = surface.getGroup(me.seriesId);
71806         if (shadow) {
71807             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
71808                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
71809             }
71810         }
71811         surface.customAttributes.segment = function(opt) {
71812             return me.getSegment(opt);
71813         };
71814     },
71815     
71816     //@private updates some onbefore render parameters.
71817     initialize: function() {
71818         var me = this,
71819             store = me.chart.getChartStore();
71820         //Add yFields to be used in Legend.js
71821         me.yField = [];
71822         if (me.label.field) {
71823             store.each(function(rec) {
71824                 me.yField.push(rec.get(me.label.field));
71825             });
71826         }
71827     },
71828
71829     // @private returns an object with properties for a Slice
71830     getSegment: function(opt) {
71831         var me = this,
71832             rad = me.rad,
71833             cos = Math.cos,
71834             sin = Math.sin,
71835             abs = Math.abs,
71836             x = me.centerX,
71837             y = me.centerY,
71838             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
71839             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
71840             delta = 1e-2,
71841             r = opt.endRho - opt.startRho,
71842             startAngle = opt.startAngle,
71843             endAngle = opt.endAngle,
71844             midAngle = (startAngle + endAngle) / 2 * rad,
71845             margin = opt.margin || 0,
71846             flag = abs(endAngle - startAngle) > 180,
71847             a1 = Math.min(startAngle, endAngle) * rad,
71848             a2 = Math.max(startAngle, endAngle) * rad,
71849             singleSlice = false;
71850
71851         x += margin * cos(midAngle);
71852         y += margin * sin(midAngle);
71853
71854         x1 = x + opt.startRho * cos(a1);
71855         y1 = y + opt.startRho * sin(a1);
71856
71857         x2 = x + opt.endRho * cos(a1);
71858         y2 = y + opt.endRho * sin(a1);
71859
71860         x3 = x + opt.startRho * cos(a2);
71861         y3 = y + opt.startRho * sin(a2);
71862
71863         x4 = x + opt.endRho * cos(a2);
71864         y4 = y + opt.endRho * sin(a2);
71865
71866         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
71867             singleSlice = true;
71868         }
71869         //Solves mysterious clipping bug with IE
71870         if (singleSlice) {
71871             return {
71872                 path: [
71873                 ["M", x1, y1],
71874                 ["L", x2, y2],
71875                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71876                 ["Z"]]
71877             };
71878         } else {
71879             return {
71880                 path: [
71881                 ["M", x1, y1],
71882                 ["L", x2, y2],
71883                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71884                 ["L", x3, y3],
71885                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
71886                 ["Z"]]
71887             };
71888         }
71889     },
71890
71891     // @private utility function to calculate the middle point of a pie slice.
71892     calcMiddle: function(item) {
71893         var me = this,
71894             rad = me.rad,
71895             slice = item.slice,
71896             x = me.centerX,
71897             y = me.centerY,
71898             startAngle = slice.startAngle,
71899             endAngle = slice.endAngle,
71900             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
71901             donut = +me.donut,
71902             a1 = Math.min(startAngle, endAngle) * rad,
71903             a2 = Math.max(startAngle, endAngle) * rad,
71904             midAngle = -(a1 + (a2 - a1) / 2),
71905             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
71906             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
71907
71908         item.middle = {
71909             x: xm,
71910             y: ym
71911         };
71912     },
71913
71914     /**
71915      * Draws the series for the current chart.
71916      */
71917     drawSeries: function() {
71918         var me = this,
71919             chart = me.chart,
71920             store = chart.getChartStore(),
71921             group = me.group,
71922             animate = me.chart.animate,
71923             axis = me.chart.axes.get(0),
71924             minimum = axis && axis.minimum || me.minimum || 0,
71925             maximum = axis && axis.maximum || me.maximum || 0,
71926             field = me.angleField || me.field || me.xField,
71927             surface = chart.surface,
71928             chartBBox = chart.chartBBox,
71929             rad = me.rad,
71930             donut = +me.donut,
71931             values = {},
71932             items = [],
71933             seriesStyle = me.seriesStyle,
71934             seriesLabelStyle = me.seriesLabelStyle,
71935             colorArrayStyle = me.colorArrayStyle,
71936             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
71937             gutterX = chart.maxGutter[0],
71938             gutterY = chart.maxGutter[1],
71939             cos = Math.cos,
71940             sin = Math.sin,
71941             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
71942             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
71943             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
71944         
71945         Ext.apply(seriesStyle, me.style || {});
71946
71947         me.setBBox();
71948         bbox = me.bbox;
71949
71950         //override theme colors
71951         if (me.colorSet) {
71952             colorArrayStyle = me.colorSet;
71953             colorArrayLength = colorArrayStyle.length;
71954         }
71955         
71956         //if not store or store is empty then there's nothing to draw
71957         if (!store || !store.getCount()) {
71958             return;
71959         }
71960         
71961         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
71962         centerY = me.centerY = chartBBox.y + chartBBox.height;
71963         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
71964         me.slices = slices = [];
71965         me.items = items = [];
71966         
71967         if (!me.value) {
71968             record = store.getAt(0);
71969             me.value = record.get(field);
71970         }
71971         
71972         value = me.value;
71973         if (me.needle) {
71974             sliceA = {
71975                 series: me,
71976                 value: value,
71977                 startAngle: -180,
71978                 endAngle: 0,
71979                 rho: me.radius
71980             };
71981             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71982             slices.push(sliceA);
71983         } else {
71984             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71985             sliceA = {
71986                 series: me,
71987                 value: value,
71988                 startAngle: -180,
71989                 endAngle: splitAngle,
71990                 rho: me.radius
71991             };
71992             sliceB = {
71993                 series: me,
71994                 value: me.maximum - value,
71995                 startAngle: splitAngle,
71996                 endAngle: 0,
71997                 rho: me.radius
71998             };
71999             slices.push(sliceA, sliceB);
72000         }
72001         
72002         //do pie slices after.
72003         for (i = 0, ln = slices.length; i < ln; i++) {
72004             slice = slices[i];
72005             sprite = group.getAt(i);
72006             //set pie slice properties
72007             rendererAttributes = Ext.apply({
72008                 segment: {
72009                     startAngle: slice.startAngle,
72010                     endAngle: slice.endAngle,
72011                     margin: 0,
72012                     rho: slice.rho,
72013                     startRho: slice.rho * +donut / 100,
72014                     endRho: slice.rho
72015                 } 
72016             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72017
72018             item = Ext.apply({},
72019             rendererAttributes.segment, {
72020                 slice: slice,
72021                 series: me,
72022                 storeItem: record,
72023                 index: i
72024             });
72025             items[i] = item;
72026             // Create a new sprite if needed (no height)
72027             if (!sprite) {
72028                 spriteOptions = Ext.apply({
72029                     type: "path",
72030                     group: group
72031                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72032                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
72033             }
72034             slice.sprite = slice.sprite || [];
72035             item.sprite = sprite;
72036             slice.sprite.push(sprite);
72037             if (animate) {
72038                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
72039                 sprite._to = rendererAttributes;
72040                 me.onAnimate(sprite, {
72041                     to: rendererAttributes
72042                 });
72043             } else {
72044                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
72045                     hidden: false
72046                 }), i, store);
72047                 sprite.setAttributes(rendererAttributes, true);
72048             }
72049         }
72050         
72051         if (me.needle) {
72052             splitAngle = splitAngle * Math.PI / 180;
72053             
72054             if (!me.needleSprite) {
72055                 me.needleSprite = me.chart.surface.add({
72056                     type: 'path',
72057                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72058                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72059                            'L', centerX + me.radius * cos(splitAngle),
72060                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
72061                     'stroke-width': 4,
72062                     'stroke': '#222'
72063                 });
72064             } else {
72065                 if (animate) {
72066                     me.onAnimate(me.needleSprite, {
72067                         to: {
72068                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72069                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72070                                'L', centerX + me.radius * cos(splitAngle),
72071                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72072                         }
72073                     });
72074                 } else {
72075                     me.needleSprite.setAttributes({
72076                         type: 'path',
72077                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72078                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72079                                'L', centerX + me.radius * cos(splitAngle),
72080                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72081                     });
72082                 }
72083             }
72084             me.needleSprite.setAttributes({
72085                 hidden: false    
72086             }, true);
72087         }
72088         
72089         delete me.value;
72090     },
72091     
72092     /**
72093      * Sets the Gauge chart to the current specified value.
72094     */
72095     setValue: function (value) {
72096         this.value = value;
72097         this.drawSeries();
72098     },
72099
72100     // @private callback for when creating a label sprite.
72101     onCreateLabel: function(storeItem, item, i, display) {},
72102
72103     // @private callback for when placing a label sprite.
72104     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
72105
72106     // @private callback for when placing a callout.
72107     onPlaceCallout: function() {},
72108
72109     // @private handles sprite animation for the series.
72110     onAnimate: function(sprite, attr) {
72111         sprite.show();
72112         return this.callParent(arguments);
72113     },
72114
72115     isItemInPoint: function(x, y, item, i) {
72116         return false;
72117     },
72118     
72119     // @private shows all elements in the series.
72120     showAll: function() {
72121         if (!isNaN(this._index)) {
72122             this.__excludes[this._index] = false;
72123             this.drawSeries();
72124         }
72125     },
72126     
72127     /**
72128      * Returns the color of the series (to be displayed as color for the series legend item).
72129      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
72130      */
72131     getLegendColor: function(index) {
72132         var me = this;
72133         return me.colorArrayStyle[index % me.colorArrayStyle.length];
72134     }
72135 });
72136
72137
72138 /**
72139  * @class Ext.chart.series.Line
72140  * @extends Ext.chart.series.Cartesian
72141  *
72142  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
72143  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
72144  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
72145  * documentation for more information. A typical configuration object for the line series could be:
72146  *
72147  *     @example
72148  *     var store = Ext.create('Ext.data.JsonStore', {
72149  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72150  *         data: [
72151  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72152  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72153  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72154  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72155  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
72156  *         ]
72157  *     });
72158  *
72159  *     Ext.create('Ext.chart.Chart', {
72160  *         renderTo: Ext.getBody(),
72161  *         width: 500,
72162  *         height: 300,
72163  *         animate: true,
72164  *         store: store,
72165  *         axes: [
72166  *             {
72167  *                 type: 'Numeric',
72168  *                 position: 'left',
72169  *                 fields: ['data1', 'data2'],
72170  *                 label: {
72171  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72172  *                 },
72173  *                 title: 'Sample Values',
72174  *                 grid: true,
72175  *                 minimum: 0
72176  *             },
72177  *             {
72178  *                 type: 'Category',
72179  *                 position: 'bottom',
72180  *                 fields: ['name'],
72181  *                 title: 'Sample Metrics'
72182  *             }
72183  *         ],
72184  *         series: [
72185  *             {
72186  *                 type: 'line',
72187  *                 highlight: {
72188  *                     size: 7,
72189  *                     radius: 7
72190  *                 },
72191  *                 axis: 'left',
72192  *                 xField: 'name',
72193  *                 yField: 'data1',
72194  *                 markerConfig: {
72195  *                     type: 'cross',
72196  *                     size: 4,
72197  *                     radius: 4,
72198  *                     'stroke-width': 0
72199  *                 }
72200  *             },
72201  *             {
72202  *                 type: 'line',
72203  *                 highlight: {
72204  *                     size: 7,
72205  *                     radius: 7
72206  *                 },
72207  *                 axis: 'left',
72208  *                 fill: true,
72209  *                 xField: 'name',
72210  *                 yField: 'data2',
72211  *                 markerConfig: {
72212  *                     type: 'circle',
72213  *                     size: 4,
72214  *                     radius: 4,
72215  *                     'stroke-width': 0
72216  *                 }
72217  *             }
72218  *         ]
72219  *     });
72220  *
72221  * In this configuration we're adding two series (or lines), one bound to the `data1`
72222  * property of the store and the other to `data3`. The type for both configurations is
72223  * `line`. The `xField` for both series is the same, the name propert of the store.
72224  * Both line series share the same axis, the left axis. You can set particular marker
72225  * configuration by adding properties onto the markerConfig object. Both series have
72226  * an object as highlight so that markers animate smoothly to the properties in highlight
72227  * when hovered. The second series has `fill=true` which means that the line will also
72228  * have an area below it of the same color.
72229  *
72230  * **Note:** In the series definition remember to explicitly set the axis to bind the
72231  * values of the line series to. This can be done by using the `axis` configuration property.
72232  */
72233 Ext.define('Ext.chart.series.Line', {
72234
72235     /* Begin Definitions */
72236
72237     extend: 'Ext.chart.series.Cartesian',
72238
72239     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
72240
72241     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
72242
72243     /* End Definitions */
72244
72245     type: 'line',
72246
72247     alias: 'series.line',
72248
72249     /**
72250      * @cfg {String} axis
72251      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
72252      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
72253      * relative scale will be used.
72254      */
72255
72256     /**
72257      * @cfg {Number} selectionTolerance
72258      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
72259      */
72260     selectionTolerance: 20,
72261
72262     /**
72263      * @cfg {Boolean} showMarkers
72264      * Whether markers should be displayed at the data points along the line. If true,
72265      * then the {@link #markerConfig} config item will determine the markers' styling.
72266      */
72267     showMarkers: true,
72268
72269     /**
72270      * @cfg {Object} markerConfig
72271      * The display style for the markers. Only used if {@link #showMarkers} is true.
72272      * The markerConfig is a configuration object containing the same set of properties defined in
72273      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
72274      * pass the object:
72275      *
72276      <pre><code>
72277         markerConfig: {
72278             type: 'circle',
72279             radius: 4,
72280             'fill': '#f00'
72281         }
72282      </code></pre>
72283
72284      */
72285     markerConfig: {},
72286
72287     /**
72288      * @cfg {Object} style
72289      * An object containing style properties for the visualization lines and fill.
72290      * These styles will override the theme styles.  The following are valid style properties:
72291      *
72292      * - `stroke` - an rgb or hex color string for the background color of the line
72293      * - `stroke-width` - the width of the stroke (integer)
72294      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
72295      * - `opacity` - the opacity of the line and the fill color (decimal)
72296      *
72297      * Example usage:
72298      *
72299      *     style: {
72300      *         stroke: '#00ff00',
72301      *         'stroke-width': 10,
72302      *         fill: '#80A080',
72303      *         opacity: 0.2
72304      *     }
72305      */
72306     style: {},
72307
72308     /**
72309      * @cfg {Boolean/Number} smooth
72310      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
72311      * straight line segments will be drawn.
72312      *
72313      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
72314      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
72315      *
72316      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
72317      */
72318     smooth: false,
72319
72320     /**
72321      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
72322      */
72323     defaultSmoothness: 3,
72324
72325     /**
72326      * @cfg {Boolean} fill
72327      * If true, the area below the line will be filled in using the {@link #style eefill} and
72328      * {@link #style opacity} config properties. Defaults to false.
72329      */
72330     fill: false,
72331
72332     constructor: function(config) {
72333         this.callParent(arguments);
72334         var me = this,
72335             surface = me.chart.surface,
72336             shadow = me.chart.shadow,
72337             i, l;
72338         Ext.apply(me, config, {
72339             highlightCfg: {
72340                 'stroke-width': 3
72341             },
72342             shadowAttributes: [{
72343                 "stroke-width": 6,
72344                 "stroke-opacity": 0.05,
72345                 stroke: 'rgb(0, 0, 0)',
72346                 translate: {
72347                     x: 1,
72348                     y: 1
72349                 }
72350             }, {
72351                 "stroke-width": 4,
72352                 "stroke-opacity": 0.1,
72353                 stroke: 'rgb(0, 0, 0)',
72354                 translate: {
72355                     x: 1,
72356                     y: 1
72357                 }
72358             }, {
72359                 "stroke-width": 2,
72360                 "stroke-opacity": 0.15,
72361                 stroke: 'rgb(0, 0, 0)',
72362                 translate: {
72363                     x: 1,
72364                     y: 1
72365                 }
72366             }]
72367         });
72368         me.group = surface.getGroup(me.seriesId);
72369         if (me.showMarkers) {
72370             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
72371         }
72372         if (shadow) {
72373             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
72374                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
72375             }
72376         }
72377     },
72378
72379     // @private makes an average of points when there are more data points than pixels to be rendered.
72380     shrink: function(xValues, yValues, size) {
72381         // Start at the 2nd point...
72382         var len = xValues.length,
72383             ratio = Math.floor(len / size),
72384             i = 1,
72385             xSum = 0,
72386             ySum = 0,
72387             xRes = [xValues[0]],
72388             yRes = [yValues[0]];
72389
72390         for (; i < len; ++i) {
72391             xSum += xValues[i] || 0;
72392             ySum += yValues[i] || 0;
72393             if (i % ratio == 0) {
72394                 xRes.push(xSum/ratio);
72395                 yRes.push(ySum/ratio);
72396                 xSum = 0;
72397                 ySum = 0;
72398             }
72399         }
72400         return {
72401             x: xRes,
72402             y: yRes
72403         };
72404     },
72405
72406     /**
72407      * Draws the series for the current chart.
72408      */
72409     drawSeries: function() {
72410         var me = this,
72411             chart = me.chart,
72412             chartAxes = chart.axes,
72413             store = chart.getChartStore(),
72414             storeCount = store.getCount(),
72415             surface = me.chart.surface,
72416             bbox = {},
72417             group = me.group,
72418             showMarkers = me.showMarkers,
72419             markerGroup = me.markerGroup,
72420             enableShadows = chart.shadow,
72421             shadowGroups = me.shadowGroups,
72422             shadowAttributes = me.shadowAttributes,
72423             smooth = me.smooth,
72424             lnsh = shadowGroups.length,
72425             dummyPath = ["M"],
72426             path = ["M"],
72427             renderPath = ["M"],
72428             smoothPath = ["M"],
72429             markerIndex = chart.markerIndex,
72430             axes = [].concat(me.axis),
72431             shadowBarAttr,
72432             xValues = [],
72433             xValueMap = {},
72434             yValues = [],
72435             yValueMap = {},
72436             onbreak = false,
72437             storeIndices = [],
72438             markerStyle = me.markerStyle,
72439             seriesStyle = me.style,
72440             colorArrayStyle = me.colorArrayStyle,
72441             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
72442             isNumber = Ext.isNumber,
72443             seriesIdx = me.seriesIdx, 
72444             boundAxes = me.getAxesForXAndYFields(),
72445             boundXAxis = boundAxes.xAxis,
72446             boundYAxis = boundAxes.yAxis,
72447             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
72448             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
72449             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
72450             endLineStyle, type, count, items;
72451
72452         if (me.fireEvent('beforedraw', me) === false) {
72453             return;
72454         }
72455
72456         //if store is empty or the series is excluded in the legend then there's nothing to draw.
72457         if (!storeCount || me.seriesIsHidden) {
72458             items = this.items;
72459             if (items) {
72460                 for (i = 0, ln = items.length; i < ln; ++i) {
72461                     if (items[i].sprite) {
72462                         items[i].sprite.hide(true);
72463                     }
72464                 }
72465             }
72466             return;
72467         }
72468
72469         //prepare style objects for line and markers
72470         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
72471         type = endMarkerStyle.type;
72472         delete endMarkerStyle.type;
72473         endLineStyle = seriesStyle;
72474         //if no stroke with is specified force it to 0.5 because this is
72475         //about making *lines*
72476         if (!endLineStyle['stroke-width']) {
72477             endLineStyle['stroke-width'] = 0.5;
72478         }
72479         //If we're using a time axis and we need to translate the points,
72480         //then reuse the first markers as the last markers.
72481         if (markerIndex && markerGroup && markerGroup.getCount()) {
72482             for (i = 0; i < markerIndex; i++) {
72483                 marker = markerGroup.getAt(i);
72484                 markerGroup.remove(marker);
72485                 markerGroup.add(marker);
72486                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
72487                 marker.setAttributes({
72488                     x: 0,
72489                     y: 0,
72490                     translate: {
72491                         x: markerAux.attr.translation.x,
72492                         y: markerAux.attr.translation.y
72493                     }
72494                 }, true);
72495             }
72496         }
72497
72498         me.unHighlightItem();
72499         me.cleanHighlights();
72500
72501         me.setBBox();
72502         bbox = me.bbox;
72503         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
72504         for (i = 0, ln = axes.length; i < ln; i++) {
72505             axis = chartAxes.get(axes[i]);
72506             if (axis) {
72507                 ends = axis.calcEnds();
72508                 if (axis.position == 'top' || axis.position == 'bottom') {
72509                     minX = ends.from;
72510                     maxX = ends.to;
72511                 }
72512                 else {
72513                     minY = ends.from;
72514                     maxY = ends.to;
72515                 }
72516             }
72517         }
72518         // If a field was specified without a corresponding axis, create one to get bounds
72519         //only do this for the axis where real values are bound (that's why we check for
72520         //me.axis)
72521         if (me.xField && !isNumber(minX) &&
72522             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
72523             !chartAxes.get(boundXAxis)) {
72524             axis = Ext.create('Ext.chart.axis.Axis', {
72525                 chart: chart,
72526                 fields: [].concat(me.xField)
72527             }).calcEnds();
72528             minX = axis.from;
72529             maxX = axis.to;
72530         }
72531         if (me.yField && !isNumber(minY) &&
72532             (boundYAxis == 'right' || boundYAxis == 'left') &&
72533             !chartAxes.get(boundYAxis)) {
72534             axis = Ext.create('Ext.chart.axis.Axis', {
72535                 chart: chart,
72536                 fields: [].concat(me.yField)
72537             }).calcEnds();
72538             minY = axis.from;
72539             maxY = axis.to;
72540         }
72541         if (isNaN(minX)) {
72542             minX = 0;
72543             xScale = bbox.width / ((storeCount - 1) || 1);
72544         }
72545         else {
72546             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
72547         }
72548
72549         if (isNaN(minY)) {
72550             minY = 0;
72551             yScale = bbox.height / ((storeCount - 1) || 1);
72552         }
72553         else {
72554             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
72555         }
72556
72557         // Extract all x and y values from the store
72558         me.eachRecord(function(record, i) {
72559             xValue = record.get(me.xField);
72560
72561             // Ensure a value
72562             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
72563                 //set as uniform distribution if the axis is a category axis.
72564                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
72565                     if (xValue in xValueMap) {
72566                         xValue = xValueMap[xValue];
72567                     } else {
72568                         xValue = xValueMap[xValue] = i;
72569                     }
72570             }
72571
72572             // Filter out values that don't fit within the pan/zoom buffer area
72573             yValue = record.get(me.yField);
72574             //skip undefined values
72575             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
72576                 return;
72577             }
72578             // Ensure a value
72579             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
72580                 //set as uniform distribution if the axis is a category axis.
72581                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
72582                 yValue = i;
72583             }
72584             storeIndices.push(i);
72585             xValues.push(xValue);
72586             yValues.push(yValue);
72587         });
72588
72589         ln = xValues.length;
72590         if (ln > bbox.width) {
72591             coords = me.shrink(xValues, yValues, bbox.width);
72592             xValues = coords.x;
72593             yValues = coords.y;
72594         }
72595
72596         me.items = [];
72597
72598         count = 0;
72599         ln = xValues.length;
72600         for (i = 0; i < ln; i++) {
72601             xValue = xValues[i];
72602             yValue = yValues[i];
72603             if (yValue === false) {
72604                 if (path.length == 1) {
72605                     path = [];
72606                 }
72607                 onbreak = true;
72608                 me.items.push(false);
72609                 continue;
72610             } else {
72611                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
72612                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
72613                 if (onbreak) {
72614                     onbreak = false;
72615                     path.push('M');
72616                 }
72617                 path = path.concat([x, y]);
72618             }
72619             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
72620                 firstY = y;
72621                 firstX = x;
72622             }
72623             // If this is the first line, create a dummypath to animate in from.
72624             if (!me.line || chart.resizing) {
72625                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
72626             }
72627
72628             // When resizing, reset before animating
72629             if (chart.animate && chart.resizing && me.line) {
72630                 me.line.setAttributes({
72631                     path: dummyPath
72632                 }, true);
72633                 if (me.fillPath) {
72634                     me.fillPath.setAttributes({
72635                         path: dummyPath,
72636                         opacity: 0.2
72637                     }, true);
72638                 }
72639                 if (me.line.shadows) {
72640                     shadows = me.line.shadows;
72641                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
72642                         shadow = shadows[j];
72643                         shadow.setAttributes({
72644                             path: dummyPath
72645                         }, true);
72646                     }
72647                 }
72648             }
72649             if (showMarkers) {
72650                 marker = markerGroup.getAt(count++);
72651                 if (!marker) {
72652                     marker = Ext.chart.Shape[type](surface, Ext.apply({
72653                         group: [group, markerGroup],
72654                         x: 0, y: 0,
72655                         translate: {
72656                             x: +(prevX || x),
72657                             y: prevY || (bbox.y + bbox.height / 2)
72658                         },
72659                         value: '"' + xValue + ', ' + yValue + '"',
72660                         zIndex: 4000
72661                     }, endMarkerStyle));
72662                     marker._to = {
72663                         translate: {
72664                             x: +x,
72665                             y: +y
72666                         }
72667                     };
72668                 } else {
72669                     marker.setAttributes({
72670                         value: '"' + xValue + ', ' + yValue + '"',
72671                         x: 0, y: 0,
72672                         hidden: false
72673                     }, true);
72674                     marker._to = {
72675                         translate: {
72676                             x: +x, 
72677                             y: +y
72678                         }
72679                     };
72680                 }
72681             }
72682             me.items.push({
72683                 series: me,
72684                 value: [xValue, yValue],
72685                 point: [x, y],
72686                 sprite: marker,
72687                 storeItem: store.getAt(storeIndices[i])
72688             });
72689             prevX = x;
72690             prevY = y;
72691         }
72692
72693         if (path.length <= 1) {
72694             //nothing to be rendered
72695             return;
72696         }
72697
72698         if (me.smooth) {
72699             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
72700         }
72701
72702         renderPath = smooth ? smoothPath : path;
72703
72704         //Correct path if we're animating timeAxis intervals
72705         if (chart.markerIndex && me.previousPath) {
72706             fromPath = me.previousPath;
72707             if (!smooth) {
72708                 Ext.Array.erase(fromPath, 1, 2);
72709             }
72710         } else {
72711             fromPath = path;
72712         }
72713
72714         // Only create a line if one doesn't exist.
72715         if (!me.line) {
72716             me.line = surface.add(Ext.apply({
72717                 type: 'path',
72718                 group: group,
72719                 path: dummyPath,
72720                 stroke: endLineStyle.stroke || endLineStyle.fill
72721             }, endLineStyle || {}));
72722
72723             if (enableShadows) {
72724                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
72725             }
72726
72727             //unset fill here (there's always a default fill withing the themes).
72728             me.line.setAttributes({
72729                 fill: 'none',
72730                 zIndex: 3000
72731             });
72732             if (!endLineStyle.stroke && colorArrayLength) {
72733                 me.line.setAttributes({
72734                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
72735                 }, true);
72736             }
72737             if (enableShadows) {
72738                 //create shadows
72739                 shadows = me.line.shadows = [];
72740                 for (shindex = 0; shindex < lnsh; shindex++) {
72741                     shadowBarAttr = shadowAttributes[shindex];
72742                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
72743                     shadow = surface.add(Ext.apply({}, {
72744                         type: 'path',
72745                         group: shadowGroups[shindex]
72746                     }, shadowBarAttr));
72747                     shadows.push(shadow);
72748                 }
72749             }
72750         }
72751         if (me.fill) {
72752             fillPath = renderPath.concat([
72753                 ["L", x, bbox.y + bbox.height],
72754                 ["L", firstX, bbox.y + bbox.height],
72755                 ["L", firstX, firstY]
72756             ]);
72757             if (!me.fillPath) {
72758                 me.fillPath = surface.add({
72759                     group: group,
72760                     type: 'path',
72761                     opacity: endLineStyle.opacity || 0.3,
72762                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72763                     path: dummyPath
72764                 });
72765             }
72766         }
72767         markerCount = showMarkers && markerGroup.getCount();
72768         if (chart.animate) {
72769             fill = me.fill;
72770             line = me.line;
72771             //Add renderer to line. There is not unique record associated with this.
72772             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
72773             Ext.apply(rendererAttributes, endLineStyle || {}, {
72774                 stroke: endLineStyle.stroke || endLineStyle.fill
72775             });
72776             //fill should not be used here but when drawing the special fill path object
72777             delete rendererAttributes.fill;
72778             line.show(true);
72779             if (chart.markerIndex && me.previousPath) {
72780                 me.animation = animation = me.onAnimate(line, {
72781                     to: rendererAttributes,
72782                     from: {
72783                         path: fromPath
72784                     }
72785                 });
72786             } else {
72787                 me.animation = animation = me.onAnimate(line, {
72788                     to: rendererAttributes
72789                 });
72790             }
72791             //animate shadows
72792             if (enableShadows) {
72793                 shadows = line.shadows;
72794                 for(j = 0; j < lnsh; j++) {
72795                     shadows[j].show(true);
72796                     if (chart.markerIndex && me.previousPath) {
72797                         me.onAnimate(shadows[j], {
72798                             to: { path: renderPath },
72799                             from: { path: fromPath }
72800                         });
72801                     } else {
72802                         me.onAnimate(shadows[j], {
72803                             to: { path: renderPath }
72804                         });
72805                     }
72806                 }
72807             }
72808             //animate fill path
72809             if (fill) {
72810                 me.fillPath.show(true);
72811                 me.onAnimate(me.fillPath, {
72812                     to: Ext.apply({}, {
72813                         path: fillPath,
72814                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72815                         'stroke-width': 0
72816                     }, endLineStyle || {})
72817                 });
72818             }
72819             //animate markers
72820             if (showMarkers) {
72821                 count = 0;
72822                 for(i = 0; i < ln; i++) {
72823                     if (me.items[i]) {
72824                         item = markerGroup.getAt(count++);
72825                         if (item) {
72826                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72827                             me.onAnimate(item, {
72828                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
72829                             });
72830                             item.show(true);
72831                         }
72832                     }
72833                 }
72834                 for(; count < markerCount; count++) {
72835                     item = markerGroup.getAt(count);
72836                     item.hide(true);
72837                 }
72838 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
72839 //                    item = markerGroup.getAt(i);
72840 //                    item.hide(true);
72841 //                }
72842             }
72843         } else {
72844             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
72845             Ext.apply(rendererAttributes, endLineStyle || {}, {
72846                 stroke: endLineStyle.stroke || endLineStyle.fill
72847             });
72848             //fill should not be used here but when drawing the special fill path object
72849             delete rendererAttributes.fill;
72850             me.line.setAttributes(rendererAttributes, true);
72851             //set path for shadows
72852             if (enableShadows) {
72853                 shadows = me.line.shadows;
72854                 for(j = 0; j < lnsh; j++) {
72855                     shadows[j].setAttributes({
72856                         path: renderPath,
72857                         hidden: false
72858                     }, true);
72859                 }
72860             }
72861             if (me.fill) {
72862                 me.fillPath.setAttributes({
72863                     path: fillPath,
72864                     hidden: false
72865                 }, true);
72866             }
72867             if (showMarkers) {
72868                 count = 0;
72869                 for(i = 0; i < ln; i++) {
72870                     if (me.items[i]) {
72871                         item = markerGroup.getAt(count++);
72872                         if (item) {
72873                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72874                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
72875                             item.show(true);
72876                         }
72877                     }
72878                 }
72879                 for(; count < markerCount; count++) {
72880                     item = markerGroup.getAt(count);
72881                     item.hide(true);
72882                 }
72883             }
72884         }
72885
72886         if (chart.markerIndex) {
72887             if (me.smooth) {
72888                 Ext.Array.erase(path, 1, 2);
72889             } else {
72890                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
72891             }
72892             me.previousPath = path;
72893         }
72894         me.renderLabels();
72895         me.renderCallouts();
72896
72897         me.fireEvent('draw', me);
72898     },
72899
72900     // @private called when a label is to be created.
72901     onCreateLabel: function(storeItem, item, i, display) {
72902         var me = this,
72903             group = me.labelsGroup,
72904             config = me.label,
72905             bbox = me.bbox,
72906             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
72907
72908         return me.chart.surface.add(Ext.apply({
72909             'type': 'text',
72910             'text-anchor': 'middle',
72911             'group': group,
72912             'x': item.point[0],
72913             'y': bbox.y + bbox.height / 2
72914         }, endLabelStyle || {}));
72915     },
72916
72917     // @private called when a label is to be created.
72918     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
72919         var me = this,
72920             chart = me.chart,
72921             resizing = chart.resizing,
72922             config = me.label,
72923             format = config.renderer,
72924             field = config.field,
72925             bbox = me.bbox,
72926             x = item.point[0],
72927             y = item.point[1],
72928             radius = item.sprite.attr.radius,
72929             bb, width, height;
72930
72931         label.setAttributes({
72932             text: format(storeItem.get(field)),
72933             hidden: true
72934         }, true);
72935
72936         if (display == 'rotate') {
72937             label.setAttributes({
72938                 'text-anchor': 'start',
72939                 'rotation': {
72940                     x: x,
72941                     y: y,
72942                     degrees: -45
72943                 }
72944             }, true);
72945             //correct label position to fit into the box
72946             bb = label.getBBox();
72947             width = bb.width;
72948             height = bb.height;
72949             x = x < bbox.x? bbox.x : x;
72950             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
72951             y = (y - height < bbox.y)? bbox.y + height : y;
72952
72953         } else if (display == 'under' || display == 'over') {
72954             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
72955             bb = item.sprite.getBBox();
72956             bb.width = bb.width || (radius * 2);
72957             bb.height = bb.height || (radius * 2);
72958             y = y + (display == 'over'? -bb.height : bb.height);
72959             //correct label position to fit into the box
72960             bb = label.getBBox();
72961             width = bb.width/2;
72962             height = bb.height/2;
72963             x = x - width < bbox.x? bbox.x + width : x;
72964             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
72965             y = y - height < bbox.y? bbox.y + height : y;
72966             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
72967         }
72968
72969         if (me.chart.animate && !me.chart.resizing) {
72970             label.show(true);
72971             me.onAnimate(label, {
72972                 to: {
72973                     x: x,
72974                     y: y
72975                 }
72976             });
72977         } else {
72978             label.setAttributes({
72979                 x: x,
72980                 y: y
72981             }, true);
72982             if (resizing && me.animation) {
72983                 me.animation.on('afteranimate', function() {
72984                     label.show(true);
72985                 });
72986             } else {
72987                 label.show(true);
72988             }
72989         }
72990     },
72991
72992     //@private Overriding highlights.js highlightItem method.
72993     highlightItem: function() {
72994         var me = this;
72995         me.callParent(arguments);
72996         if (me.line && !me.highlighted) {
72997             if (!('__strokeWidth' in me.line)) {
72998                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
72999             }
73000             if (me.line.__anim) {
73001                 me.line.__anim.paused = true;
73002             }
73003             me.line.__anim = Ext.create('Ext.fx.Anim', {
73004                 target: me.line,
73005                 to: {
73006                     'stroke-width': me.line.__strokeWidth + 3
73007                 }
73008             });
73009             me.highlighted = true;
73010         }
73011     },
73012
73013     //@private Overriding highlights.js unHighlightItem method.
73014     unHighlightItem: function() {
73015         var me = this;
73016         me.callParent(arguments);
73017         if (me.line && me.highlighted) {
73018             me.line.__anim = Ext.create('Ext.fx.Anim', {
73019                 target: me.line,
73020                 to: {
73021                     'stroke-width': me.line.__strokeWidth
73022                 }
73023             });
73024             me.highlighted = false;
73025         }
73026     },
73027
73028     //@private called when a callout needs to be placed.
73029     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
73030         if (!display) {
73031             return;
73032         }
73033
73034         var me = this,
73035             chart = me.chart,
73036             surface = chart.surface,
73037             resizing = chart.resizing,
73038             config = me.callouts,
73039             items = me.items,
73040             prev = i == 0? false : items[i -1].point,
73041             next = (i == items.length -1)? false : items[i +1].point,
73042             cur = [+item.point[0], +item.point[1]],
73043             dir, norm, normal, a, aprev, anext,
73044             offsetFromViz = config.offsetFromViz || 30,
73045             offsetToSide = config.offsetToSide || 10,
73046             offsetBox = config.offsetBox || 3,
73047             boxx, boxy, boxw, boxh,
73048             p, clipRect = me.clipRect,
73049             bbox = {
73050                 width: config.styles.width || 10,
73051                 height: config.styles.height || 10
73052             },
73053             x, y;
73054
73055         //get the right two points
73056         if (!prev) {
73057             prev = cur;
73058         }
73059         if (!next) {
73060             next = cur;
73061         }
73062         a = (next[1] - prev[1]) / (next[0] - prev[0]);
73063         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
73064         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
73065
73066         norm = Math.sqrt(1 + a * a);
73067         dir = [1 / norm, a / norm];
73068         normal = [-dir[1], dir[0]];
73069
73070         //keep the label always on the outer part of the "elbow"
73071         if (aprev > 0 && anext < 0 && normal[1] < 0
73072             || aprev < 0 && anext > 0 && normal[1] > 0) {
73073             normal[0] *= -1;
73074             normal[1] *= -1;
73075         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
73076                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
73077             normal[0] *= -1;
73078             normal[1] *= -1;
73079         }
73080         //position
73081         x = cur[0] + normal[0] * offsetFromViz;
73082         y = cur[1] + normal[1] * offsetFromViz;
73083
73084         //box position and dimensions
73085         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73086         boxy = y - bbox.height /2 - offsetBox;
73087         boxw = bbox.width + 2 * offsetBox;
73088         boxh = bbox.height + 2 * offsetBox;
73089
73090         //now check if we're out of bounds and invert the normal vector correspondingly
73091         //this may add new overlaps between labels (but labels won't be out of bounds).
73092         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
73093             normal[0] *= -1;
73094         }
73095         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
73096             normal[1] *= -1;
73097         }
73098
73099         //update positions
73100         x = cur[0] + normal[0] * offsetFromViz;
73101         y = cur[1] + normal[1] * offsetFromViz;
73102
73103         //update box position and dimensions
73104         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73105         boxy = y - bbox.height /2 - offsetBox;
73106         boxw = bbox.width + 2 * offsetBox;
73107         boxh = bbox.height + 2 * offsetBox;
73108
73109         if (chart.animate) {
73110             //set the line from the middle of the pie to the box.
73111             me.onAnimate(callout.lines, {
73112                 to: {
73113                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73114                 }
73115             });
73116             //set component position
73117             if (callout.panel) {
73118                 callout.panel.setPosition(boxx, boxy, true);
73119             }
73120         }
73121         else {
73122             //set the line from the middle of the pie to the box.
73123             callout.lines.setAttributes({
73124                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73125             }, true);
73126             //set component position
73127             if (callout.panel) {
73128                 callout.panel.setPosition(boxx, boxy);
73129             }
73130         }
73131         for (p in callout) {
73132             callout[p].show(true);
73133         }
73134     },
73135
73136     isItemInPoint: function(x, y, item, i) {
73137         var me = this,
73138             items = me.items,
73139             tolerance = me.selectionTolerance,
73140             result = null,
73141             prevItem,
73142             nextItem,
73143             prevPoint,
73144             nextPoint,
73145             ln,
73146             x1,
73147             y1,
73148             x2,
73149             y2,
73150             xIntersect,
73151             yIntersect,
73152             dist1, dist2, dist, midx, midy,
73153             sqrt = Math.sqrt, abs = Math.abs;
73154
73155         nextItem = items[i];
73156         prevItem = i && items[i - 1];
73157
73158         if (i >= ln) {
73159             prevItem = items[ln - 1];
73160         }
73161         prevPoint = prevItem && prevItem.point;
73162         nextPoint = nextItem && nextItem.point;
73163         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
73164         y1 = prevItem ? prevPoint[1] : nextPoint[1];
73165         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
73166         y2 = nextItem ? nextPoint[1] : prevPoint[1];
73167         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
73168         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
73169         dist = Math.min(dist1, dist2);
73170
73171         if (dist <= tolerance) {
73172             return dist == dist1? prevItem : nextItem;
73173         }
73174         return false;
73175     },
73176
73177     // @private toggle visibility of all series elements (markers, sprites).
73178     toggleAll: function(show) {
73179         var me = this,
73180             i, ln, shadow, shadows;
73181         if (!show) {
73182             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
73183         }
73184         else {
73185             Ext.chart.series.Cartesian.prototype.showAll.call(me);
73186         }
73187         if (me.line) {
73188             me.line.setAttributes({
73189                 hidden: !show
73190             }, true);
73191             //hide shadows too
73192             if (me.line.shadows) {
73193                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
73194                     shadow = shadows[i];
73195                     shadow.setAttributes({
73196                         hidden: !show
73197                     }, true);
73198                 }
73199             }
73200         }
73201         if (me.fillPath) {
73202             me.fillPath.setAttributes({
73203                 hidden: !show
73204             }, true);
73205         }
73206     },
73207
73208     // @private hide all series elements (markers, sprites).
73209     hideAll: function() {
73210         this.toggleAll(false);
73211     },
73212
73213     // @private hide all series elements (markers, sprites).
73214     showAll: function() {
73215         this.toggleAll(true);
73216     }
73217 });
73218
73219 /**
73220  * @class Ext.chart.series.Pie
73221  * @extends Ext.chart.series.Series
73222  *
73223  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
73224  * categories that also have a meaning as a whole.
73225  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
73226  * documentation for more information. A typical configuration object for the pie series could be:
73227  *
73228  *     @example
73229  *     var store = Ext.create('Ext.data.JsonStore', {
73230  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
73231  *         data: [
73232  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
73233  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
73234  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
73235  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
73236  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
73237  *         ]
73238  *     });
73239  *
73240  *     Ext.create('Ext.chart.Chart', {
73241  *         renderTo: Ext.getBody(),
73242  *         width: 500,
73243  *         height: 350,
73244  *         animate: true,
73245  *         store: store,
73246  *         theme: 'Base:gradients',
73247  *         series: [{
73248  *             type: 'pie',
73249  *             field: 'data1',
73250  *             showInLegend: true,
73251  *             tips: {
73252  *                 trackMouse: true,
73253  *                 width: 140,
73254  *                 height: 28,
73255  *                 renderer: function(storeItem, item) {
73256  *                     // calculate and display percentage on hover
73257  *                     var total = 0;
73258  *                     store.each(function(rec) {
73259  *                         total += rec.get('data1');
73260  *                     });
73261  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
73262  *                 }
73263  *             },
73264  *             highlight: {
73265  *                 segment: {
73266  *                     margin: 20
73267  *                 }
73268  *             },
73269  *             label: {
73270  *                 field: 'name',
73271  *                 display: 'rotate',
73272  *                 contrast: true,
73273  *                 font: '18px Arial'
73274  *             }
73275  *         }]
73276  *     });
73277  *
73278  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
73279  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
73280  *
73281  * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object
73282  * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated.
73283  *
73284  * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family
73285  * and size through the `font` parameter.
73286  *
73287  * @xtype pie
73288  */
73289 Ext.define('Ext.chart.series.Pie', {
73290
73291     /* Begin Definitions */
73292
73293     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
73294
73295     extend: 'Ext.chart.series.Series',
73296
73297     /* End Definitions */
73298
73299     type: "pie",
73300
73301     alias: 'series.pie',
73302
73303     rad: Math.PI / 180,
73304
73305     /**
73306      * @cfg {Number} highlightDuration
73307      * The duration for the pie slice highlight effect.
73308      */
73309     highlightDuration: 150,
73310
73311     /**
73312      * @cfg {String} angleField (required)
73313      * The store record field name to be used for the pie angles.
73314      * The values bound to this field name must be positive real numbers.
73315      */
73316     angleField: false,
73317
73318     /**
73319      * @cfg {String} lengthField
73320      * The store record field name to be used for the pie slice lengths.
73321      * The values bound to this field name must be positive real numbers.
73322      */
73323     lengthField: false,
73324
73325     /**
73326      * @cfg {Boolean/Number} donut
73327      * Whether to set the pie chart as donut chart.
73328      * Default's false. Can be set to a particular percentage to set the radius
73329      * of the donut chart.
73330      */
73331     donut: false,
73332
73333     /**
73334      * @cfg {Boolean} showInLegend
73335      * Whether to add the pie chart elements as legend items. Default's false.
73336      */
73337     showInLegend: false,
73338
73339     /**
73340      * @cfg {Array} colorSet
73341      * An array of color values which will be used, in order, as the pie slice fill colors.
73342      */
73343
73344     /**
73345      * @cfg {Object} style
73346      * An object containing styles for overriding series styles from Theming.
73347      */
73348     style: {},
73349
73350     constructor: function(config) {
73351         this.callParent(arguments);
73352         var me = this,
73353             chart = me.chart,
73354             surface = chart.surface,
73355             store = chart.store,
73356             shadow = chart.shadow, i, l, cfg;
73357         Ext.applyIf(me, {
73358             highlightCfg: {
73359                 segment: {
73360                     margin: 20
73361                 }
73362             }
73363         });
73364         Ext.apply(me, config, {
73365             shadowAttributes: [{
73366                 "stroke-width": 6,
73367                 "stroke-opacity": 1,
73368                 stroke: 'rgb(200, 200, 200)',
73369                 translate: {
73370                     x: 1.2,
73371                     y: 2
73372                 }
73373             },
73374             {
73375                 "stroke-width": 4,
73376                 "stroke-opacity": 1,
73377                 stroke: 'rgb(150, 150, 150)',
73378                 translate: {
73379                     x: 0.9,
73380                     y: 1.5
73381                 }
73382             },
73383             {
73384                 "stroke-width": 2,
73385                 "stroke-opacity": 1,
73386                 stroke: 'rgb(100, 100, 100)',
73387                 translate: {
73388                     x: 0.6,
73389                     y: 1
73390                 }
73391             }]
73392         });
73393         me.group = surface.getGroup(me.seriesId);
73394         if (shadow) {
73395             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
73396                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
73397             }
73398         }
73399         surface.customAttributes.segment = function(opt) {
73400             return me.getSegment(opt);
73401         };
73402         me.__excludes = me.__excludes || [];
73403     },
73404
73405     //@private updates some onbefore render parameters.
73406     initialize: function() {
73407         var me = this,
73408             store = me.chart.getChartStore();
73409         //Add yFields to be used in Legend.js
73410         me.yField = [];
73411         if (me.label.field) {
73412             store.each(function(rec) {
73413                 me.yField.push(rec.get(me.label.field));
73414             });
73415         }
73416     },
73417
73418     // @private returns an object with properties for a PieSlice.
73419     getSegment: function(opt) {
73420         var me = this,
73421             rad = me.rad,
73422             cos = Math.cos,
73423             sin = Math.sin,
73424             x = me.centerX,
73425             y = me.centerY,
73426             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
73427             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
73428             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
73429             delta = 1e-2,
73430             startAngle = opt.startAngle,
73431             endAngle = opt.endAngle,
73432             midAngle = (startAngle + endAngle) / 2 * rad,
73433             margin = opt.margin || 0,
73434             a1 = Math.min(startAngle, endAngle) * rad,
73435             a2 = Math.max(startAngle, endAngle) * rad,
73436             c1 = cos(a1), s1 = sin(a1),
73437             c2 = cos(a2), s2 = sin(a2),
73438             cm = cos(midAngle), sm = sin(midAngle),
73439             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
73440
73441         if (a2 - a1 < delta) {
73442             return {path: ""};
73443         }
73444
73445         if (margin !== 0) {
73446             x += margin * cm;
73447             y += margin * sm;
73448         }
73449
73450         x2 = x + opt.endRho * c1;
73451         y2 = y + opt.endRho * s1;
73452
73453         x4 = x + opt.endRho * c2;
73454         y4 = y + opt.endRho * s2;
73455
73456         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
73457             cm = hsqr2;
73458             sm = -hsqr2;
73459             flag = 1;
73460         }
73461
73462         x6 = x + opt.endRho * cm;
73463         y6 = y + opt.endRho * sm;
73464
73465         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
73466         // Better fix the VML engine for half circles.
73467
73468         if (opt.startRho !== 0) {
73469             x1 = x + opt.startRho * c1;
73470             y1 = y + opt.startRho * s1;
73471     
73472             x3 = x + opt.startRho * c2;
73473             y3 = y + opt.startRho * s2;
73474     
73475             x5 = x + opt.startRho * cm;
73476             y5 = y + opt.startRho * sm;
73477
73478             return {
73479                 path: [
73480                     ["M", x2, y2],
73481                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73482                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73483                     ["L", x3, y3],
73484                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
73485                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
73486                     ["Z"]
73487                 ]
73488             };
73489         } else {
73490             return {
73491                 path: [
73492                     ["M", x, y],
73493                     ["L", x2, y2],
73494                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73495                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73496                     ["L", x, y],
73497                     ["Z"]
73498                 ]
73499             };
73500         }
73501     },
73502
73503     // @private utility function to calculate the middle point of a pie slice.
73504     calcMiddle: function(item) {
73505         var me = this,
73506             rad = me.rad,
73507             slice = item.slice,
73508             x = me.centerX,
73509             y = me.centerY,
73510             startAngle = slice.startAngle,
73511             endAngle = slice.endAngle,
73512             donut = +me.donut,
73513             midAngle = -(startAngle + endAngle) * rad / 2,
73514             r = (item.endRho + item.startRho) / 2,
73515             xm = x + r * Math.cos(midAngle),
73516             ym = y - r * Math.sin(midAngle);
73517
73518         item.middle = {
73519             x: xm,
73520             y: ym
73521         };
73522     },
73523
73524     /**
73525      * Draws the series for the current chart.
73526      */
73527     drawSeries: function() {
73528         var me = this,
73529             store = me.chart.getChartStore(),
73530             group = me.group,
73531             animate = me.chart.animate,
73532             field = me.angleField || me.field || me.xField,
73533             lenField = [].concat(me.lengthField),
73534             totalLenField = 0,
73535             colors = me.colorSet,
73536             chart = me.chart,
73537             surface = chart.surface,
73538             chartBBox = chart.chartBBox,
73539             enableShadows = chart.shadow,
73540             shadowGroups = me.shadowGroups,
73541             shadowAttributes = me.shadowAttributes,
73542             lnsh = shadowGroups.length,
73543             rad = me.rad,
73544             layers = lenField.length,
73545             rhoAcum = 0,
73546             donut = +me.donut,
73547             layerTotals = [],
73548             values = {},
73549             fieldLength,
73550             items = [],
73551             passed = false,
73552             totalField = 0,
73553             maxLenField = 0,
73554             cut = 9,
73555             defcut = true,
73556             angle = 0,
73557             seriesStyle = me.seriesStyle,
73558             seriesLabelStyle = me.seriesLabelStyle,
73559             colorArrayStyle = me.colorArrayStyle,
73560             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
73561             gutterX = chart.maxGutter[0],
73562             gutterY = chart.maxGutter[1],
73563             abs = Math.abs,
73564             rendererAttributes,
73565             shadowGroup,
73566             shadowAttr,
73567             shadows,
73568             shadow,
73569             shindex,
73570             centerX,
73571             centerY,
73572             deltaRho,
73573             first = 0,
73574             slice,
73575             slices,
73576             sprite,
73577             value,
73578             item,
73579             lenValue,
73580             ln,
73581             record,
73582             i,
73583             j,
73584             startAngle,
73585             endAngle,
73586             middleAngle,
73587             sliceLength,
73588             path,
73589             p,
73590             spriteOptions, bbox;
73591
73592         Ext.apply(seriesStyle, me.style || {});
73593
73594         me.setBBox();
73595         bbox = me.bbox;
73596
73597         //override theme colors
73598         if (me.colorSet) {
73599             colorArrayStyle = me.colorSet;
73600             colorArrayLength = colorArrayStyle.length;
73601         }
73602
73603         //if not store or store is empty then there's nothing to draw
73604         if (!store || !store.getCount()) {
73605             return;
73606         }
73607
73608         me.unHighlightItem();
73609         me.cleanHighlights();
73610
73611         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
73612         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
73613         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
73614         me.slices = slices = [];
73615         me.items = items = [];
73616
73617         store.each(function(record, i) {
73618             if (this.__excludes && this.__excludes[i]) {
73619                 //hidden series
73620                 return;
73621             }
73622             totalField += +record.get(field);
73623             if (lenField[0]) {
73624                 for (j = 0, totalLenField = 0; j < layers; j++) {
73625                     totalLenField += +record.get(lenField[j]);
73626                 }
73627                 layerTotals[i] = totalLenField;
73628                 maxLenField = Math.max(maxLenField, totalLenField);
73629             }
73630         }, this);
73631
73632         totalField = totalField || 1;
73633         store.each(function(record, i) {
73634             if (this.__excludes && this.__excludes[i]) {
73635                 value = 0;
73636             } else {
73637                 value = record.get(field);
73638                 if (first == 0) {
73639                     first = 1;
73640                 }
73641             }
73642
73643             // First slice
73644             if (first == 1) {
73645                 first = 2;
73646                 me.firstAngle = angle = 360 * value / totalField / 2;
73647                 for (j = 0; j < i; j++) {
73648                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
73649                 }
73650             }
73651             
73652             endAngle = angle - 360 * value / totalField;
73653             slice = {
73654                 series: me,
73655                 value: value,
73656                 startAngle: angle,
73657                 endAngle: endAngle,
73658                 storeItem: record
73659             };
73660             if (lenField[0]) {
73661                 lenValue = layerTotals[i];
73662                 slice.rho = me.radius * (lenValue / maxLenField);
73663             } else {
73664                 slice.rho = me.radius;
73665             }
73666             slices[i] = slice;
73667             angle = endAngle;
73668         }, me);
73669         //do all shadows first.
73670         if (enableShadows) {
73671             for (i = 0, ln = slices.length; i < ln; i++) {
73672                 slice = slices[i];
73673                 slice.shadowAttrs = [];
73674                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
73675                     sprite = group.getAt(i * layers + j);
73676                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73677                     //set pie slice properties
73678                     rendererAttributes = {
73679                         segment: {
73680                             startAngle: slice.startAngle,
73681                             endAngle: slice.endAngle,
73682                             margin: 0,
73683                             rho: slice.rho,
73684                             startRho: rhoAcum + (deltaRho * donut / 100),
73685                             endRho: rhoAcum + deltaRho
73686                         },
73687                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
73688                     };
73689                     //create shadows
73690                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
73691                         shadowAttr = shadowAttributes[shindex];
73692                         shadow = shadowGroups[shindex].getAt(i);
73693                         if (!shadow) {
73694                             shadow = chart.surface.add(Ext.apply({}, {
73695                                 type: 'path',
73696                                 group: shadowGroups[shindex],
73697                                 strokeLinejoin: "round"
73698                             }, rendererAttributes, shadowAttr));
73699                         }
73700                         if (animate) {
73701                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
73702                             me.onAnimate(shadow, {
73703                                 to: shadowAttr
73704                             });
73705                         } else {
73706                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
73707                             shadow.setAttributes(shadowAttr, true);
73708                         }
73709                         shadows.push(shadow);
73710                     }
73711                     slice.shadowAttrs[j] = shadows;
73712                 }
73713             }
73714         }
73715         //do pie slices after.
73716         for (i = 0, ln = slices.length; i < ln; i++) {
73717             slice = slices[i];
73718             for (j = 0, rhoAcum = 0; j < layers; j++) {
73719                 sprite = group.getAt(i * layers + j);
73720                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73721                 //set pie slice properties
73722                 rendererAttributes = Ext.apply({
73723                     segment: {
73724                         startAngle: slice.startAngle,
73725                         endAngle: slice.endAngle,
73726                         margin: 0,
73727                         rho: slice.rho,
73728                         startRho: rhoAcum + (deltaRho * donut / 100),
73729                         endRho: rhoAcum + deltaRho
73730                     },
73731                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
73732                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73733                 item = Ext.apply({},
73734                 rendererAttributes.segment, {
73735                     slice: slice,
73736                     series: me,
73737                     storeItem: slice.storeItem,
73738                     index: i
73739                 });
73740                 me.calcMiddle(item);
73741                 if (enableShadows) {
73742                     item.shadows = slice.shadowAttrs[j];
73743                 }
73744                 items[i] = item;
73745                 // Create a new sprite if needed (no height)
73746                 if (!sprite) {
73747                     spriteOptions = Ext.apply({
73748                         type: "path",
73749                         group: group,
73750                         middle: item.middle
73751                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73752                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
73753                 }
73754                 slice.sprite = slice.sprite || [];
73755                 item.sprite = sprite;
73756                 slice.sprite.push(sprite);
73757                 slice.point = [item.middle.x, item.middle.y];
73758                 if (animate) {
73759                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
73760                     sprite._to = rendererAttributes;
73761                     sprite._animating = true;
73762                     me.onAnimate(sprite, {
73763                         to: rendererAttributes,
73764                         listeners: {
73765                             afteranimate: {
73766                                 fn: function() {
73767                                     this._animating = false;
73768                                 },
73769                                 scope: sprite
73770                             }
73771                         }
73772                     });
73773                 } else {
73774                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
73775                         hidden: false
73776                     }), i, store);
73777                     sprite.setAttributes(rendererAttributes, true);
73778                 }
73779                 rhoAcum += deltaRho;
73780             }
73781         }
73782
73783         // Hide unused bars
73784         ln = group.getCount();
73785         for (i = 0; i < ln; i++) {
73786             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
73787                 group.getAt(i).hide(true);
73788             }
73789         }
73790         if (enableShadows) {
73791             lnsh = shadowGroups.length;
73792             for (shindex = 0; shindex < ln; shindex++) {
73793                 if (!slices[(shindex / layers) >> 0]) {
73794                     for (j = 0; j < lnsh; j++) {
73795                         if (shadowGroups[j].getAt(shindex)) {
73796                             shadowGroups[j].getAt(shindex).hide(true);
73797                         }
73798                     }
73799                 }
73800             }
73801         }
73802         me.renderLabels();
73803         me.renderCallouts();
73804     },
73805
73806     // @private callback for when creating a label sprite.
73807     onCreateLabel: function(storeItem, item, i, display) {
73808         var me = this,
73809             group = me.labelsGroup,
73810             config = me.label,
73811             centerX = me.centerX,
73812             centerY = me.centerY,
73813             middle = item.middle,
73814             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
73815
73816         return me.chart.surface.add(Ext.apply({
73817             'type': 'text',
73818             'text-anchor': 'middle',
73819             'group': group,
73820             'x': middle.x,
73821             'y': middle.y
73822         }, endLabelStyle));
73823     },
73824
73825     // @private callback for when placing a label sprite.
73826     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
73827         var me = this,
73828             chart = me.chart,
73829             resizing = chart.resizing,
73830             config = me.label,
73831             format = config.renderer,
73832             field = [].concat(config.field),
73833             centerX = me.centerX,
73834             centerY = me.centerY,
73835             middle = item.middle,
73836             opt = {
73837                 x: middle.x,
73838                 y: middle.y
73839             },
73840             x = middle.x - centerX,
73841             y = middle.y - centerY,
73842             from = {},
73843             rho = 1,
73844             theta = Math.atan2(y, x || 1),
73845             dg = theta * 180 / Math.PI,
73846             prevDg;
73847         if (this.__excludes && this.__excludes[i]) {
73848             opt.hidden = true;
73849         }
73850         function fixAngle(a) {
73851             if (a < 0) {
73852                 a += 360;
73853             }
73854             return a % 360;
73855         }
73856
73857         label.setAttributes({
73858             text: format(storeItem.get(field[index]))
73859         }, true);
73860
73861         switch (display) {
73862         case 'outside':
73863             rho = Math.sqrt(x * x + y * y) * 2;
73864             //update positions
73865             opt.x = rho * Math.cos(theta) + centerX;
73866             opt.y = rho * Math.sin(theta) + centerY;
73867             break;
73868
73869         case 'rotate':
73870             dg = fixAngle(dg);
73871             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
73872
73873             prevDg = label.attr.rotation.degrees;
73874             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
73875                 if (dg > prevDg) {
73876                     dg -= 360;
73877                 } else {
73878                     dg += 360;
73879                 }
73880                 dg = dg % 360;
73881             } else {
73882                 dg = fixAngle(dg);
73883             }
73884             //update rotation angle
73885             opt.rotate = {
73886                 degrees: dg,
73887                 x: opt.x,
73888                 y: opt.y
73889             };
73890             break;
73891
73892         default:
73893             break;
73894         }
73895         //ensure the object has zero translation
73896         opt.translate = {
73897             x: 0, y: 0
73898         };
73899         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
73900             me.onAnimate(label, {
73901                 to: opt
73902             });
73903         } else {
73904             label.setAttributes(opt, true);
73905         }
73906         label._from = from;
73907     },
73908
73909     // @private callback for when placing a callout sprite.
73910     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
73911         var me = this,
73912             chart = me.chart,
73913             resizing = chart.resizing,
73914             config = me.callouts,
73915             centerX = me.centerX,
73916             centerY = me.centerY,
73917             middle = item.middle,
73918             opt = {
73919                 x: middle.x,
73920                 y: middle.y
73921             },
73922             x = middle.x - centerX,
73923             y = middle.y - centerY,
73924             rho = 1,
73925             rhoCenter,
73926             theta = Math.atan2(y, x || 1),
73927             bbox = callout.label.getBBox(),
73928             offsetFromViz = 20,
73929             offsetToSide = 10,
73930             offsetBox = 10,
73931             p;
73932
73933         //should be able to config this.
73934         rho = item.endRho + offsetFromViz;
73935         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
73936         //update positions
73937         opt.x = rho * Math.cos(theta) + centerX;
73938         opt.y = rho * Math.sin(theta) + centerY;
73939
73940         x = rhoCenter * Math.cos(theta);
73941         y = rhoCenter * Math.sin(theta);
73942
73943         if (chart.animate) {
73944             //set the line from the middle of the pie to the box.
73945             me.onAnimate(callout.lines, {
73946                 to: {
73947                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73948                 }
73949             });
73950             //set box position
73951             me.onAnimate(callout.box, {
73952                 to: {
73953                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73954                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73955                     width: bbox.width + 2 * offsetBox,
73956                     height: bbox.height + 2 * offsetBox
73957                 }
73958             });
73959             //set text position
73960             me.onAnimate(callout.label, {
73961                 to: {
73962                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73963                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73964                 }
73965             });
73966         } else {
73967             //set the line from the middle of the pie to the box.
73968             callout.lines.setAttributes({
73969                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73970             },
73971             true);
73972             //set box position
73973             callout.box.setAttributes({
73974                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73975                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73976                 width: bbox.width + 2 * offsetBox,
73977                 height: bbox.height + 2 * offsetBox
73978             },
73979             true);
73980             //set text position
73981             callout.label.setAttributes({
73982                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73983                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73984             },
73985             true);
73986         }
73987         for (p in callout) {
73988             callout[p].show(true);
73989         }
73990     },
73991
73992     // @private handles sprite animation for the series.
73993     onAnimate: function(sprite, attr) {
73994         sprite.show();
73995         return this.callParent(arguments);
73996     },
73997
73998     isItemInPoint: function(x, y, item, i) {
73999         var me = this,
74000             cx = me.centerX,
74001             cy = me.centerY,
74002             abs = Math.abs,
74003             dx = abs(x - cx),
74004             dy = abs(y - cy),
74005             startAngle = item.startAngle,
74006             endAngle = item.endAngle,
74007             rho = Math.sqrt(dx * dx + dy * dy),
74008             angle = Math.atan2(y - cy, x - cx) / me.rad;
74009
74010         // normalize to the same range of angles created by drawSeries
74011         if (angle > me.firstAngle) {
74012             angle -= 360;
74013         }
74014         return (angle <= startAngle && angle > endAngle
74015                 && rho >= item.startRho && rho <= item.endRho);
74016     },
74017
74018     // @private hides all elements in the series.
74019     hideAll: function() {
74020         var i, l, shadow, shadows, sh, lsh, sprite;
74021         if (!isNaN(this._index)) {
74022             this.__excludes = this.__excludes || [];
74023             this.__excludes[this._index] = true;
74024             sprite = this.slices[this._index].sprite;
74025             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
74026                 sprite[sh].setAttributes({
74027                     hidden: true
74028                 }, true);
74029             }
74030             if (this.slices[this._index].shadowAttrs) {
74031                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
74032                     shadow = shadows[i];
74033                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
74034                         shadow[sh].setAttributes({
74035                             hidden: true
74036                         }, true);
74037                     }
74038                 }
74039             }
74040             this.drawSeries();
74041         }
74042     },
74043
74044     // @private shows all elements in the series.
74045     showAll: function() {
74046         if (!isNaN(this._index)) {
74047             this.__excludes[this._index] = false;
74048             this.drawSeries();
74049         }
74050     },
74051
74052     /**
74053      * Highlight the specified item. If no item is provided the whole series will be highlighted.
74054      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74055      */
74056     highlightItem: function(item) {
74057         var me = this,
74058             rad = me.rad;
74059         item = item || this.items[this._index];
74060
74061         //TODO(nico): sometimes in IE itemmouseover is triggered
74062         //twice without triggering itemmouseout in between. This
74063         //fixes the highlighting bug. Eventually, events should be
74064         //changed to trigger one itemmouseout between two itemmouseovers.
74065         this.unHighlightItem();
74066
74067         if (!item || item.sprite && item.sprite._animating) {
74068             return;
74069         }
74070         me.callParent([item]);
74071         if (!me.highlight) {
74072             return;
74073         }
74074         if ('segment' in me.highlightCfg) {
74075             var highlightSegment = me.highlightCfg.segment,
74076                 animate = me.chart.animate,
74077                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
74078             //animate labels
74079             if (me.labelsGroup) {
74080                 var group = me.labelsGroup,
74081                     display = me.label.display,
74082                     label = group.getAt(item.index),
74083                     middle = (item.startAngle + item.endAngle) / 2 * rad,
74084                     r = highlightSegment.margin || 0,
74085                     x = r * Math.cos(middle),
74086                     y = r * Math.sin(middle);
74087
74088                 //TODO(nico): rounding to 1e-10
74089                 //gives the right translation. Translation
74090                 //was buggy for very small numbers. In this
74091                 //case we're not looking to translate to very small
74092                 //numbers but not to translate at all.
74093                 if (Math.abs(x) < 1e-10) {
74094                     x = 0;
74095                 }
74096                 if (Math.abs(y) < 1e-10) {
74097                     y = 0;
74098                 }
74099
74100                 if (animate) {
74101                     label.stopAnimation();
74102                     label.animate({
74103                         to: {
74104                             translate: {
74105                                 x: x,
74106                                 y: y
74107                             }
74108                         },
74109                         duration: me.highlightDuration
74110                     });
74111                 }
74112                 else {
74113                     label.setAttributes({
74114                         translate: {
74115                             x: x,
74116                             y: y
74117                         }
74118                     }, true);
74119                 }
74120             }
74121             //animate shadows
74122             if (me.chart.shadow && item.shadows) {
74123                 i = 0;
74124                 shadows = item.shadows;
74125                 ln = shadows.length;
74126                 for (; i < ln; i++) {
74127                     shadow = shadows[i];
74128                     to = {};
74129                     itemHighlightSegment = item.sprite._from.segment;
74130                     for (prop in itemHighlightSegment) {
74131                         if (! (prop in highlightSegment)) {
74132                             to[prop] = itemHighlightSegment[prop];
74133                         }
74134                     }
74135                     attrs = {
74136                         segment: Ext.applyIf(to, me.highlightCfg.segment)
74137                     };
74138                     if (animate) {
74139                         shadow.stopAnimation();
74140                         shadow.animate({
74141                             to: attrs,
74142                             duration: me.highlightDuration
74143                         });
74144                     }
74145                     else {
74146                         shadow.setAttributes(attrs, true);
74147                     }
74148                 }
74149             }
74150         }
74151     },
74152
74153     /**
74154      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
74155      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74156      */
74157     unHighlightItem: function() {
74158         var me = this;
74159         if (!me.highlight) {
74160             return;
74161         }
74162
74163         if (('segment' in me.highlightCfg) && me.items) {
74164             var items = me.items,
74165                 animate = me.chart.animate,
74166                 shadowsEnabled = !!me.chart.shadow,
74167                 group = me.labelsGroup,
74168                 len = items.length,
74169                 i = 0,
74170                 j = 0,
74171                 display = me.label.display,
74172                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
74173
74174             for (; i < len; i++) {
74175                 item = items[i];
74176                 if (!item) {
74177                     continue;
74178                 }
74179                 sprite = item.sprite;
74180                 if (sprite && sprite._highlighted) {
74181                     //animate labels
74182                     if (group) {
74183                         label = group.getAt(item.index);
74184                         attrs = Ext.apply({
74185                             translate: {
74186                                 x: 0,
74187                                 y: 0
74188                             }
74189                         },
74190                         display == 'rotate' ? {
74191                             rotate: {
74192                                 x: label.attr.x,
74193                                 y: label.attr.y,
74194                                 degrees: label.attr.rotation.degrees
74195                             }
74196                         }: {});
74197                         if (animate) {
74198                             label.stopAnimation();
74199                             label.animate({
74200                                 to: attrs,
74201                                 duration: me.highlightDuration
74202                             });
74203                         }
74204                         else {
74205                             label.setAttributes(attrs, true);
74206                         }
74207                     }
74208                     if (shadowsEnabled) {
74209                         shadows = item.shadows;
74210                         shadowLen = shadows.length;
74211                         for (; j < shadowLen; j++) {
74212                             to = {};
74213                             ihs = item.sprite._to.segment;
74214                             hs = item.sprite._from.segment;
74215                             Ext.apply(to, hs);
74216                             for (p in ihs) {
74217                                 if (! (p in hs)) {
74218                                     to[p] = ihs[p];
74219                                 }
74220                             }
74221                             shadow = shadows[j];
74222                             if (animate) {
74223                                 shadow.stopAnimation();
74224                                 shadow.animate({
74225                                     to: {
74226                                         segment: to
74227                                     },
74228                                     duration: me.highlightDuration
74229                                 });
74230                             }
74231                             else {
74232                                 shadow.setAttributes({ segment: to }, true);
74233                             }
74234                         }
74235                     }
74236                 }
74237             }
74238         }
74239         me.callParent(arguments);
74240     },
74241
74242     /**
74243      * Returns the color of the series (to be displayed as color for the series legend item).
74244      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74245      */
74246     getLegendColor: function(index) {
74247         var me = this;
74248         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
74249     }
74250 });
74251
74252
74253 /**
74254  * @class Ext.chart.series.Radar
74255  * @extends Ext.chart.series.Series
74256  *
74257  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
74258  * a constrained number of categories.
74259  *
74260  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
74261  * documentation for more information. A typical configuration object for the radar series could be:
74262  *
74263  *     @example
74264  *     var store = Ext.create('Ext.data.JsonStore', {
74265  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74266  *         data: [
74267  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74268  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74269  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74270  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74271  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74272  *         ]
74273  *     });
74274  *
74275  *     Ext.create('Ext.chart.Chart', {
74276  *         renderTo: Ext.getBody(),
74277  *         width: 500,
74278  *         height: 300,
74279  *         animate: true,
74280  *         theme:'Category2',
74281  *         store: store,
74282  *         axes: [{
74283  *             type: 'Radial',
74284  *             position: 'radial',
74285  *             label: {
74286  *                 display: true
74287  *             }
74288  *         }],
74289  *         series: [{
74290  *             type: 'radar',
74291  *             xField: 'name',
74292  *             yField: 'data3',
74293  *             showInLegend: true,
74294  *             showMarkers: true,
74295  *             markerConfig: {
74296  *                 radius: 5,
74297  *                 size: 5
74298  *             },
74299  *             style: {
74300  *                 'stroke-width': 2,
74301  *                 fill: 'none'
74302  *             }
74303  *         },{
74304  *             type: 'radar',
74305  *             xField: 'name',
74306  *             yField: 'data2',
74307  *             showMarkers: true,
74308  *             showInLegend: true,
74309  *             markerConfig: {
74310  *                 radius: 5,
74311  *                 size: 5
74312  *             },
74313  *             style: {
74314  *                 'stroke-width': 2,
74315  *                 fill: 'none'
74316  *             }
74317  *         },{
74318  *             type: 'radar',
74319  *             xField: 'name',
74320  *             yField: 'data5',
74321  *             showMarkers: true,
74322  *             showInLegend: true,
74323  *             markerConfig: {
74324  *                 radius: 5,
74325  *                 size: 5
74326  *             },
74327  *             style: {
74328  *                 'stroke-width': 2,
74329  *                 fill: 'none'
74330  *             }
74331  *         }]
74332  *     });
74333  *
74334  * In this configuration we add three series to the chart. Each of these series is bound to the same
74335  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
74336  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
74337  * for the markers of each series can be set by adding properties onto the markerConfig object.
74338  * Finally we override some theme styling properties by adding properties to the `style` object.
74339  *
74340  * @xtype radar
74341  */
74342 Ext.define('Ext.chart.series.Radar', {
74343
74344     /* Begin Definitions */
74345
74346     extend: 'Ext.chart.series.Series',
74347
74348     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
74349
74350     /* End Definitions */
74351
74352     type: "radar",
74353     alias: 'series.radar',
74354
74355
74356     rad: Math.PI / 180,
74357
74358     showInLegend: false,
74359
74360     /**
74361      * @cfg {Object} style
74362      * An object containing styles for overriding series styles from Theming.
74363      */
74364     style: {},
74365
74366     constructor: function(config) {
74367         this.callParent(arguments);
74368         var me = this,
74369             surface = me.chart.surface, i, l;
74370         me.group = surface.getGroup(me.seriesId);
74371         if (me.showMarkers) {
74372             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
74373         }
74374     },
74375
74376     /**
74377      * Draws the series for the current chart.
74378      */
74379     drawSeries: function() {
74380         var me = this,
74381             store = me.chart.getChartStore(),
74382             group = me.group,
74383             sprite,
74384             chart = me.chart,
74385             animate = chart.animate,
74386             field = me.field || me.yField,
74387             surface = chart.surface,
74388             chartBBox = chart.chartBBox,
74389             rendererAttributes,
74390             centerX, centerY,
74391             items,
74392             radius,
74393             maxValue = 0,
74394             fields = [],
74395             max = Math.max,
74396             cos = Math.cos,
74397             sin = Math.sin,
74398             pi2 = Math.PI * 2,
74399             l = store.getCount(),
74400             startPath, path, x, y, rho,
74401             i, nfields,
74402             seriesStyle = me.seriesStyle,
74403             seriesLabelStyle = me.seriesLabelStyle,
74404             first = chart.resizing || !me.radar,
74405             axis = chart.axes && chart.axes.get(0),
74406             aggregate = !(axis && axis.maximum);
74407
74408         me.setBBox();
74409
74410         maxValue = aggregate? 0 : (axis.maximum || 0);
74411
74412         Ext.apply(seriesStyle, me.style || {});
74413
74414         //if the store is empty then there's nothing to draw
74415         if (!store || !store.getCount()) {
74416             return;
74417         }
74418
74419         me.unHighlightItem();
74420         me.cleanHighlights();
74421
74422         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
74423         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
74424         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
74425         me.items = items = [];
74426
74427         if (aggregate) {
74428             //get all renderer fields
74429             chart.series.each(function(series) {
74430                 fields.push(series.yField);
74431             });
74432             //get maxValue to interpolate
74433             store.each(function(record, i) {
74434                 for (i = 0, nfields = fields.length; i < nfields; i++) {
74435                     maxValue = max(+record.get(fields[i]), maxValue);
74436                 }
74437             });
74438         }
74439         //ensure non-zero value.
74440         maxValue = maxValue || 1;
74441         //create path and items
74442         startPath = []; path = [];
74443         store.each(function(record, i) {
74444             rho = radius * record.get(field) / maxValue;
74445             x = rho * cos(i / l * pi2);
74446             y = rho * sin(i / l * pi2);
74447             if (i == 0) {
74448                 path.push('M', x + centerX, y + centerY);
74449                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
74450             } else {
74451                 path.push('L', x + centerX, y + centerY);
74452                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
74453             }
74454             items.push({
74455                 sprite: false, //TODO(nico): add markers
74456                 point: [centerX + x, centerY + y],
74457                 series: me
74458             });
74459         });
74460         path.push('Z');
74461         //create path sprite
74462         if (!me.radar) {
74463             me.radar = surface.add(Ext.apply({
74464                 type: 'path',
74465                 group: group,
74466                 path: startPath
74467             }, seriesStyle || {}));
74468         }
74469         //reset on resizing
74470         if (chart.resizing) {
74471             me.radar.setAttributes({
74472                 path: startPath
74473             }, true);
74474         }
74475         //render/animate
74476         if (chart.animate) {
74477             me.onAnimate(me.radar, {
74478                 to: Ext.apply({
74479                     path: path
74480                 }, seriesStyle || {})
74481             });
74482         } else {
74483             me.radar.setAttributes(Ext.apply({
74484                 path: path
74485             }, seriesStyle || {}), true);
74486         }
74487         //render markers, labels and callouts
74488         if (me.showMarkers) {
74489             me.drawMarkers();
74490         }
74491         me.renderLabels();
74492         me.renderCallouts();
74493     },
74494
74495     // @private draws the markers for the lines (if any).
74496     drawMarkers: function() {
74497         var me = this,
74498             chart = me.chart,
74499             surface = chart.surface,
74500             markerStyle = Ext.apply({}, me.markerStyle || {}),
74501             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
74502             items = me.items,
74503             type = endMarkerStyle.type,
74504             markerGroup = me.markerGroup,
74505             centerX = me.centerX,
74506             centerY = me.centerY,
74507             item, i, l, marker;
74508
74509         delete endMarkerStyle.type;
74510
74511         for (i = 0, l = items.length; i < l; i++) {
74512             item = items[i];
74513             marker = markerGroup.getAt(i);
74514             if (!marker) {
74515                 marker = Ext.chart.Shape[type](surface, Ext.apply({
74516                     group: markerGroup,
74517                     x: 0,
74518                     y: 0,
74519                     translate: {
74520                         x: centerX,
74521                         y: centerY
74522                     }
74523                 }, endMarkerStyle));
74524             }
74525             else {
74526                 marker.show();
74527             }
74528             if (chart.resizing) {
74529                 marker.setAttributes({
74530                     x: 0,
74531                     y: 0,
74532                     translate: {
74533                         x: centerX,
74534                         y: centerY
74535                     }
74536                 }, true);
74537             }
74538             marker._to = {
74539                 translate: {
74540                     x: item.point[0],
74541                     y: item.point[1]
74542                 }
74543             };
74544             //render/animate
74545             if (chart.animate) {
74546                 me.onAnimate(marker, {
74547                     to: marker._to
74548                 });
74549             }
74550             else {
74551                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
74552             }
74553         }
74554     },
74555
74556     isItemInPoint: function(x, y, item) {
74557         var point,
74558             tolerance = 10,
74559             abs = Math.abs;
74560         point = item.point;
74561         return (abs(point[0] - x) <= tolerance &&
74562                 abs(point[1] - y) <= tolerance);
74563     },
74564
74565     // @private callback for when creating a label sprite.
74566     onCreateLabel: function(storeItem, item, i, display) {
74567         var me = this,
74568             group = me.labelsGroup,
74569             config = me.label,
74570             centerX = me.centerX,
74571             centerY = me.centerY,
74572             point = item.point,
74573             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
74574
74575         return me.chart.surface.add(Ext.apply({
74576             'type': 'text',
74577             'text-anchor': 'middle',
74578             'group': group,
74579             'x': centerX,
74580             'y': centerY
74581         }, config || {}));
74582     },
74583
74584     // @private callback for when placing a label sprite.
74585     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
74586         var me = this,
74587             chart = me.chart,
74588             resizing = chart.resizing,
74589             config = me.label,
74590             format = config.renderer,
74591             field = config.field,
74592             centerX = me.centerX,
74593             centerY = me.centerY,
74594             opt = {
74595                 x: item.point[0],
74596                 y: item.point[1]
74597             },
74598             x = opt.x - centerX,
74599             y = opt.y - centerY;
74600
74601         label.setAttributes({
74602             text: format(storeItem.get(field)),
74603             hidden: true
74604         },
74605         true);
74606
74607         if (resizing) {
74608             label.setAttributes({
74609                 x: centerX,
74610                 y: centerY
74611             }, true);
74612         }
74613
74614         if (animate) {
74615             label.show(true);
74616             me.onAnimate(label, {
74617                 to: opt
74618             });
74619         } else {
74620             label.setAttributes(opt, true);
74621             label.show(true);
74622         }
74623     },
74624
74625     // @private for toggling (show/hide) series.
74626     toggleAll: function(show) {
74627         var me = this,
74628             i, ln, shadow, shadows;
74629         if (!show) {
74630             Ext.chart.series.Radar.superclass.hideAll.call(me);
74631         }
74632         else {
74633             Ext.chart.series.Radar.superclass.showAll.call(me);
74634         }
74635         if (me.radar) {
74636             me.radar.setAttributes({
74637                 hidden: !show
74638             }, true);
74639             //hide shadows too
74640             if (me.radar.shadows) {
74641                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
74642                     shadow = shadows[i];
74643                     shadow.setAttributes({
74644                         hidden: !show
74645                     }, true);
74646                 }
74647             }
74648         }
74649     },
74650
74651     // @private hide all elements in the series.
74652     hideAll: function() {
74653         this.toggleAll(false);
74654         this.hideMarkers(0);
74655     },
74656
74657     // @private show all elements in the series.
74658     showAll: function() {
74659         this.toggleAll(true);
74660     },
74661
74662     // @private hide all markers that belong to `markerGroup`
74663     hideMarkers: function(index) {
74664         var me = this,
74665             count = me.markerGroup && me.markerGroup.getCount() || 0,
74666             i = index || 0;
74667         for (; i < count; i++) {
74668             me.markerGroup.getAt(i).hide(true);
74669         }
74670     }
74671 });
74672
74673
74674 /**
74675  * @class Ext.chart.series.Scatter
74676  * @extends Ext.chart.series.Cartesian
74677  *
74678  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
74679  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
74680  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
74681  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
74682  *
74683  *     @example
74684  *     var store = Ext.create('Ext.data.JsonStore', {
74685  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74686  *         data: [
74687  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74688  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74689  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74690  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74691  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74692  *         ]
74693  *     });
74694  *
74695  *     Ext.create('Ext.chart.Chart', {
74696  *         renderTo: Ext.getBody(),
74697  *         width: 500,
74698  *         height: 300,
74699  *         animate: true,
74700  *         theme:'Category2',
74701  *         store: store,
74702  *         axes: [{
74703  *             type: 'Numeric',
74704  *             position: 'left',
74705  *             fields: ['data2', 'data3'],
74706  *             title: 'Sample Values',
74707  *             grid: true,
74708  *             minimum: 0
74709  *         }, {
74710  *             type: 'Category',
74711  *             position: 'bottom',
74712  *             fields: ['name'],
74713  *             title: 'Sample Metrics'
74714  *         }],
74715  *         series: [{
74716  *             type: 'scatter',
74717  *             markerConfig: {
74718  *                 radius: 5,
74719  *                 size: 5
74720  *             },
74721  *             axis: 'left',
74722  *             xField: 'name',
74723  *             yField: 'data2'
74724  *         }, {
74725  *             type: 'scatter',
74726  *             markerConfig: {
74727  *                 radius: 5,
74728  *                 size: 5
74729  *             },
74730  *             axis: 'left',
74731  *             xField: 'name',
74732  *             yField: 'data3'
74733  *         }]
74734  *     });
74735  *
74736  * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
74737  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
74738  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
74739  * axis to show the current values of the elements.
74740  *
74741  * @xtype scatter
74742  */
74743 Ext.define('Ext.chart.series.Scatter', {
74744
74745     /* Begin Definitions */
74746
74747     extend: 'Ext.chart.series.Cartesian',
74748
74749     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
74750
74751     /* End Definitions */
74752
74753     type: 'scatter',
74754     alias: 'series.scatter',
74755
74756     /**
74757      * @cfg {Object} markerConfig
74758      * The display style for the scatter series markers.
74759      */
74760
74761     /**
74762      * @cfg {Object} style
74763      * Append styling properties to this object for it to override theme properties.
74764      */
74765     
74766     /**
74767      * @cfg {String/Array} axis
74768      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
74769      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
74770      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
74771      */
74772
74773     constructor: function(config) {
74774         this.callParent(arguments);
74775         var me = this,
74776             shadow = me.chart.shadow,
74777             surface = me.chart.surface, i, l;
74778         Ext.apply(me, config, {
74779             style: {},
74780             markerConfig: {},
74781             shadowAttributes: [{
74782                 "stroke-width": 6,
74783                 "stroke-opacity": 0.05,
74784                 stroke: 'rgb(0, 0, 0)'
74785             }, {
74786                 "stroke-width": 4,
74787                 "stroke-opacity": 0.1,
74788                 stroke: 'rgb(0, 0, 0)'
74789             }, {
74790                 "stroke-width": 2,
74791                 "stroke-opacity": 0.15,
74792                 stroke: 'rgb(0, 0, 0)'
74793             }]
74794         });
74795         me.group = surface.getGroup(me.seriesId);
74796         if (shadow) {
74797             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
74798                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
74799             }
74800         }
74801     },
74802
74803     // @private Get chart and data boundaries
74804     getBounds: function() {
74805         var me = this,
74806             chart = me.chart,
74807             store = chart.getChartStore(),
74808             axes = [].concat(me.axis),
74809             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
74810
74811         me.setBBox();
74812         bbox = me.bbox;
74813
74814         for (i = 0, ln = axes.length; i < ln; i++) {
74815             axis = chart.axes.get(axes[i]);
74816             if (axis) {
74817                 ends = axis.calcEnds();
74818                 if (axis.position == 'top' || axis.position == 'bottom') {
74819                     minX = ends.from;
74820                     maxX = ends.to;
74821                 }
74822                 else {
74823                     minY = ends.from;
74824                     maxY = ends.to;
74825                 }
74826             }
74827         }
74828         // If a field was specified without a corresponding axis, create one to get bounds
74829         if (me.xField && !Ext.isNumber(minX)) {
74830             axis = Ext.create('Ext.chart.axis.Axis', {
74831                 chart: chart,
74832                 fields: [].concat(me.xField)
74833             }).calcEnds();
74834             minX = axis.from;
74835             maxX = axis.to;
74836         }
74837         if (me.yField && !Ext.isNumber(minY)) {
74838             axis = Ext.create('Ext.chart.axis.Axis', {
74839                 chart: chart,
74840                 fields: [].concat(me.yField)
74841             }).calcEnds();
74842             minY = axis.from;
74843             maxY = axis.to;
74844         }
74845
74846         if (isNaN(minX)) {
74847             minX = 0;
74848             maxX = store.getCount() - 1;
74849             xScale = bbox.width / (store.getCount() - 1);
74850         }
74851         else {
74852             xScale = bbox.width / (maxX - minX);
74853         }
74854
74855         if (isNaN(minY)) {
74856             minY = 0;
74857             maxY = store.getCount() - 1;
74858             yScale = bbox.height / (store.getCount() - 1);
74859         }
74860         else {
74861             yScale = bbox.height / (maxY - minY);
74862         }
74863
74864         return {
74865             bbox: bbox,
74866             minX: minX,
74867             minY: minY,
74868             xScale: xScale,
74869             yScale: yScale
74870         };
74871     },
74872
74873     // @private Build an array of paths for the chart
74874     getPaths: function() {
74875         var me = this,
74876             chart = me.chart,
74877             enableShadows = chart.shadow,
74878             store = chart.getChartStore(),
74879             group = me.group,
74880             bounds = me.bounds = me.getBounds(),
74881             bbox = me.bbox,
74882             xScale = bounds.xScale,
74883             yScale = bounds.yScale,
74884             minX = bounds.minX,
74885             minY = bounds.minY,
74886             boxX = bbox.x,
74887             boxY = bbox.y,
74888             boxHeight = bbox.height,
74889             items = me.items = [],
74890             attrs = [],
74891             x, y, xValue, yValue, sprite;
74892
74893         store.each(function(record, i) {
74894             xValue = record.get(me.xField);
74895             yValue = record.get(me.yField);
74896             //skip undefined values
74897             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
74898                 return;
74899             }
74900             // Ensure a value
74901             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
74902                 xValue = i;
74903             }
74904             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
74905                 yValue = i;
74906             }
74907             x = boxX + (xValue - minX) * xScale;
74908             y = boxY + boxHeight - (yValue - minY) * yScale;
74909             attrs.push({
74910                 x: x,
74911                 y: y
74912             });
74913
74914             me.items.push({
74915                 series: me,
74916                 value: [xValue, yValue],
74917                 point: [x, y],
74918                 storeItem: record
74919             });
74920
74921             // When resizing, reset before animating
74922             if (chart.animate && chart.resizing) {
74923                 sprite = group.getAt(i);
74924                 if (sprite) {
74925                     me.resetPoint(sprite);
74926                     if (enableShadows) {
74927                         me.resetShadow(sprite);
74928                     }
74929                 }
74930             }
74931         });
74932         return attrs;
74933     },
74934
74935     // @private translate point to the center
74936     resetPoint: function(sprite) {
74937         var bbox = this.bbox;
74938         sprite.setAttributes({
74939             translate: {
74940                 x: (bbox.x + bbox.width) / 2,
74941                 y: (bbox.y + bbox.height) / 2
74942             }
74943         }, true);
74944     },
74945
74946     // @private translate shadows of a sprite to the center
74947     resetShadow: function(sprite) {
74948         var me = this,
74949             shadows = sprite.shadows,
74950             shadowAttributes = me.shadowAttributes,
74951             ln = me.shadowGroups.length,
74952             bbox = me.bbox,
74953             i, attr;
74954         for (i = 0; i < ln; i++) {
74955             attr = Ext.apply({}, shadowAttributes[i]);
74956             if (attr.translate) {
74957                 attr.translate.x += (bbox.x + bbox.width) / 2;
74958                 attr.translate.y += (bbox.y + bbox.height) / 2;
74959             }
74960             else {
74961                 attr.translate = {
74962                     x: (bbox.x + bbox.width) / 2,
74963                     y: (bbox.y + bbox.height) / 2
74964                 };
74965             }
74966             shadows[i].setAttributes(attr, true);
74967         }
74968     },
74969
74970     // @private create a new point
74971     createPoint: function(attr, type) {
74972         var me = this,
74973             chart = me.chart,
74974             group = me.group,
74975             bbox = me.bbox;
74976
74977         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
74978             x: 0,
74979             y: 0,
74980             group: group,
74981             translate: {
74982                 x: (bbox.x + bbox.width) / 2,
74983                 y: (bbox.y + bbox.height) / 2
74984             }
74985         }, attr));
74986     },
74987
74988     // @private create a new set of shadows for a sprite
74989     createShadow: function(sprite, endMarkerStyle, type) {
74990         var me = this,
74991             chart = me.chart,
74992             shadowGroups = me.shadowGroups,
74993             shadowAttributes = me.shadowAttributes,
74994             lnsh = shadowGroups.length,
74995             bbox = me.bbox,
74996             i, shadow, shadows, attr;
74997
74998         sprite.shadows = shadows = [];
74999
75000         for (i = 0; i < lnsh; i++) {
75001             attr = Ext.apply({}, shadowAttributes[i]);
75002             if (attr.translate) {
75003                 attr.translate.x += (bbox.x + bbox.width) / 2;
75004                 attr.translate.y += (bbox.y + bbox.height) / 2;
75005             }
75006             else {
75007                 Ext.apply(attr, {
75008                     translate: {
75009                         x: (bbox.x + bbox.width) / 2,
75010                         y: (bbox.y + bbox.height) / 2
75011                     }
75012                 });
75013             }
75014             Ext.apply(attr, endMarkerStyle);
75015             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
75016                 x: 0,
75017                 y: 0,
75018                 group: shadowGroups[i]
75019             }, attr));
75020             shadows.push(shadow);
75021         }
75022     },
75023
75024     /**
75025      * Draws the series for the current chart.
75026      */
75027     drawSeries: function() {
75028         var me = this,
75029             chart = me.chart,
75030             store = chart.getChartStore(),
75031             group = me.group,
75032             enableShadows = chart.shadow,
75033             shadowGroups = me.shadowGroups,
75034             shadowAttributes = me.shadowAttributes,
75035             lnsh = shadowGroups.length,
75036             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
75037             rendererAttributes, shadowAttribute;
75038
75039         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
75040         type = endMarkerStyle.type;
75041         delete endMarkerStyle.type;
75042
75043         //if the store is empty then there's nothing to be rendered
75044         if (!store || !store.getCount()) {
75045             return;
75046         }
75047
75048         me.unHighlightItem();
75049         me.cleanHighlights();
75050
75051         attrs = me.getPaths();
75052         ln = attrs.length;
75053         for (i = 0; i < ln; i++) {
75054             attr = attrs[i];
75055             sprite = group.getAt(i);
75056             Ext.apply(attr, endMarkerStyle);
75057
75058             // Create a new sprite if needed (no height)
75059             if (!sprite) {
75060                 sprite = me.createPoint(attr, type);
75061                 if (enableShadows) {
75062                     me.createShadow(sprite, endMarkerStyle, type);
75063                 }
75064             }
75065
75066             shadows = sprite.shadows;
75067             if (chart.animate) {
75068                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75069                 sprite._to = rendererAttributes;
75070                 me.onAnimate(sprite, {
75071                     to: rendererAttributes
75072                 });
75073                 //animate shadows
75074                 for (shindex = 0; shindex < lnsh; shindex++) {
75075                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75076                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75077                         hidden: false,
75078                         translate: {
75079                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75080                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75081                         }
75082                     }, shadowAttribute), i, store);
75083                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
75084                 }
75085             }
75086             else {
75087                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75088                 sprite._to = rendererAttributes;
75089                 sprite.setAttributes(rendererAttributes, true);
75090                 //animate shadows
75091                 for (shindex = 0; shindex < lnsh; shindex++) {
75092                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75093                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75094                         hidden: false,
75095                         translate: {
75096                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75097                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75098                         } 
75099                     }, shadowAttribute), i, store);
75100                     shadows[shindex].setAttributes(rendererAttributes, true);
75101                 }
75102             }
75103             me.items[i].sprite = sprite;
75104         }
75105
75106         // Hide unused sprites
75107         ln = group.getCount();
75108         for (i = attrs.length; i < ln; i++) {
75109             group.getAt(i).hide(true);
75110         }
75111         me.renderLabels();
75112         me.renderCallouts();
75113     },
75114
75115     // @private callback for when creating a label sprite.
75116     onCreateLabel: function(storeItem, item, i, display) {
75117         var me = this,
75118             group = me.labelsGroup,
75119             config = me.label,
75120             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
75121             bbox = me.bbox;
75122
75123         return me.chart.surface.add(Ext.apply({
75124             type: 'text',
75125             group: group,
75126             x: item.point[0],
75127             y: bbox.y + bbox.height / 2
75128         }, endLabelStyle));
75129     },
75130
75131     // @private callback for when placing a label sprite.
75132     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75133         var me = this,
75134             chart = me.chart,
75135             resizing = chart.resizing,
75136             config = me.label,
75137             format = config.renderer,
75138             field = config.field,
75139             bbox = me.bbox,
75140             x = item.point[0],
75141             y = item.point[1],
75142             radius = item.sprite.attr.radius,
75143             bb, width, height, anim;
75144
75145         label.setAttributes({
75146             text: format(storeItem.get(field)),
75147             hidden: true
75148         }, true);
75149
75150         if (display == 'rotate') {
75151             label.setAttributes({
75152                 'text-anchor': 'start',
75153                 'rotation': {
75154                     x: x,
75155                     y: y,
75156                     degrees: -45
75157                 }
75158             }, true);
75159             //correct label position to fit into the box
75160             bb = label.getBBox();
75161             width = bb.width;
75162             height = bb.height;
75163             x = x < bbox.x? bbox.x : x;
75164             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
75165             y = (y - height < bbox.y)? bbox.y + height : y;
75166
75167         } else if (display == 'under' || display == 'over') {
75168             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
75169             bb = item.sprite.getBBox();
75170             bb.width = bb.width || (radius * 2);
75171             bb.height = bb.height || (radius * 2);
75172             y = y + (display == 'over'? -bb.height : bb.height);
75173             //correct label position to fit into the box
75174             bb = label.getBBox();
75175             width = bb.width/2;
75176             height = bb.height/2;
75177             x = x - width < bbox.x ? bbox.x + width : x;
75178             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
75179             y = y - height < bbox.y? bbox.y + height : y;
75180             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
75181         }
75182
75183         if (!chart.animate) {
75184             label.setAttributes({
75185                 x: x,
75186                 y: y
75187             }, true);
75188             label.show(true);
75189         }
75190         else {
75191             if (resizing) {
75192                 anim = item.sprite.getActiveAnimation();
75193                 if (anim) {
75194                     anim.on('afteranimate', function() {
75195                         label.setAttributes({
75196                             x: x,
75197                             y: y
75198                         }, true);
75199                         label.show(true);
75200                     });
75201                 }
75202                 else {
75203                     label.show(true);
75204                 }
75205             }
75206             else {
75207                 me.onAnimate(label, {
75208                     to: {
75209                         x: x,
75210                         y: y
75211                     }
75212                 });
75213             }
75214         }
75215     },
75216
75217     // @private callback for when placing a callout sprite.
75218     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
75219         var me = this,
75220             chart = me.chart,
75221             surface = chart.surface,
75222             resizing = chart.resizing,
75223             config = me.callouts,
75224             items = me.items,
75225             cur = item.point,
75226             normal,
75227             bbox = callout.label.getBBox(),
75228             offsetFromViz = 30,
75229             offsetToSide = 10,
75230             offsetBox = 3,
75231             boxx, boxy, boxw, boxh,
75232             p, clipRect = me.bbox,
75233             x, y;
75234
75235         //position
75236         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
75237         x = cur[0] + normal[0] * offsetFromViz;
75238         y = cur[1] + normal[1] * offsetFromViz;
75239
75240         //box position and dimensions
75241         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75242         boxy = y - bbox.height /2 - offsetBox;
75243         boxw = bbox.width + 2 * offsetBox;
75244         boxh = bbox.height + 2 * offsetBox;
75245
75246         //now check if we're out of bounds and invert the normal vector correspondingly
75247         //this may add new overlaps between labels (but labels won't be out of bounds).
75248         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
75249             normal[0] *= -1;
75250         }
75251         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
75252             normal[1] *= -1;
75253         }
75254
75255         //update positions
75256         x = cur[0] + normal[0] * offsetFromViz;
75257         y = cur[1] + normal[1] * offsetFromViz;
75258
75259         //update box position and dimensions
75260         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75261         boxy = y - bbox.height /2 - offsetBox;
75262         boxw = bbox.width + 2 * offsetBox;
75263         boxh = bbox.height + 2 * offsetBox;
75264
75265         if (chart.animate) {
75266             //set the line from the middle of the pie to the box.
75267             me.onAnimate(callout.lines, {
75268                 to: {
75269                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75270                 }
75271             }, true);
75272             //set box position
75273             me.onAnimate(callout.box, {
75274                 to: {
75275                     x: boxx,
75276                     y: boxy,
75277                     width: boxw,
75278                     height: boxh
75279                 }
75280             }, true);
75281             //set text position
75282             me.onAnimate(callout.label, {
75283                 to: {
75284                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75285                     y: y
75286                 }
75287             }, true);
75288         } else {
75289             //set the line from the middle of the pie to the box.
75290             callout.lines.setAttributes({
75291                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75292             }, true);
75293             //set box position
75294             callout.box.setAttributes({
75295                 x: boxx,
75296                 y: boxy,
75297                 width: boxw,
75298                 height: boxh
75299             }, true);
75300             //set text position
75301             callout.label.setAttributes({
75302                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75303                 y: y
75304             }, true);
75305         }
75306         for (p in callout) {
75307             callout[p].show(true);
75308         }
75309     },
75310
75311     // @private handles sprite animation for the series.
75312     onAnimate: function(sprite, attr) {
75313         sprite.show();
75314         return this.callParent(arguments);
75315     },
75316
75317     isItemInPoint: function(x, y, item) {
75318         var point,
75319             tolerance = 10,
75320             abs = Math.abs;
75321
75322         function dist(point) {
75323             var dx = abs(point[0] - x),
75324                 dy = abs(point[1] - y);
75325             return Math.sqrt(dx * dx + dy * dy);
75326         }
75327         point = item.point;
75328         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
75329             point[1] - tolerance <= y && point[1] + tolerance >= y);
75330     }
75331 });
75332
75333
75334 /**
75335  * @class Ext.chart.theme.Base
75336  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
75337  * @ignore
75338  */
75339 Ext.define('Ext.chart.theme.Base', {
75340
75341     /* Begin Definitions */
75342
75343     requires: ['Ext.chart.theme.Theme'],
75344
75345     /* End Definitions */
75346
75347     constructor: function(config) {
75348         Ext.chart.theme.call(this, config, {
75349             background: false,
75350             axis: {
75351                 stroke: '#444',
75352                 'stroke-width': 1
75353             },
75354             axisLabelTop: {
75355                 fill: '#444',
75356                 font: '12px Arial, Helvetica, sans-serif',
75357                 spacing: 2,
75358                 padding: 5,
75359                 renderer: function(v) { return v; }
75360             },
75361             axisLabelRight: {
75362                 fill: '#444',
75363                 font: '12px Arial, Helvetica, sans-serif',
75364                 spacing: 2,
75365                 padding: 5,
75366                 renderer: function(v) { return v; }
75367             },
75368             axisLabelBottom: {
75369                 fill: '#444',
75370                 font: '12px Arial, Helvetica, sans-serif',
75371                 spacing: 2,
75372                 padding: 5,
75373                 renderer: function(v) { return v; }
75374             },
75375             axisLabelLeft: {
75376                 fill: '#444',
75377                 font: '12px Arial, Helvetica, sans-serif',
75378                 spacing: 2,
75379                 padding: 5,
75380                 renderer: function(v) { return v; }
75381             },
75382             axisTitleTop: {
75383                 font: 'bold 18px Arial',
75384                 fill: '#444'
75385             },
75386             axisTitleRight: {
75387                 font: 'bold 18px Arial',
75388                 fill: '#444',
75389                 rotate: {
75390                     x:0, y:0,
75391                     degrees: 270
75392                 }
75393             },
75394             axisTitleBottom: {
75395                 font: 'bold 18px Arial',
75396                 fill: '#444'
75397             },
75398             axisTitleLeft: {
75399                 font: 'bold 18px Arial',
75400                 fill: '#444',
75401                 rotate: {
75402                     x:0, y:0,
75403                     degrees: 270
75404                 }
75405             },
75406             series: {
75407                 'stroke-width': 0
75408             },
75409             seriesLabel: {
75410                 font: '12px Arial',
75411                 fill: '#333'
75412             },
75413             marker: {
75414                 stroke: '#555',
75415                 fill: '#000',
75416                 radius: 3,
75417                 size: 3
75418             },
75419             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
75420             seriesThemes: [{
75421                 fill: "#115fa6"
75422             }, {
75423                 fill: "#94ae0a"
75424             }, {
75425                 fill: "#a61120"
75426             }, {
75427                 fill: "#ff8809"
75428             }, {
75429                 fill: "#ffd13e"
75430             }, {
75431                 fill: "#a61187"
75432             }, {
75433                 fill: "#24ad9a"
75434             }, {
75435                 fill: "#7c7474"
75436             }, {
75437                 fill: "#a66111"
75438             }],
75439             markerThemes: [{
75440                 fill: "#115fa6",
75441                 type: 'circle' 
75442             }, {
75443                 fill: "#94ae0a",
75444                 type: 'cross'
75445             }, {
75446                 fill: "#a61120",
75447                 type: 'plus'
75448             }]
75449         });
75450     }
75451 }, function() {
75452     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
75453         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
75454         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
75455         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
75456                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
75457                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
75458                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
75459                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
75460                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
75461         cats = categories.length;
75462     
75463     //Create themes from base colors
75464     for (; i < l; i++) {
75465         themes[names[i]] = (function(color) {
75466             return Ext.extend(themes.Base, {
75467                 constructor: function(config) {
75468                     themes.Base.prototype.constructor.call(this, Ext.apply({
75469                         baseColor: color
75470                     }, config));
75471                 }
75472             });
75473         })(palette[i]);
75474     }
75475     
75476     //Create theme from color array
75477     for (i = 0; i < cats; i++) {
75478         themes['Category' + (i + 1)] = (function(category) {
75479             return Ext.extend(themes.Base, {
75480                 constructor: function(config) {
75481                     themes.Base.prototype.constructor.call(this, Ext.apply({
75482                         colors: category
75483                     }, config));
75484                 }
75485             });
75486         })(categories[i]);
75487     }
75488 });
75489
75490 /**
75491  * @author Ed Spencer
75492  *
75493  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
75494  * automatically configured with a {@link Ext.data.reader.Array}.
75495  *
75496  * A store configuration would be something like:
75497  *
75498  *     var store = Ext.create('Ext.data.ArrayStore', {
75499  *         // store configs
75500  *         autoDestroy: true,
75501  *         storeId: 'myStore',
75502  *         // reader configs
75503  *         idIndex: 0,
75504  *         fields: [
75505  *            'company',
75506  *            {name: 'price', type: 'float'},
75507  *            {name: 'change', type: 'float'},
75508  *            {name: 'pctChange', type: 'float'},
75509  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
75510  *         ]
75511  *     });
75512  *
75513  * This store is configured to consume a returned object of the form:
75514  *
75515  *     var myData = [
75516  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
75517  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
75518  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
75519  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
75520  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
75521  *     ];
75522  *
75523  * An object literal of this form could also be used as the {@link #data} config option.
75524  *
75525  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
75526  */
75527 Ext.define('Ext.data.ArrayStore', {
75528     extend: 'Ext.data.Store',
75529     alias: 'store.array',
75530     uses: ['Ext.data.reader.Array'],
75531
75532     constructor: function(config) {
75533         config = config || {};
75534
75535         Ext.applyIf(config, {
75536             proxy: {
75537                 type: 'memory',
75538                 reader: 'array'
75539             }
75540         });
75541
75542         this.callParent([config]);
75543     },
75544
75545     loadData: function(data, append) {
75546         if (this.expandData === true) {
75547             var r = [],
75548                 i = 0,
75549                 ln = data.length;
75550
75551             for (; i < ln; i++) {
75552                 r[r.length] = [data[i]];
75553             }
75554
75555             data = r;
75556         }
75557
75558         this.callParent([data, append]);
75559     }
75560 }, function() {
75561     // backwards compat
75562     Ext.data.SimpleStore = Ext.data.ArrayStore;
75563     // Ext.reg('simplestore', Ext.data.SimpleStore);
75564 });
75565
75566 /**
75567  * @author Ed Spencer
75568  * @class Ext.data.Batch
75569  *
75570  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
75571  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
75572  * event if any of the Operations encounter an exception.</p>
75573  *
75574  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
75575  *
75576  */
75577 Ext.define('Ext.data.Batch', {
75578     mixins: {
75579         observable: 'Ext.util.Observable'
75580     },
75581
75582     /**
75583      * @property {Boolean} autoStart
75584      * True to immediately start processing the batch as soon as it is constructed.
75585      */
75586     autoStart: false,
75587
75588     /**
75589      * @property {Number} current
75590      * The index of the current operation being executed
75591      */
75592     current: -1,
75593
75594     /**
75595      * @property {Number} total
75596      * The total number of operations in this batch. Read only
75597      */
75598     total: 0,
75599
75600     /**
75601      * @property {Boolean} isRunning
75602      * True if the batch is currently running
75603      */
75604     isRunning: false,
75605
75606     /**
75607      * @property {Boolean} isComplete
75608      * True if this batch has been executed completely
75609      */
75610     isComplete: false,
75611
75612     /**
75613      * @property {Boolean} hasException
75614      * True if this batch has encountered an exception. This is cleared at the start of each operation
75615      */
75616     hasException: false,
75617
75618     /**
75619      * @property {Boolean} pauseOnException
75620      * True to automatically pause the execution of the batch if any operation encounters an exception
75621      */
75622     pauseOnException: true,
75623
75624     /**
75625      * Creates new Batch object.
75626      * @param {Object} [config] Config object
75627      */
75628     constructor: function(config) {
75629         var me = this;
75630
75631         me.addEvents(
75632           /**
75633            * @event complete
75634            * Fired when all operations of this batch have been completed
75635            * @param {Ext.data.Batch} batch The batch object
75636            * @param {Object} operation The last operation that was executed
75637            */
75638           'complete',
75639
75640           /**
75641            * @event exception
75642            * Fired when a operation encountered an exception
75643            * @param {Ext.data.Batch} batch The batch object
75644            * @param {Object} operation The operation that encountered the exception
75645            */
75646           'exception',
75647
75648           /**
75649            * @event operationcomplete
75650            * Fired when each operation of the batch completes
75651            * @param {Ext.data.Batch} batch The batch object
75652            * @param {Object} operation The operation that just completed
75653            */
75654           'operationcomplete'
75655         );
75656
75657         me.mixins.observable.constructor.call(me, config);
75658
75659         /**
75660          * Ordered array of operations that will be executed by this batch
75661          * @property {Ext.data.Operation[]} operations
75662          */
75663         me.operations = [];
75664     },
75665
75666     /**
75667      * Adds a new operation to this batch
75668      * @param {Object} operation The {@link Ext.data.Operation Operation} object
75669      */
75670     add: function(operation) {
75671         this.total++;
75672
75673         operation.setBatch(this);
75674
75675         this.operations.push(operation);
75676     },
75677
75678     /**
75679      * Kicks off the execution of the batch, continuing from the next operation if the previous
75680      * operation encountered an exception, or if execution was paused
75681      */
75682     start: function() {
75683         this.hasException = false;
75684         this.isRunning = true;
75685
75686         this.runNextOperation();
75687     },
75688
75689     /**
75690      * @private
75691      * Runs the next operation, relative to this.current.
75692      */
75693     runNextOperation: function() {
75694         this.runOperation(this.current + 1);
75695     },
75696
75697     /**
75698      * Pauses execution of the batch, but does not cancel the current operation
75699      */
75700     pause: function() {
75701         this.isRunning = false;
75702     },
75703
75704     /**
75705      * Executes a operation by its numeric index
75706      * @param {Number} index The operation index to run
75707      */
75708     runOperation: function(index) {
75709         var me = this,
75710             operations = me.operations,
75711             operation  = operations[index],
75712             onProxyReturn;
75713
75714         if (operation === undefined) {
75715             me.isRunning  = false;
75716             me.isComplete = true;
75717             me.fireEvent('complete', me, operations[operations.length - 1]);
75718         } else {
75719             me.current = index;
75720
75721             onProxyReturn = function(operation) {
75722                 var hasException = operation.hasException();
75723
75724                 if (hasException) {
75725                     me.hasException = true;
75726                     me.fireEvent('exception', me, operation);
75727                 } else {
75728                     me.fireEvent('operationcomplete', me, operation);
75729                 }
75730
75731                 if (hasException && me.pauseOnException) {
75732                     me.pause();
75733                 } else {
75734                     operation.setCompleted();
75735                     me.runNextOperation();
75736                 }
75737             };
75738
75739             operation.setStarted();
75740
75741             me.proxy[operation.action](operation, onProxyReturn, me);
75742         }
75743     }
75744 });
75745 /**
75746  * @author Ed Spencer
75747  * @class Ext.data.BelongsToAssociation
75748  * @extends Ext.data.Association
75749  *
75750  * Represents a many to one association with another model. The owner model is expected to have
75751  * a foreign key which references the primary key of the associated model:
75752  *
75753  *     Ext.define('Category', {
75754  *         extend: 'Ext.data.Model',
75755  *         fields: [
75756  *             { name: 'id',   type: 'int' },
75757  *             { name: 'name', type: 'string' }
75758  *         ]
75759  *     });
75760  *
75761  *     Ext.define('Product', {
75762  *         extend: 'Ext.data.Model',
75763  *         fields: [
75764  *             { name: 'id',          type: 'int' },
75765  *             { name: 'category_id', type: 'int' },
75766  *             { name: 'name',        type: 'string' }
75767  *         ],
75768  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
75769  *         associations: [
75770  *             { type: 'belongsTo', model: 'Category' }
75771  *         ]
75772  *     });
75773  *
75774  * In the example above we have created models for Products and Categories, and linked them together
75775  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
75776  * based on the Product's category_id, and provides new functions on the Product model:
75777  *
75778  * ## Generated getter function
75779  *
75780  * The first function that is added to the owner model is a getter function:
75781  *
75782  *     var product = new Product({
75783  *         id: 100,
75784  *         category_id: 20,
75785  *         name: 'Sneakers'
75786  *     });
75787  *
75788  *     product.getCategory(function(category, operation) {
75789  *         // do something with the category object
75790  *         alert(category.get('id')); // alerts 20
75791  *     }, this);
75792  *
75793  * The getCategory function was created on the Product model when we defined the association. This uses the
75794  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
75795  * callback when it has loaded.
75796  *
75797  * The new getCategory function will also accept an object containing success, failure and callback properties
75798  * - callback will always be called, success will only be called if the associated model was loaded successfully
75799  * and failure will only be called if the associatied model could not be loaded:
75800  *
75801  *     product.getCategory({
75802  *         callback: function(category, operation) {}, // a function that will always be called
75803  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
75804  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
75805  *         scope   : this // optionally pass in a scope object to execute the callbacks in
75806  *     });
75807  *
75808  * In each case above the callbacks are called with two arguments - the associated model instance and the
75809  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
75810  * useful when the instance could not be loaded.
75811  *
75812  * ## Generated setter function
75813  *
75814  * The second generated function sets the associated model instance - if only a single argument is passed to
75815  * the setter then the following two calls are identical:
75816  *
75817  *     // this call...
75818  *     product.setCategory(10);
75819  *
75820  *     // is equivalent to this call:
75821  *     product.set('category_id', 10);
75822  *
75823  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
75824  * the owner model's {@link Ext.data.Model#save save} method:
75825  *
75826  *     product.setCategory(10, function(product, operation) {
75827  *         // the product has been saved
75828  *         alert(product.get('category_id')); //now alerts 10
75829  *     });
75830  *
75831  *     //alternative syntax:
75832  *     product.setCategory(10, {
75833  *         callback: function(product, operation), // a function that will always be called
75834  *         success : function(product, operation), // a function that will only be called if the load succeeded
75835  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
75836  *         scope   : this //optionally pass in a scope object to execute the callbacks in
75837  *     })
75838  *
75839  * ## Customisation
75840  *
75841  * Associations reflect on the models they are linking to automatically set up properties such as the
75842  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
75843  *
75844  *     Ext.define('Product', {
75845  *         fields: [...],
75846  *
75847  *         associations: [
75848  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
75849  *         ]
75850  *     });
75851  *
75852  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
75853  * with our own settings. Usually this will not be needed.
75854  */
75855 Ext.define('Ext.data.BelongsToAssociation', {
75856     extend: 'Ext.data.Association',
75857
75858     alias: 'association.belongsto',
75859
75860     /**
75861      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
75862      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
75863      * model called Product would set up a product_id foreign key.
75864      *
75865      *     Ext.define('Order', {
75866      *         extend: 'Ext.data.Model',
75867      *         fields: ['id', 'date'],
75868      *         hasMany: 'Product'
75869      *     });
75870      *
75871      *     Ext.define('Product', {
75872      *         extend: 'Ext.data.Model',
75873      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
75874      *         belongsTo: 'Group'
75875      *     });
75876      *     var product = new Product({
75877      *         id: 1,
75878      *         name: 'Product 1',
75879      *         order_id: 22
75880      *     }, 1);
75881      *     product.getOrder(); // Will make a call to the server asking for order_id 22
75882      *
75883      */
75884
75885     /**
75886      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
75887      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
75888      */
75889
75890     /**
75891      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
75892      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
75893      */
75894
75895     /**
75896      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
75897      * Use 'belongsTo' to create a HasManyAssocation
75898      *
75899      *     associations: [{
75900      *         type: 'belongsTo',
75901      *         model: 'User'
75902      *     }]
75903      */
75904     constructor: function(config) {
75905         this.callParent(arguments);
75906
75907         var me             = this,
75908             ownerProto     = me.ownerModel.prototype,
75909             associatedName = me.associatedName,
75910             getterName     = me.getterName || 'get' + associatedName,
75911             setterName     = me.setterName || 'set' + associatedName;
75912
75913         Ext.applyIf(me, {
75914             name        : associatedName,
75915             foreignKey  : associatedName.toLowerCase() + "_id",
75916             instanceName: associatedName + 'BelongsToInstance',
75917             associationKey: associatedName.toLowerCase()
75918         });
75919
75920         ownerProto[getterName] = me.createGetter();
75921         ownerProto[setterName] = me.createSetter();
75922     },
75923
75924     /**
75925      * @private
75926      * Returns a setter function to be placed on the owner model's prototype
75927      * @return {Function} The setter function
75928      */
75929     createSetter: function() {
75930         var me              = this,
75931             ownerModel      = me.ownerModel,
75932             associatedModel = me.associatedModel,
75933             foreignKey      = me.foreignKey,
75934             primaryKey      = me.primaryKey;
75935
75936         //'this' refers to the Model instance inside this function
75937         return function(value, options, scope) {
75938             this.set(foreignKey, value);
75939
75940             if (typeof options == 'function') {
75941                 options = {
75942                     callback: options,
75943                     scope: scope || this
75944                 };
75945             }
75946
75947             if (Ext.isObject(options)) {
75948                 return this.save(options);
75949             }
75950         };
75951     },
75952
75953     /**
75954      * @private
75955      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
75956      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
75957      * @return {Function} The getter function
75958      */
75959     createGetter: function() {
75960         var me              = this,
75961             ownerModel      = me.ownerModel,
75962             associatedName  = me.associatedName,
75963             associatedModel = me.associatedModel,
75964             foreignKey      = me.foreignKey,
75965             primaryKey      = me.primaryKey,
75966             instanceName    = me.instanceName;
75967
75968         //'this' refers to the Model instance inside this function
75969         return function(options, scope) {
75970             options = options || {};
75971
75972             var model = this,
75973                 foreignKeyId = model.get(foreignKey),
75974                 instance,
75975                 args;
75976
75977             if (model[instanceName] === undefined) {
75978                 instance = Ext.ModelManager.create({}, associatedName);
75979                 instance.set(primaryKey, foreignKeyId);
75980
75981                 if (typeof options == 'function') {
75982                     options = {
75983                         callback: options,
75984                         scope: scope || model
75985                     };
75986                 }
75987
75988                 associatedModel.load(foreignKeyId, options);
75989                 model[instanceName] = associatedModel;
75990                 return associatedModel;
75991             } else {
75992                 instance = model[instanceName];
75993                 args = [instance];
75994                 scope = scope || model;
75995
75996                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
75997                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
75998                 //instead of the association layer.
75999                 Ext.callback(options, scope, args);
76000                 Ext.callback(options.success, scope, args);
76001                 Ext.callback(options.failure, scope, args);
76002                 Ext.callback(options.callback, scope, args);
76003
76004                 return instance;
76005             }
76006         };
76007     },
76008
76009     /**
76010      * Read associated data
76011      * @private
76012      * @param {Ext.data.Model} record The record we're writing to
76013      * @param {Ext.data.reader.Reader} reader The reader for the associated model
76014      * @param {Object} associationData The raw associated data
76015      */
76016     read: function(record, reader, associationData){
76017         record[this.instanceName] = reader.read([associationData]).records[0];
76018     }
76019 });
76020
76021 /**
76022  * @class Ext.data.BufferStore
76023  * @extends Ext.data.Store
76024  * @ignore
76025  */
76026 Ext.define('Ext.data.BufferStore', {
76027     extend: 'Ext.data.Store',
76028     alias: 'store.buffer',
76029     sortOnLoad: false,
76030     filterOnLoad: false,
76031     
76032     constructor: function() {
76033         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
76034     }
76035 });
76036 /**
76037  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
76038  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
76039  * error conditions, etc).
76040  *
76041  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
76042  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
76043  *
76044  * # Specification
76045  *
76046  * For additional information consult the [Ext.Direct Specification][1].
76047  *
76048  * # Providers
76049  *
76050  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
76051  * server. There are several providers that exist in the core at the moment:
76052  *
76053  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
76054  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
76055  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
76056  *
76057  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
76058  *
76059  * # Router
76060  *
76061  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
76062  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
76063  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
76064  *
76065  * # Server side events
76066  *
76067  * Custom events from the server may be handled by the client by adding listeners, for example:
76068  *
76069  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
76070  *
76071  *     // add a handler for a 'message' event sent by the server
76072  *     Ext.direct.Manager.on('message', function(e){
76073  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
76074  *         out.el.scrollTo('t', 100000, true);
76075  *     });
76076  *
76077  *    [1]: http://sencha.com/products/extjs/extdirect
76078  *
76079  * @singleton
76080  * @alternateClassName Ext.Direct
76081  */
76082 Ext.define('Ext.direct.Manager', {
76083
76084     /* Begin Definitions */
76085     singleton: true,
76086
76087     mixins: {
76088         observable: 'Ext.util.Observable'
76089     },
76090
76091     requires: ['Ext.util.MixedCollection'],
76092
76093     statics: {
76094         exceptions: {
76095             TRANSPORT: 'xhr',
76096             PARSE: 'parse',
76097             LOGIN: 'login',
76098             SERVER: 'exception'
76099         }
76100     },
76101
76102     /* End Definitions */
76103
76104     constructor: function(){
76105         var me = this;
76106
76107         me.addEvents(
76108             /**
76109              * @event event
76110              * Fires after an event.
76111              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
76112              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76113              */
76114             'event',
76115             /**
76116              * @event exception
76117              * Fires after an event exception.
76118              * @param {Ext.direct.Event} e The event type that occurred.
76119              */
76120             'exception'
76121         );
76122         me.transactions = Ext.create('Ext.util.MixedCollection');
76123         me.providers = Ext.create('Ext.util.MixedCollection');
76124
76125         me.mixins.observable.constructor.call(me);
76126     },
76127
76128     /**
76129      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
76130      * is not already connected, it will auto-connect.
76131      *
76132      *     var pollProv = new Ext.direct.PollingProvider({
76133      *         url: 'php/poll2.php'
76134      *     });
76135      *
76136      *     Ext.direct.Manager.addProvider({
76137      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
76138      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
76139      *         "actions":{              // each property within the actions object represents a Class
76140      *             "TestAction":[       // array of methods within each server side Class
76141      *             {
76142      *                 "name":"doEcho", // name of method
76143      *                 "len":1
76144      *             },{
76145      *                 "name":"multiply",
76146      *                 "len":1
76147      *             },{
76148      *                 "name":"doForm",
76149      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
76150      *                 "len":1
76151      *             }]
76152      *         },
76153      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
76154      *     },{
76155      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
76156      *         url:  'php/poll.php'
76157      *     }, pollProv); // reference to previously created instance
76158      *
76159      * @param {Ext.direct.Provider/Object...} provider
76160      * Accepts any number of Provider descriptions (an instance or config object for
76161      * a Provider). Each Provider description instructs Ext.Directhow to create
76162      * client-side stub methods.
76163      */
76164     addProvider : function(provider){
76165         var me = this,
76166             args = arguments,
76167             i = 0,
76168             len;
76169
76170         if (args.length > 1) {
76171             for (len = args.length; i < len; ++i) {
76172                 me.addProvider(args[i]);
76173             }
76174             return;
76175         }
76176
76177         // if provider has not already been instantiated
76178         if (!provider.isProvider) {
76179             provider = Ext.create('direct.' + provider.type + 'provider', provider);
76180         }
76181         me.providers.add(provider);
76182         provider.on('data', me.onProviderData, me);
76183
76184
76185         if (!provider.isConnected()) {
76186             provider.connect();
76187         }
76188
76189         return provider;
76190     },
76191
76192     /**
76193      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
76194      * provider is {@link #addProvider added}.
76195      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
76196      */
76197     getProvider : function(id){
76198         return id.isProvider ? id : this.providers.get(id);
76199     },
76200
76201     /**
76202      * Removes the provider.
76203      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
76204      * @return {Ext.direct.Provider} The provider, null if not found.
76205      */
76206     removeProvider : function(provider){
76207         var me = this,
76208             providers = me.providers;
76209
76210         provider = provider.isProvider ? provider : providers.get(provider);
76211
76212         if (provider) {
76213             provider.un('data', me.onProviderData, me);
76214             providers.remove(provider);
76215             return provider;
76216         }
76217         return null;
76218     },
76219
76220     /**
76221      * Adds a transaction to the manager.
76222      * @private
76223      * @param {Ext.direct.Transaction} transaction The transaction to add
76224      * @return {Ext.direct.Transaction} transaction
76225      */
76226     addTransaction: function(transaction){
76227         this.transactions.add(transaction);
76228         return transaction;
76229     },
76230
76231     /**
76232      * Removes a transaction from the manager.
76233      * @private
76234      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
76235      * @return {Ext.direct.Transaction} transaction
76236      */
76237     removeTransaction: function(transaction){
76238         transaction = this.getTransaction(transaction);
76239         this.transactions.remove(transaction);
76240         return transaction;
76241     },
76242
76243     /**
76244      * Gets a transaction
76245      * @private
76246      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
76247      * @return {Ext.direct.Transaction}
76248      */
76249     getTransaction: function(transaction){
76250         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
76251     },
76252
76253     onProviderData : function(provider, event){
76254         var me = this,
76255             i = 0,
76256             len;
76257
76258         if (Ext.isArray(event)) {
76259             for (len = event.length; i < len; ++i) {
76260                 me.onProviderData(provider, event[i]);
76261             }
76262             return;
76263         }
76264         if (event.name && event.name != 'event' && event.name != 'exception') {
76265             me.fireEvent(event.name, event);
76266         } else if (event.status === false) {
76267             me.fireEvent('exception', event);
76268         }
76269         me.fireEvent('event', event, provider);
76270     }
76271 }, function(){
76272     // Backwards compatibility
76273     Ext.Direct = Ext.direct.Manager;
76274 });
76275
76276 /**
76277  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
76278  * request is made, the transport mechanism is handed off to the appropriate
76279  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
76280  *
76281  * # Specifying the function
76282  *
76283  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
76284  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
76285  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
76286  * allows you to specify a different remoting method for each CRUD action.
76287  *
76288  * # Parameters
76289  *
76290  * This proxy provides options to help configure which parameters will be sent to the server.
76291  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
76292  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
76293  * the remoting method parameters are passed.
76294  *
76295  * # Example Usage
76296  *
76297  *     Ext.define('User', {
76298  *         extend: 'Ext.data.Model',
76299  *         fields: ['firstName', 'lastName'],
76300  *         proxy: {
76301  *             type: 'direct',
76302  *             directFn: MyApp.getUsers,
76303  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
76304  *         }
76305  *     });
76306  *     User.load(1);
76307  */
76308 Ext.define('Ext.data.proxy.Direct', {
76309     /* Begin Definitions */
76310
76311     extend: 'Ext.data.proxy.Server',
76312     alternateClassName: 'Ext.data.DirectProxy',
76313
76314     alias: 'proxy.direct',
76315
76316     requires: ['Ext.direct.Manager'],
76317
76318     /* End Definitions */
76319
76320     /**
76321      * @cfg {String/String[]} paramOrder
76322      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
76323      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
76324      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
76325      * acceptable:
76326      *
76327      *     paramOrder: ['param1','param2','param3']
76328      *     paramOrder: 'param1 param2 param3'
76329      *     paramOrder: 'param1,param2,param3'
76330      *     paramOrder: 'param1|param2|param'
76331      */
76332     paramOrder: undefined,
76333
76334     /**
76335      * @cfg {Boolean} paramsAsHash
76336      * Send parameters as a collection of named arguments.
76337      * Providing a {@link #paramOrder} nullifies this configuration.
76338      */
76339     paramsAsHash: true,
76340
76341     /**
76342      * @cfg {Function} directFn
76343      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
76344      * for Store's which will not implement a full CRUD api.
76345      */
76346     directFn : undefined,
76347
76348     /**
76349      * @cfg {Object} api
76350      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
76351      * function call.
76352      */
76353
76354     /**
76355      * @cfg {Object} extraParams
76356      * Extra parameters that will be included on every read request. Individual requests with params
76357      * of the same name will override these params when they are in conflict.
76358      */
76359
76360     // private
76361     paramOrderRe: /[\s,|]/,
76362
76363     constructor: function(config){
76364         var me = this;
76365
76366         Ext.apply(me, config);
76367         if (Ext.isString(me.paramOrder)) {
76368             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
76369         }
76370         me.callParent(arguments);
76371     },
76372
76373     doRequest: function(operation, callback, scope) {
76374         var me = this,
76375             writer = me.getWriter(),
76376             request = me.buildRequest(operation, callback, scope),
76377             fn = me.api[request.action]  || me.directFn,
76378             args = [],
76379             params = request.params,
76380             paramOrder = me.paramOrder,
76381             method,
76382             i = 0,
76383             len;
76384
76385
76386         if (operation.allowWrite()) {
76387             request = writer.write(request);
76388         }
76389
76390         if (operation.action == 'read') {
76391             // We need to pass params
76392             method = fn.directCfg.method;
76393
76394             if (method.ordered) {
76395                 if (method.len > 0) {
76396                     if (paramOrder) {
76397                         for (len = paramOrder.length; i < len; ++i) {
76398                             args.push(params[paramOrder[i]]);
76399                         }
76400                     } else if (me.paramsAsHash) {
76401                         args.push(params);
76402                     }
76403                 }
76404             } else {
76405                 args.push(params);
76406             }
76407         } else {
76408             args.push(request.jsonData);
76409         }
76410
76411         Ext.apply(request, {
76412             args: args,
76413             directFn: fn
76414         });
76415         args.push(me.createRequestCallback(request, operation, callback, scope), me);
76416         fn.apply(window, args);
76417     },
76418
76419     /*
76420      * Inherit docs. We don't apply any encoding here because
76421      * all of the direct requests go out as jsonData
76422      */
76423     applyEncoding: function(value){
76424         return value;
76425     },
76426
76427     createRequestCallback: function(request, operation, callback, scope){
76428         var me = this;
76429
76430         return function(data, event){
76431             me.processResponse(event.status, operation, request, event, callback, scope);
76432         };
76433     },
76434
76435     // inherit docs
76436     extractResponseData: function(response){
76437         return Ext.isDefined(response.result) ? response.result : response.data;
76438     },
76439
76440     // inherit docs
76441     setException: function(operation, response) {
76442         operation.setException(response.message);
76443     },
76444
76445     // inherit docs
76446     buildUrl: function(){
76447         return '';
76448     }
76449 });
76450
76451 /**
76452  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
76453  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
76454  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
76455  * {@link Ext.data.Store} configured as needed.
76456  *
76457  * **Note:** Although they are not listed, this class inherits all of the config options of:
76458  *
76459  * - **{@link Ext.data.Store Store}**
76460  *
76461  * - **{@link Ext.data.reader.Json JsonReader}**
76462  *
76463  *   - **{@link Ext.data.reader.Json#root root}**
76464  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
76465  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
76466  *
76467  * - **{@link Ext.data.proxy.Direct DirectProxy}**
76468  *
76469  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
76470  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
76471  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
76472  *
76473  */
76474 Ext.define('Ext.data.DirectStore', {
76475     /* Begin Definitions */
76476     
76477     extend: 'Ext.data.Store',
76478     
76479     alias: 'store.direct',
76480     
76481     requires: ['Ext.data.proxy.Direct'],
76482    
76483     /* End Definitions */
76484
76485     constructor : function(config){
76486         config = Ext.apply({}, config);
76487         if (!config.proxy) {
76488             var proxy = {
76489                 type: 'direct',
76490                 reader: {
76491                     type: 'json'
76492                 }
76493             };
76494             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
76495             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
76496             config.proxy = proxy;
76497         }
76498         this.callParent([config]);
76499     }    
76500 });
76501
76502 /**
76503  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
76504  * {@link #ordinalize ordinalizes} words. Sample usage:
76505  *
76506  *     //turning singular words into plurals
76507  *     Ext.util.Inflector.pluralize('word'); //'words'
76508  *     Ext.util.Inflector.pluralize('person'); //'people'
76509  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
76510  *
76511  *     //turning plurals into singulars
76512  *     Ext.util.Inflector.singularize('words'); //'word'
76513  *     Ext.util.Inflector.singularize('people'); //'person'
76514  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
76515  *
76516  *     //ordinalizing numbers
76517  *     Ext.util.Inflector.ordinalize(11); //"11th"
76518  *     Ext.util.Inflector.ordinalize(21); //"21th"
76519  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
76520  *
76521  * # Customization
76522  *
76523  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
76524  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
76525  * Here is how we might add a rule that pluralizes "ox" to "oxen":
76526  *
76527  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
76528  *
76529  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
76530  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
76531  * how we could add the inverse rule:
76532  *
76533  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
76534  *
76535  * Note that the ox/oxen rules are present by default.
76536  */
76537 Ext.define('Ext.util.Inflector', {
76538
76539     /* Begin Definitions */
76540
76541     singleton: true,
76542
76543     /* End Definitions */
76544
76545     /**
76546      * @private
76547      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
76548      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
76549      * part of the regular expression. This is managed by the {@link #plural} method.
76550      * @property {Array} plurals
76551      */
76552     plurals: [
76553         [(/(quiz)$/i),                "$1zes"  ],
76554         [(/^(ox)$/i),                 "$1en"   ],
76555         [(/([m|l])ouse$/i),           "$1ice"  ],
76556         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
76557         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
76558         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
76559         [(/(hive)$/i),                "$1s"    ],
76560         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
76561         [(/sis$/i),                   "ses"    ],
76562         [(/([ti])um$/i),              "$1a"    ],
76563         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
76564         [(/(bu)s$/i),                 "$1ses"  ],
76565         [(/(alias|status|sex)$/i),    "$1es"   ],
76566         [(/(octop|vir)us$/i),         "$1i"    ],
76567         [(/(ax|test)is$/i),           "$1es"   ],
76568         [(/^person$/),                "people" ],
76569         [(/^man$/),                   "men"    ],
76570         [(/^(child)$/),               "$1ren"  ],
76571         [(/s$/i),                     "s"      ],
76572         [(/$/),                       "s"      ]
76573     ],
76574
76575     /**
76576      * @private
76577      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
76578      * regular expression that matches the plural form of a word, the second must be a String that replaces the
76579      * matched part of the regular expression. This is managed by the {@link #singular} method.
76580      * @property {Array} singulars
76581      */
76582     singulars: [
76583       [(/(quiz)zes$/i),                                                    "$1"     ],
76584       [(/(matr)ices$/i),                                                   "$1ix"   ],
76585       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
76586       [(/^(ox)en/i),                                                       "$1"     ],
76587       [(/(alias|status)es$/i),                                             "$1"     ],
76588       [(/(octop|vir)i$/i),                                                 "$1us"   ],
76589       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
76590       [(/(shoe)s$/i),                                                      "$1"     ],
76591       [(/(o)es$/i),                                                        "$1"     ],
76592       [(/(bus)es$/i),                                                      "$1"     ],
76593       [(/([m|l])ice$/i),                                                   "$1ouse" ],
76594       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
76595       [(/(m)ovies$/i),                                                     "$1ovie" ],
76596       [(/(s)eries$/i),                                                     "$1eries"],
76597       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
76598       [(/([lr])ves$/i),                                                    "$1f"    ],
76599       [(/(tive)s$/i),                                                      "$1"     ],
76600       [(/(hive)s$/i),                                                      "$1"     ],
76601       [(/([^f])ves$/i),                                                    "$1fe"   ],
76602       [(/(^analy)ses$/i),                                                  "$1sis"  ],
76603       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
76604       [(/([ti])a$/i),                                                      "$1um"   ],
76605       [(/(n)ews$/i),                                                       "$1ews"  ],
76606       [(/people$/i),                                                       "person" ],
76607       [(/s$/i),                                                            ""       ]
76608     ],
76609
76610     /**
76611      * @private
76612      * The registered uncountable words
76613      * @property {String[]} uncountable
76614      */
76615      uncountable: [
76616         "sheep",
76617         "fish",
76618         "series",
76619         "species",
76620         "money",
76621         "rice",
76622         "information",
76623         "equipment",
76624         "grass",
76625         "mud",
76626         "offspring",
76627         "deer",
76628         "means"
76629     ],
76630
76631     /**
76632      * Adds a new singularization rule to the Inflector. See the intro docs for more information
76633      * @param {RegExp} matcher The matcher regex
76634      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76635      */
76636     singular: function(matcher, replacer) {
76637         this.singulars.unshift([matcher, replacer]);
76638     },
76639
76640     /**
76641      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
76642      * @param {RegExp} matcher The matcher regex
76643      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76644      */
76645     plural: function(matcher, replacer) {
76646         this.plurals.unshift([matcher, replacer]);
76647     },
76648
76649     /**
76650      * Removes all registered singularization rules
76651      */
76652     clearSingulars: function() {
76653         this.singulars = [];
76654     },
76655
76656     /**
76657      * Removes all registered pluralization rules
76658      */
76659     clearPlurals: function() {
76660         this.plurals = [];
76661     },
76662
76663     /**
76664      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
76665      * @param {String} word The word to test
76666      * @return {Boolean} True if the word is transnumeral
76667      */
76668     isTransnumeral: function(word) {
76669         return Ext.Array.indexOf(this.uncountable, word) != -1;
76670     },
76671
76672     /**
76673      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
76674      * @param {String} word The word to pluralize
76675      * @return {String} The pluralized form of the word
76676      */
76677     pluralize: function(word) {
76678         if (this.isTransnumeral(word)) {
76679             return word;
76680         }
76681
76682         var plurals = this.plurals,
76683             length  = plurals.length,
76684             tuple, regex, i;
76685
76686         for (i = 0; i < length; i++) {
76687             tuple = plurals[i];
76688             regex = tuple[0];
76689
76690             if (regex == word || (regex.test && regex.test(word))) {
76691                 return word.replace(regex, tuple[1]);
76692             }
76693         }
76694
76695         return word;
76696     },
76697
76698     /**
76699      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
76700      * @param {String} word The word to singularize
76701      * @return {String} The singularized form of the word
76702      */
76703     singularize: function(word) {
76704         if (this.isTransnumeral(word)) {
76705             return word;
76706         }
76707
76708         var singulars = this.singulars,
76709             length    = singulars.length,
76710             tuple, regex, i;
76711
76712         for (i = 0; i < length; i++) {
76713             tuple = singulars[i];
76714             regex = tuple[0];
76715
76716             if (regex == word || (regex.test && regex.test(word))) {
76717                 return word.replace(regex, tuple[1]);
76718             }
76719         }
76720
76721         return word;
76722     },
76723
76724     /**
76725      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
76726      * package
76727      * @param {String} word The word to classify
76728      * @return {String} The classified version of the word
76729      */
76730     classify: function(word) {
76731         return Ext.String.capitalize(this.singularize(word));
76732     },
76733
76734     /**
76735      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
76736      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
76737      * @param {Number} number The number to ordinalize
76738      * @return {String} The ordinalized number
76739      */
76740     ordinalize: function(number) {
76741         var parsed = parseInt(number, 10),
76742             mod10  = parsed % 10,
76743             mod100 = parsed % 100;
76744
76745         //11 through 13 are a special case
76746         if (11 <= mod100 && mod100 <= 13) {
76747             return number + "th";
76748         } else {
76749             switch(mod10) {
76750                 case 1 : return number + "st";
76751                 case 2 : return number + "nd";
76752                 case 3 : return number + "rd";
76753                 default: return number + "th";
76754             }
76755         }
76756     }
76757 }, function() {
76758     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
76759     var irregulars = {
76760             alumnus: 'alumni',
76761             cactus : 'cacti',
76762             focus  : 'foci',
76763             nucleus: 'nuclei',
76764             radius: 'radii',
76765             stimulus: 'stimuli',
76766             ellipsis: 'ellipses',
76767             paralysis: 'paralyses',
76768             oasis: 'oases',
76769             appendix: 'appendices',
76770             index: 'indexes',
76771             beau: 'beaux',
76772             bureau: 'bureaux',
76773             tableau: 'tableaux',
76774             woman: 'women',
76775             child: 'children',
76776             man: 'men',
76777             corpus:     'corpora',
76778             criterion: 'criteria',
76779             curriculum: 'curricula',
76780             genus: 'genera',
76781             memorandum: 'memoranda',
76782             phenomenon: 'phenomena',
76783             foot: 'feet',
76784             goose: 'geese',
76785             tooth: 'teeth',
76786             antenna: 'antennae',
76787             formula: 'formulae',
76788             nebula: 'nebulae',
76789             vertebra: 'vertebrae',
76790             vita: 'vitae'
76791         },
76792         singular;
76793
76794     for (singular in irregulars) {
76795         this.plural(singular, irregulars[singular]);
76796         this.singular(irregulars[singular], singular);
76797     }
76798 });
76799 /**
76800  * @author Ed Spencer
76801  * @class Ext.data.HasManyAssociation
76802  * @extends Ext.data.Association
76803  * 
76804  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
76805  * 
76806 <pre><code>
76807 Ext.define('Product', {
76808     extend: 'Ext.data.Model',
76809     fields: [
76810         {name: 'id',      type: 'int'},
76811         {name: 'user_id', type: 'int'},
76812         {name: 'name',    type: 'string'}
76813     ]
76814 });
76815
76816 Ext.define('User', {
76817     extend: 'Ext.data.Model',
76818     fields: [
76819         {name: 'id',   type: 'int'},
76820         {name: 'name', type: 'string'}
76821     ],
76822     // we can use the hasMany shortcut on the model to create a hasMany association
76823     hasMany: {model: 'Product', name: 'products'}
76824 });
76825 </pre></code>
76826
76827  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
76828  * us a new function on every User instance, in this case the function is called 'products' because that is the name
76829  * we specified in the association configuration above.</p>
76830  * 
76831  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
76832  * only Products for the given model instance:</p>
76833  * 
76834 <pre><code>
76835 //first, we load up a User with id of 1
76836 var user = Ext.create('User', {id: 1, name: 'Ed'});
76837
76838 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
76839 //the created store is automatically scoped to the set of Products for the User with id of 1
76840 var products = user.products();
76841
76842 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
76843 products.add({
76844     name: 'Another Product'
76845 });
76846
76847 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
76848 products.sync();
76849 </code></pre>
76850  * 
76851  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
76852  * though calling products() a second time returns the same store instance.</p>
76853  * 
76854  * <p><u>Custom filtering</u></p>
76855  * 
76856  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
76857  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
76858  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
76859  * 
76860  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
76861  * have models for Search and Tweet:</p>
76862  * 
76863 <pre><code>
76864 Ext.define('Search', {
76865     extend: 'Ext.data.Model',
76866     fields: [
76867         'id', 'query'
76868     ],
76869
76870     hasMany: {
76871         model: 'Tweet',
76872         name : 'tweets',
76873         filterProperty: 'query'
76874     }
76875 });
76876
76877 Ext.define('Tweet', {
76878     extend: 'Ext.data.Model',
76879     fields: [
76880         'id', 'text', 'from_user'
76881     ]
76882 });
76883
76884 //returns a Store filtered by the filterProperty
76885 var store = new Search({query: 'Sencha Touch'}).tweets();
76886 </code></pre>
76887  * 
76888  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
76889  * equivalent to this:</p>
76890  * 
76891 <pre><code>
76892 var store = Ext.create('Ext.data.Store', {
76893     model: 'Tweet',
76894     filters: [
76895         {
76896             property: 'query',
76897             value   : 'Sencha Touch'
76898         }
76899     ]
76900 });
76901 </code></pre>
76902  */
76903 Ext.define('Ext.data.HasManyAssociation', {
76904     extend: 'Ext.data.Association',
76905     requires: ['Ext.util.Inflector'],
76906
76907     alias: 'association.hasmany',
76908
76909     /**
76910      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
76911      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
76912      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
76913      * the store is automatically filtered so that only records with a matching foreign key are included in the 
76914      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
76915      * <pre><code>
76916 Ext.define('Group', {
76917     extend: 'Ext.data.Model',
76918     fields: ['id', 'name'],
76919     hasMany: 'User'
76920 });
76921
76922 Ext.define('User', {
76923     extend: 'Ext.data.Model',
76924     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
76925     belongsTo: 'Group'
76926 });
76927      * </code></pre>
76928      */
76929     
76930     /**
76931      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
76932      * If not specified, the pluralized name of the child model is used.
76933      * <pre><code>
76934 // This will create a users() method on any Group model instance
76935 Ext.define('Group', {
76936     extend: 'Ext.data.Model',
76937     fields: ['id', 'name'],
76938     hasMany: 'User'
76939 });
76940 var group = new Group();
76941 console.log(group.users());
76942
76943 // The method to retrieve the users will now be getUserList
76944 Ext.define('Group', {
76945     extend: 'Ext.data.Model',
76946     fields: ['id', 'name'],
76947     hasMany: {model: 'User', name: 'getUserList'}
76948 });
76949 var group = new Group();
76950 console.log(group.getUserList());
76951      * </code></pre>
76952      */
76953     
76954     /**
76955      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
76956      * undefined.
76957      */
76958     
76959     /**
76960      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
76961      * this is not set, a filter is automatically created which filters the association based on the configured 
76962      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
76963      */
76964     
76965     /**
76966      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
76967      * Defaults to <tt>false</tt>.
76968      */
76969     
76970     /**
76971      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
76972      * Use 'hasMany' to create a HasManyAssocation
76973      * <pre><code>
76974 associations: [{
76975     type: 'hasMany',
76976     model: 'User'
76977 }]
76978      * </code></pre>
76979      */
76980     
76981     constructor: function(config) {
76982         var me = this,
76983             ownerProto,
76984             name;
76985             
76986         me.callParent(arguments);
76987         
76988         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
76989         
76990         ownerProto = me.ownerModel.prototype;
76991         name = me.name;
76992         
76993         Ext.applyIf(me, {
76994             storeName : name + "Store",
76995             foreignKey: me.ownerName.toLowerCase() + "_id"
76996         });
76997         
76998         ownerProto[name] = me.createStore();
76999     },
77000     
77001     /**
77002      * @private
77003      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
77004      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
77005      * returns a Store configured to return the filtered set of a single Group's Users.
77006      * @return {Function} The store-generating function
77007      */
77008     createStore: function() {
77009         var that            = this,
77010             associatedModel = that.associatedModel,
77011             storeName       = that.storeName,
77012             foreignKey      = that.foreignKey,
77013             primaryKey      = that.primaryKey,
77014             filterProperty  = that.filterProperty,
77015             autoLoad        = that.autoLoad,
77016             storeConfig     = that.storeConfig || {};
77017         
77018         return function() {
77019             var me = this,
77020                 config, filter,
77021                 modelDefaults = {};
77022                 
77023             if (me[storeName] === undefined) {
77024                 if (filterProperty) {
77025                     filter = {
77026                         property  : filterProperty,
77027                         value     : me.get(filterProperty),
77028                         exactMatch: true
77029                     };
77030                 } else {
77031                     filter = {
77032                         property  : foreignKey,
77033                         value     : me.get(primaryKey),
77034                         exactMatch: true
77035                     };
77036                 }
77037                 
77038                 modelDefaults[foreignKey] = me.get(primaryKey);
77039                 
77040                 config = Ext.apply({}, storeConfig, {
77041                     model        : associatedModel,
77042                     filters      : [filter],
77043                     remoteFilter : false,
77044                     modelDefaults: modelDefaults
77045                 });
77046                 
77047                 me[storeName] = Ext.create('Ext.data.Store', config);
77048                 if (autoLoad) {
77049                     me[storeName].load();
77050                 }
77051             }
77052             
77053             return me[storeName];
77054         };
77055     },
77056     
77057     /**
77058      * Read associated data
77059      * @private
77060      * @param {Ext.data.Model} record The record we're writing to
77061      * @param {Ext.data.reader.Reader} reader The reader for the associated model
77062      * @param {Object} associationData The raw associated data
77063      */
77064     read: function(record, reader, associationData){
77065         var store = record[this.name](),
77066             inverse;
77067     
77068         store.add(reader.read(associationData).records);
77069     
77070         //now that we've added the related records to the hasMany association, set the inverse belongsTo
77071         //association on each of them if it exists
77072         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
77073             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
77074         });
77075     
77076         //if the inverse association was found, set it now on each record we've just created
77077         if (inverse) {
77078             store.data.each(function(associatedRecord){
77079                 associatedRecord[inverse.instanceName] = record;
77080             });
77081         }
77082     }
77083 });
77084 /**
77085  * @class Ext.data.JsonP
77086  * @singleton
77087  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
77088  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
77089  */
77090 Ext.define('Ext.data.JsonP', {
77091
77092     /* Begin Definitions */
77093
77094     singleton: true,
77095
77096     statics: {
77097         requestCount: 0,
77098         requests: {}
77099     },
77100
77101     /* End Definitions */
77102
77103     /**
77104      * @property timeout
77105      * @type Number
77106      * A default timeout for any JsonP requests. If the request has not completed in this time the
77107      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
77108      */
77109     timeout: 30000,
77110
77111     /**
77112      * @property disableCaching
77113      * @type Boolean
77114      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
77115      */
77116     disableCaching: true,
77117
77118     /**
77119      * @property disableCachingParam
77120      * @type String
77121      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
77122      */
77123     disableCachingParam: '_dc',
77124
77125     /**
77126      * @property callbackKey
77127      * @type String
77128      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
77129      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
77130      * url?callback=Ext.data.JsonP.callback1
77131      */
77132     callbackKey: 'callback',
77133
77134     /**
77135      * Makes a JSONP request.
77136      * @param {Object} options An object which may contain the following properties. Note that options will
77137      * take priority over any defaults that are specified in the class.
77138      * <ul>
77139      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
77140      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
77141      * key value pairs that will be sent along with the request.</div></li>
77142      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
77143      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
77144      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77145      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77146      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
77147      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
77148      * in each JsonP request that you make.</div></li>
77149      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
77150      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
77151      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
77152      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
77153      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
77154      * completes, whether it is a success or failure.</div></li>
77155      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
77156      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
77157      * </ul>
77158      * @return {Object} request An object containing the request details.
77159      */
77160     request: function(options){
77161         options = Ext.apply({}, options);
77162
77163
77164         var me = this,
77165             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
77166             cacheParam = options.disableCachingParam || me.disableCachingParam,
77167             id = ++me.statics().requestCount,
77168             callbackName = options.callbackName || 'callback' + id,
77169             callbackKey = options.callbackKey || me.callbackKey,
77170             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
77171             params = Ext.apply({}, options.params),
77172             url = options.url,
77173             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
77174             request,
77175             script;
77176
77177         params[callbackKey] = name + '.data.JsonP.' + callbackName;
77178         if (disableCaching) {
77179             params[cacheParam] = new Date().getTime();
77180         }
77181
77182         script = me.createScript(url, params);
77183
77184         me.statics().requests[id] = request = {
77185             url: url,
77186             params: params,
77187             script: script,
77188             id: id,
77189             scope: options.scope,
77190             success: options.success,
77191             failure: options.failure,
77192             callback: options.callback,
77193             callbackName: callbackName
77194         };
77195
77196         if (timeout > 0) {
77197             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
77198         }
77199
77200         me.setupErrorHandling(request);
77201         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
77202         Ext.getHead().appendChild(script);
77203         return request;
77204     },
77205
77206     /**
77207      * Abort a request. If the request parameter is not specified all open requests will
77208      * be aborted.
77209      * @param {Object/String} request (Optional) The request to abort
77210      */
77211     abort: function(request){
77212         var requests = this.statics().requests,
77213             key;
77214
77215         if (request) {
77216             if (!request.id) {
77217                 request = requests[request];
77218             }
77219             this.abort(request);
77220         } else {
77221             for (key in requests) {
77222                 if (requests.hasOwnProperty(key)) {
77223                     this.abort(requests[key]);
77224                 }
77225             }
77226         }
77227     },
77228
77229     /**
77230      * Sets up error handling for the script
77231      * @private
77232      * @param {Object} request The request
77233      */
77234     setupErrorHandling: function(request){
77235         request.script.onerror = Ext.bind(this.handleError, this, [request]);
77236     },
77237
77238     /**
77239      * Handles any aborts when loading the script
77240      * @private
77241      * @param {Object} request The request
77242      */
77243     handleAbort: function(request){
77244         request.errorType = 'abort';
77245         this.handleResponse(null, request);
77246     },
77247
77248     /**
77249      * Handles any script errors when loading the script
77250      * @private
77251      * @param {Object} request The request
77252      */
77253     handleError: function(request){
77254         request.errorType = 'error';
77255         this.handleResponse(null, request);
77256     },
77257
77258     /**
77259      * Cleans up anu script handling errors
77260      * @private
77261      * @param {Object} request The request
77262      */
77263     cleanupErrorHandling: function(request){
77264         request.script.onerror = null;
77265     },
77266
77267     /**
77268      * Handle any script timeouts
77269      * @private
77270      * @param {Object} request The request
77271      */
77272     handleTimeout: function(request){
77273         request.errorType = 'timeout';
77274         this.handleResponse(null, request);
77275     },
77276
77277     /**
77278      * Handle a successful response
77279      * @private
77280      * @param {Object} result The result from the request
77281      * @param {Object} request The request
77282      */
77283     handleResponse: function(result, request){
77284
77285         var success = true;
77286
77287         if (request.timeout) {
77288             clearTimeout(request.timeout);
77289         }
77290         delete this[request.callbackName];
77291         delete this.statics()[request.id];
77292         this.cleanupErrorHandling(request);
77293         Ext.fly(request.script).remove();
77294
77295         if (request.errorType) {
77296             success = false;
77297             Ext.callback(request.failure, request.scope, [request.errorType]);
77298         } else {
77299             Ext.callback(request.success, request.scope, [result]);
77300         }
77301         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
77302     },
77303
77304     /**
77305      * Create the script tag
77306      * @private
77307      * @param {String} url The url of the request
77308      * @param {Object} params Any extra params to be sent
77309      */
77310     createScript: function(url, params) {
77311         var script = document.createElement('script');
77312         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
77313         script.setAttribute("async", true);
77314         script.setAttribute("type", "text/javascript");
77315         return script;
77316     }
77317 });
77318
77319 /**
77320  * @class Ext.data.JsonPStore
77321  * @extends Ext.data.Store
77322  * @private
77323  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
77324  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
77325  * <p>A store configuration would be something like:<pre><code>
77326 var store = new Ext.data.JsonPStore({
77327     // store configs
77328     autoDestroy: true,
77329     storeId: 'myStore',
77330
77331     // proxy configs
77332     url: 'get-images.php',
77333
77334     // reader configs
77335     root: 'images',
77336     idProperty: 'name',
77337     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
77338 });
77339  * </code></pre></p>
77340  * <p>This store is configured to consume a returned object of the form:<pre><code>
77341 stcCallback({
77342     images: [
77343         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
77344         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
77345     ]
77346 })
77347  * </code></pre>
77348  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
77349  * for details of how this works.</p>
77350  * An object literal of this form could also be used as the {@link #data} config option.</p>
77351  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
77352  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
77353  * @xtype jsonpstore
77354  */
77355 Ext.define('Ext.data.JsonPStore', {
77356     extend: 'Ext.data.Store',
77357     alias : 'store.jsonp',
77358
77359     /**
77360      * @cfg {Ext.data.DataReader} reader @hide
77361      */
77362     constructor: function(config) {
77363         this.callParent(Ext.apply(config, {
77364             reader: Ext.create('Ext.data.reader.Json', config),
77365             proxy : Ext.create('Ext.data.proxy.JsonP', config)
77366         }));
77367     }
77368 });
77369
77370 /**
77371  * This class is used as a set of methods that are applied to the prototype of a
77372  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
77373  * will have all of the tree related methods available on the model. In general this class will
77374  * not be used directly by the developer. This class also creates extra fields on the model if
77375  * they do not exist, to help maintain the tree state and UI. These fields are documented as
77376  * config options.
77377  */
77378 Ext.define('Ext.data.NodeInterface', {
77379     requires: ['Ext.data.Field'],
77380
77381     /**
77382      * @cfg {String} parentId
77383      * ID of parent node.
77384      */
77385
77386     /**
77387      * @cfg {Number} index
77388      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
77389      * index will be 2.
77390      */
77391
77392     /**
77393      * @cfg {Number} depth
77394      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
77395      */
77396
77397     /**
77398      * @cfg {Boolean} [expanded=false]
77399      * True if the node is expanded.
77400      */
77401
77402     /**
77403      * @cfg {Boolean} [expandable=false]
77404      * Set to true to allow for expanding/collapsing of this node.
77405      */
77406
77407     /**
77408      * @cfg {Boolean} [checked=null]
77409      * Set to true or false to show a checkbox alongside this node.
77410      */
77411
77412     /**
77413      * @cfg {Boolean} [leaf=false]
77414      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
77415      * rendered for this node.
77416      */
77417
77418     /**
77419      * @cfg {String} cls
77420      * CSS class to apply for this node.
77421      */
77422
77423     /**
77424      * @cfg {String} iconCls
77425      * CSS class to apply for this node's icon.
77426      */
77427
77428     /**
77429      * @cfg {String} icon
77430      * URL for this node's icon.
77431      */
77432
77433     /**
77434      * @cfg {Boolean} root
77435      * True if this is the root node.
77436      */
77437
77438     /**
77439      * @cfg {Boolean} isLast
77440      * True if this is the last node.
77441      */
77442
77443     /**
77444      * @cfg {Boolean} isFirst
77445      * True if this is the first node.
77446      */
77447
77448     /**
77449      * @cfg {Boolean} [allowDrop=true]
77450      * Set to false to deny dropping on this node.
77451      */
77452
77453     /**
77454      * @cfg {Boolean} [allowDrag=true]
77455      * Set to false to deny dragging of this node.
77456      */
77457
77458     /**
77459      * @cfg {Boolean} [loaded=false]
77460      * True if the node has finished loading.
77461      */
77462
77463     /**
77464      * @cfg {Boolean} [loading=false]
77465      * True if the node is currently loading.
77466      */
77467
77468     /**
77469      * @cfg {String} href
77470      * An URL for a link that's created when this config is specified.
77471      */
77472
77473     /**
77474      * @cfg {String} hrefTarget
77475      * Target for link. Only applicable when {@link #href} also specified.
77476      */
77477
77478     /**
77479      * @cfg {String} qtip
77480      * Tooltip text to show on this node.
77481      */
77482
77483     /**
77484      * @cfg {String} qtitle
77485      * Tooltip title.
77486      */
77487
77488     /**
77489      * @cfg {String} text
77490      * The text for to show on node label.
77491      */
77492
77493     /**
77494      * @cfg {Ext.data.NodeInterface[]} children
77495      * Array of child nodes.
77496      */
77497
77498
77499     /**
77500      * @property nextSibling
77501      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
77502      */
77503
77504     /**
77505      * @property previousSibling
77506      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
77507      */
77508
77509     /**
77510      * @property parentNode
77511      * A reference to this node's parent node. `null` if this node is the root node.
77512      */
77513
77514     /**
77515      * @property lastChild
77516      * A reference to this node's last child node. `null` if this node has no children.
77517      */
77518
77519     /**
77520      * @property firstChild
77521      * A reference to this node's first child node. `null` if this node has no children.
77522      */
77523
77524     /**
77525      * @property childNodes
77526      * An array of this nodes children.  Array will be empty if this node has no chidren.
77527      */
77528
77529     statics: {
77530         /**
77531          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
77532          * This adds a set of methods, new events, new properties and new fields on every Record
77533          * with the same Model as the passed Record.
77534          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
77535          * @static
77536          */
77537         decorate: function(record) {
77538             if (!record.isNode) {
77539                 // Apply the methods and fields to the prototype
77540                 // @TODO: clean this up to use proper class system stuff
77541                 var mgr = Ext.ModelManager,
77542                     modelName = record.modelName,
77543                     modelClass = mgr.getModel(modelName),
77544                     idName = modelClass.prototype.idProperty,
77545                     newFields = [],
77546                     i, newField, len;
77547
77548                 // Start by adding the NodeInterface methods to the Model's prototype
77549                 modelClass.override(this.getPrototypeBody());
77550                 newFields = this.applyFields(modelClass, [
77551                     {name: idName,       type: 'string',  defaultValue: null},
77552                     {name: 'parentId',   type: 'string',  defaultValue: null},
77553                     {name: 'index',      type: 'int',     defaultValue: null},
77554                     {name: 'depth',      type: 'int',     defaultValue: 0},
77555                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
77556                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
77557                     {name: 'checked',    type: 'auto',    defaultValue: null},
77558                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
77559                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
77560                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
77561                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
77562                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
77563                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
77564                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
77565                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
77566                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
77567                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
77568                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
77569                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
77570                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
77571                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
77572                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
77573                 ]);
77574
77575                 len = newFields.length;
77576                 // Set default values
77577                 for (i = 0; i < len; ++i) {
77578                     newField = newFields[i];
77579                     if (record.get(newField.name) === undefined) {
77580                         record.data[newField.name] = newField.defaultValue;
77581                     }
77582                 }
77583             }
77584
77585             Ext.applyIf(record, {
77586                 firstChild: null,
77587                 lastChild: null,
77588                 parentNode: null,
77589                 previousSibling: null,
77590                 nextSibling: null,
77591                 childNodes: []
77592             });
77593             // Commit any fields so the record doesn't show as dirty initially
77594             record.commit(true);
77595
77596             record.enableBubble([
77597                 /**
77598                  * @event append
77599                  * Fires when a new child node is appended
77600                  * @param {Ext.data.NodeInterface} this This node
77601                  * @param {Ext.data.NodeInterface} node The newly appended node
77602                  * @param {Number} index The index of the newly appended node
77603                  */
77604                 "append",
77605
77606                 /**
77607                  * @event remove
77608                  * Fires when a child node is removed
77609                  * @param {Ext.data.NodeInterface} this This node
77610                  * @param {Ext.data.NodeInterface} node The removed node
77611                  */
77612                 "remove",
77613
77614                 /**
77615                  * @event move
77616                  * Fires when this node is moved to a new location in the tree
77617                  * @param {Ext.data.NodeInterface} this This node
77618                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
77619                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
77620                  * @param {Number} index The index it was moved to
77621                  */
77622                 "move",
77623
77624                 /**
77625                  * @event insert
77626                  * Fires when a new child node is inserted.
77627                  * @param {Ext.data.NodeInterface} this This node
77628                  * @param {Ext.data.NodeInterface} node The child node inserted
77629                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
77630                  */
77631                 "insert",
77632
77633                 /**
77634                  * @event beforeappend
77635                  * Fires before a new child is appended, return false to cancel the append.
77636                  * @param {Ext.data.NodeInterface} this This node
77637                  * @param {Ext.data.NodeInterface} node The child node to be appended
77638                  */
77639                 "beforeappend",
77640
77641                 /**
77642                  * @event beforeremove
77643                  * Fires before a child is removed, return false to cancel the remove.
77644                  * @param {Ext.data.NodeInterface} this This node
77645                  * @param {Ext.data.NodeInterface} node The child node to be removed
77646                  */
77647                 "beforeremove",
77648
77649                 /**
77650                  * @event beforemove
77651                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
77652                  * @param {Ext.data.NodeInterface} this This node
77653                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
77654                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
77655                  * @param {Number} index The index it is being moved to
77656                  */
77657                 "beforemove",
77658
77659                  /**
77660                   * @event beforeinsert
77661                   * Fires before a new child is inserted, return false to cancel the insert.
77662                   * @param {Ext.data.NodeInterface} this This node
77663                   * @param {Ext.data.NodeInterface} node The child node to be inserted
77664                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
77665                   */
77666                 "beforeinsert",
77667
77668                 /**
77669                  * @event expand
77670                  * Fires when this node is expanded.
77671                  * @param {Ext.data.NodeInterface} this The expanding node
77672                  */
77673                 "expand",
77674
77675                 /**
77676                  * @event collapse
77677                  * Fires when this node is collapsed.
77678                  * @param {Ext.data.NodeInterface} this The collapsing node
77679                  */
77680                 "collapse",
77681
77682                 /**
77683                  * @event beforeexpand
77684                  * Fires before this node is expanded.
77685                  * @param {Ext.data.NodeInterface} this The expanding node
77686                  */
77687                 "beforeexpand",
77688
77689                 /**
77690                  * @event beforecollapse
77691                  * Fires before this node is collapsed.
77692                  * @param {Ext.data.NodeInterface} this The collapsing node
77693                  */
77694                 "beforecollapse",
77695
77696                 /**
77697                  * @event sort
77698                  * Fires when this node's childNodes are sorted.
77699                  * @param {Ext.data.NodeInterface} this This node.
77700                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
77701                  */
77702                 "sort"
77703             ]);
77704
77705             return record;
77706         },
77707
77708         applyFields: function(modelClass, addFields) {
77709             var modelPrototype = modelClass.prototype,
77710                 fields = modelPrototype.fields,
77711                 keys = fields.keys,
77712                 ln = addFields.length,
77713                 addField, i, name,
77714                 newFields = [];
77715
77716             for (i = 0; i < ln; i++) {
77717                 addField = addFields[i];
77718                 if (!Ext.Array.contains(keys, addField.name)) {
77719                     addField = Ext.create('data.field', addField);
77720
77721                     newFields.push(addField);
77722                     fields.add(addField);
77723                 }
77724             }
77725
77726             return newFields;
77727         },
77728
77729         getPrototypeBody: function() {
77730             return {
77731                 isNode: true,
77732
77733                 /**
77734                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
77735                  * @return {Boolean}
77736                  */
77737                 createNode: function(node) {
77738                     if (Ext.isObject(node) && !node.isModel) {
77739                         node = Ext.ModelManager.create(node, this.modelName);
77740                     }
77741                     // Make sure the node implements the node interface
77742                     return Ext.data.NodeInterface.decorate(node);
77743                 },
77744
77745                 /**
77746                  * Returns true if this node is a leaf
77747                  * @return {Boolean}
77748                  */
77749                 isLeaf : function() {
77750                     return this.get('leaf') === true;
77751                 },
77752
77753                 /**
77754                  * Sets the first child of this node
77755                  * @private
77756                  * @param {Ext.data.NodeInterface} node
77757                  */
77758                 setFirstChild : function(node) {
77759                     this.firstChild = node;
77760                 },
77761
77762                 /**
77763                  * Sets the last child of this node
77764                  * @private
77765                  * @param {Ext.data.NodeInterface} node
77766                  */
77767                 setLastChild : function(node) {
77768                     this.lastChild = node;
77769                 },
77770
77771                 /**
77772                  * Updates general data of this node like isFirst, isLast, depth. This
77773                  * method is internally called after a node is moved. This shouldn't
77774                  * have to be called by the developer unless they are creating custom
77775                  * Tree plugins.
77776                  * @return {Boolean}
77777                  */
77778                 updateInfo: function(silent) {
77779                     var me = this,
77780                         isRoot = me.isRoot(),
77781                         parentNode = me.parentNode,
77782                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
77783                         isLast = (!parentNode ? true : parentNode.lastChild == me),
77784                         depth = 0,
77785                         parent = me,
77786                         children = me.childNodes,
77787                         len = children.length,
77788                         i = 0;
77789
77790                     while (parent.parentNode) {
77791                         ++depth;
77792                         parent = parent.parentNode;
77793                     }
77794
77795                     me.beginEdit();
77796                     me.set({
77797                         isFirst: isFirst,
77798                         isLast: isLast,
77799                         depth: depth,
77800                         index: parentNode ? parentNode.indexOf(me) : 0,
77801                         parentId: parentNode ? parentNode.getId() : null
77802                     });
77803                     me.endEdit(silent);
77804                     if (silent) {
77805                         me.commit();
77806                     }
77807
77808                     for (i = 0; i < len; i++) {
77809                         children[i].updateInfo(silent);
77810                     }
77811                 },
77812
77813                 /**
77814                  * Returns true if this node is the last child of its parent
77815                  * @return {Boolean}
77816                  */
77817                 isLast : function() {
77818                    return this.get('isLast');
77819                 },
77820
77821                 /**
77822                  * Returns true if this node is the first child of its parent
77823                  * @return {Boolean}
77824                  */
77825                 isFirst : function() {
77826                    return this.get('isFirst');
77827                 },
77828
77829                 /**
77830                  * Returns true if this node has one or more child nodes, else false.
77831                  * @return {Boolean}
77832                  */
77833                 hasChildNodes : function() {
77834                     return !this.isLeaf() && this.childNodes.length > 0;
77835                 },
77836
77837                 /**
77838                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
77839                  * node attribute is explicitly specified as true, otherwise returns false.
77840                  * @return {Boolean}
77841                  */
77842                 isExpandable : function() {
77843                     var me = this;
77844
77845                     if (me.get('expandable')) {
77846                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
77847                     }
77848                     return false;
77849                 },
77850
77851                 /**
77852                  * Inserts node(s) as the last child node of this node.
77853                  *
77854                  * If the node was previously a child node of another parent node, it will be removed from that node first.
77855                  *
77856                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
77857                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
77858                  */
77859                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
77860                     var me = this,
77861                         i, ln,
77862                         index,
77863                         oldParent,
77864                         ps;
77865
77866                     // if passed an array or multiple args do them one by one
77867                     if (Ext.isArray(node)) {
77868                         for (i = 0, ln = node.length; i < ln; i++) {
77869                             me.appendChild(node[i]);
77870                         }
77871                     } else {
77872                         // Make sure it is a record
77873                         node = me.createNode(node);
77874
77875                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
77876                             return false;
77877                         }
77878
77879                         index = me.childNodes.length;
77880                         oldParent = node.parentNode;
77881
77882                         // it's a move, make sure we move it cleanly
77883                         if (oldParent) {
77884                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
77885                                 return false;
77886                             }
77887                             oldParent.removeChild(node, null, false, true);
77888                         }
77889
77890                         index = me.childNodes.length;
77891                         if (index === 0) {
77892                             me.setFirstChild(node);
77893                         }
77894
77895                         me.childNodes.push(node);
77896                         node.parentNode = me;
77897                         node.nextSibling = null;
77898
77899                         me.setLastChild(node);
77900
77901                         ps = me.childNodes[index - 1];
77902                         if (ps) {
77903                             node.previousSibling = ps;
77904                             ps.nextSibling = node;
77905                             ps.updateInfo(suppressNodeUpdate);
77906                         } else {
77907                             node.previousSibling = null;
77908                         }
77909
77910                         node.updateInfo(suppressNodeUpdate);
77911
77912                         // As soon as we append a child to this node, we are loaded
77913                         if (!me.isLoaded()) {
77914                             me.set('loaded', true);
77915                         }
77916                         // If this node didnt have any childnodes before, update myself
77917                         else if (me.childNodes.length === 1) {
77918                             me.set('loaded', me.isLoaded());
77919                         }
77920
77921                         if (suppressEvents !== true) {
77922                             me.fireEvent("append", me, node, index);
77923
77924                             if (oldParent) {
77925                                 node.fireEvent("move", node, oldParent, me, index);
77926                             }
77927                         }
77928
77929                         return node;
77930                     }
77931                 },
77932
77933                 /**
77934                  * Returns the bubble target for this node
77935                  * @private
77936                  * @return {Object} The bubble target
77937                  */
77938                 getBubbleTarget: function() {
77939                     return this.parentNode;
77940                 },
77941
77942                 /**
77943                  * Removes a child node from this node.
77944                  * @param {Ext.data.NodeInterface} node The node to remove
77945                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
77946                  * @return {Ext.data.NodeInterface} The removed node
77947                  */
77948                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
77949                     var me = this,
77950                         index = me.indexOf(node);
77951
77952                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
77953                         return false;
77954                     }
77955
77956                     // remove it from childNodes collection
77957                     Ext.Array.erase(me.childNodes, index, 1);
77958
77959                     // update child refs
77960                     if (me.firstChild == node) {
77961                         me.setFirstChild(node.nextSibling);
77962                     }
77963                     if (me.lastChild == node) {
77964                         me.setLastChild(node.previousSibling);
77965                     }
77966
77967                     // update siblings
77968                     if (node.previousSibling) {
77969                         node.previousSibling.nextSibling = node.nextSibling;
77970                         node.previousSibling.updateInfo(suppressNodeUpdate);
77971                     }
77972                     if (node.nextSibling) {
77973                         node.nextSibling.previousSibling = node.previousSibling;
77974                         node.nextSibling.updateInfo(suppressNodeUpdate);
77975                     }
77976
77977                     if (suppressEvents !== true) {
77978                         me.fireEvent("remove", me, node);
77979                     }
77980
77981
77982                     // If this node suddenly doesnt have childnodes anymore, update myself
77983                     if (!me.childNodes.length) {
77984                         me.set('loaded', me.isLoaded());
77985                     }
77986
77987                     if (destroy) {
77988                         node.destroy(true);
77989                     } else {
77990                         node.clear();
77991                     }
77992
77993                     return node;
77994                 },
77995
77996                 /**
77997                  * Creates a copy (clone) of this Node.
77998                  * @param {String} [id] A new id, defaults to this Node's id.
77999                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
78000                  * False to copy without child Nodes.
78001                  * @return {Ext.data.NodeInterface} A copy of this Node.
78002                  */
78003                 copy: function(newId, deep) {
78004                     var me = this,
78005                         result = me.callOverridden(arguments),
78006                         len = me.childNodes ? me.childNodes.length : 0,
78007                         i;
78008
78009                     // Move child nodes across to the copy if required
78010                     if (deep) {
78011                         for (i = 0; i < len; i++) {
78012                             result.appendChild(me.childNodes[i].copy(true));
78013                         }
78014                     }
78015                     return result;
78016                 },
78017
78018                 /**
78019                  * Clears the node.
78020                  * @private
78021                  * @param {Boolean} [destroy=false] True to destroy the node.
78022                  */
78023                 clear : function(destroy) {
78024                     var me = this;
78025
78026                     // clear any references from the node
78027                     me.parentNode = me.previousSibling = me.nextSibling = null;
78028                     if (destroy) {
78029                         me.firstChild = me.lastChild = null;
78030                     }
78031                 },
78032
78033                 /**
78034                  * Destroys the node.
78035                  */
78036                 destroy : function(silent) {
78037                     /*
78038                      * Silent is to be used in a number of cases
78039                      * 1) When setRoot is called.
78040                      * 2) When destroy on the tree is called
78041                      * 3) For destroying child nodes on a node
78042                      */
78043                     var me = this,
78044                         options = me.destroyOptions;
78045
78046                     if (silent === true) {
78047                         me.clear(true);
78048                         Ext.each(me.childNodes, function(n) {
78049                             n.destroy(true);
78050                         });
78051                         me.childNodes = null;
78052                         delete me.destroyOptions;
78053                         me.callOverridden([options]);
78054                     } else {
78055                         me.destroyOptions = silent;
78056                         // overridden method will be called, since remove will end up calling destroy(true);
78057                         me.remove(true);
78058                     }
78059                 },
78060
78061                 /**
78062                  * Inserts the first node before the second node in this nodes childNodes collection.
78063                  * @param {Ext.data.NodeInterface} node The node to insert
78064                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
78065                  * @return {Ext.data.NodeInterface} The inserted node
78066                  */
78067                 insertBefore : function(node, refNode, suppressEvents) {
78068                     var me = this,
78069                         index     = me.indexOf(refNode),
78070                         oldParent = node.parentNode,
78071                         refIndex  = index,
78072                         ps;
78073
78074                     if (!refNode) { // like standard Dom, refNode can be null for append
78075                         return me.appendChild(node);
78076                     }
78077
78078                     // nothing to do
78079                     if (node == refNode) {
78080                         return false;
78081                     }
78082
78083                     // Make sure it is a record with the NodeInterface
78084                     node = me.createNode(node);
78085
78086                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
78087                         return false;
78088                     }
78089
78090                     // when moving internally, indexes will change after remove
78091                     if (oldParent == me && me.indexOf(node) < index) {
78092                         refIndex--;
78093                     }
78094
78095                     // it's a move, make sure we move it cleanly
78096                     if (oldParent) {
78097                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
78098                             return false;
78099                         }
78100                         oldParent.removeChild(node);
78101                     }
78102
78103                     if (refIndex === 0) {
78104                         me.setFirstChild(node);
78105                     }
78106
78107                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
78108                     node.parentNode = me;
78109
78110                     node.nextSibling = refNode;
78111                     refNode.previousSibling = node;
78112
78113                     ps = me.childNodes[refIndex - 1];
78114                     if (ps) {
78115                         node.previousSibling = ps;
78116                         ps.nextSibling = node;
78117                         ps.updateInfo();
78118                     } else {
78119                         node.previousSibling = null;
78120                     }
78121
78122                     node.updateInfo();
78123
78124                     if (!me.isLoaded()) {
78125                         me.set('loaded', true);
78126                     }
78127                     // If this node didnt have any childnodes before, update myself
78128                     else if (me.childNodes.length === 1) {
78129                         me.set('loaded', me.isLoaded());
78130                     }
78131
78132                     if (suppressEvents !== true) {
78133                         me.fireEvent("insert", me, node, refNode);
78134
78135                         if (oldParent) {
78136                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
78137                         }
78138                     }
78139
78140                     return node;
78141                 },
78142
78143                 /**
78144                  * Insert a node into this node
78145                  * @param {Number} index The zero-based index to insert the node at
78146                  * @param {Ext.data.Model} node The node to insert
78147                  * @return {Ext.data.Model} The record you just inserted
78148                  */
78149                 insertChild: function(index, node) {
78150                     var sibling = this.childNodes[index];
78151                     if (sibling) {
78152                         return this.insertBefore(node, sibling);
78153                     }
78154                     else {
78155                         return this.appendChild(node);
78156                     }
78157                 },
78158
78159                 /**
78160                  * Removes this node from its parent
78161                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78162                  * @return {Ext.data.NodeInterface} this
78163                  */
78164                 remove : function(destroy, suppressEvents) {
78165                     var parentNode = this.parentNode;
78166
78167                     if (parentNode) {
78168                         parentNode.removeChild(this, destroy, suppressEvents, true);
78169                     }
78170                     return this;
78171                 },
78172
78173                 /**
78174                  * Removes all child nodes from this node.
78175                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
78176                  * @return {Ext.data.NodeInterface} this
78177                  */
78178                 removeAll : function(destroy, suppressEvents) {
78179                     var cn = this.childNodes,
78180                         n;
78181
78182                     while ((n = cn[0])) {
78183                         this.removeChild(n, destroy, suppressEvents);
78184                     }
78185                     return this;
78186                 },
78187
78188                 /**
78189                  * Returns the child node at the specified index.
78190                  * @param {Number} index
78191                  * @return {Ext.data.NodeInterface}
78192                  */
78193                 getChildAt : function(index) {
78194                     return this.childNodes[index];
78195                 },
78196
78197                 /**
78198                  * Replaces one child node in this node with another.
78199                  * @param {Ext.data.NodeInterface} newChild The replacement node
78200                  * @param {Ext.data.NodeInterface} oldChild The node to replace
78201                  * @return {Ext.data.NodeInterface} The replaced node
78202                  */
78203                 replaceChild : function(newChild, oldChild, suppressEvents) {
78204                     var s = oldChild ? oldChild.nextSibling : null;
78205
78206                     this.removeChild(oldChild, suppressEvents);
78207                     this.insertBefore(newChild, s, suppressEvents);
78208                     return oldChild;
78209                 },
78210
78211                 /**
78212                  * Returns the index of a child node
78213                  * @param {Ext.data.NodeInterface} node
78214                  * @return {Number} The index of the node or -1 if it was not found
78215                  */
78216                 indexOf : function(child) {
78217                     return Ext.Array.indexOf(this.childNodes, child);
78218                 },
78219
78220                 /**
78221                  * Gets the hierarchical path from the root of the current node.
78222                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
78223                  * @param {String} [separator="/"] A separator to use.
78224                  * @return {String} The node path
78225                  */
78226                 getPath: function(field, separator) {
78227                     field = field || this.idProperty;
78228                     separator = separator || '/';
78229
78230                     var path = [this.get(field)],
78231                         parent = this.parentNode;
78232
78233                     while (parent) {
78234                         path.unshift(parent.get(field));
78235                         parent = parent.parentNode;
78236                     }
78237                     return separator + path.join(separator);
78238                 },
78239
78240                 /**
78241                  * Returns depth of this node (the root node has a depth of 0)
78242                  * @return {Number}
78243                  */
78244                 getDepth : function() {
78245                     return this.get('depth');
78246                 },
78247
78248                 /**
78249                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
78250                  * will be the args provided or the current node. If the function returns false at any point,
78251                  * the bubble is stopped.
78252                  * @param {Function} fn The function to call
78253                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78254                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78255                  */
78256                 bubble : function(fn, scope, args) {
78257                     var p = this;
78258                     while (p) {
78259                         if (fn.apply(scope || p, args || [p]) === false) {
78260                             break;
78261                         }
78262                         p = p.parentNode;
78263                     }
78264                 },
78265
78266                 cascade: function() {
78267                     if (Ext.isDefined(Ext.global.console)) {
78268                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
78269                     }
78270                     return this.cascadeBy.apply(this, arguments);
78271                 },
78272
78273                 /**
78274                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
78275                  * will be the args provided or the current node. If the function returns false at any point,
78276                  * the cascade is stopped on that branch.
78277                  * @param {Function} fn The function to call
78278                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78279                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78280                  */
78281                 cascadeBy : function(fn, scope, args) {
78282                     if (fn.apply(scope || this, args || [this]) !== false) {
78283                         var childNodes = this.childNodes,
78284                             length     = childNodes.length,
78285                             i;
78286
78287                         for (i = 0; i < length; i++) {
78288                             childNodes[i].cascadeBy(fn, scope, args);
78289                         }
78290                     }
78291                 },
78292
78293                 /**
78294                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
78295                  * will be the args provided or the current node. If the function returns false at any point,
78296                  * the iteration stops.
78297                  * @param {Function} fn The function to call
78298                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
78299                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78300                  */
78301                 eachChild : function(fn, scope, args) {
78302                     var childNodes = this.childNodes,
78303                         length     = childNodes.length,
78304                         i;
78305
78306                     for (i = 0; i < length; i++) {
78307                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
78308                             break;
78309                         }
78310                     }
78311                 },
78312
78313                 /**
78314                  * Finds the first child that has the attribute with the specified value.
78315                  * @param {String} attribute The attribute name
78316                  * @param {Object} value The value to search for
78317                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78318                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78319                  */
78320                 findChild : function(attribute, value, deep) {
78321                     return this.findChildBy(function() {
78322                         return this.get(attribute) == value;
78323                     }, null, deep);
78324                 },
78325
78326                 /**
78327                  * Finds the first child by a custom function. The child matches if the function passed returns true.
78328                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
78329                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
78330                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78331                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78332                  */
78333                 findChildBy : function(fn, scope, deep) {
78334                     var cs = this.childNodes,
78335                         len = cs.length,
78336                         i = 0, n, res;
78337
78338                     for (; i < len; i++) {
78339                         n = cs[i];
78340                         if (fn.call(scope || n, n) === true) {
78341                             return n;
78342                         }
78343                         else if (deep) {
78344                             res = n.findChildBy(fn, scope, deep);
78345                             if (res !== null) {
78346                                 return res;
78347                             }
78348                         }
78349                     }
78350
78351                     return null;
78352                 },
78353
78354                 /**
78355                  * Returns true if this node is an ancestor (at any point) of the passed node.
78356                  * @param {Ext.data.NodeInterface} node
78357                  * @return {Boolean}
78358                  */
78359                 contains : function(node) {
78360                     return node.isAncestor(this);
78361                 },
78362
78363                 /**
78364                  * Returns true if the passed node is an ancestor (at any point) of this node.
78365                  * @param {Ext.data.NodeInterface} node
78366                  * @return {Boolean}
78367                  */
78368                 isAncestor : function(node) {
78369                     var p = this.parentNode;
78370                     while (p) {
78371                         if (p == node) {
78372                             return true;
78373                         }
78374                         p = p.parentNode;
78375                     }
78376                     return false;
78377                 },
78378
78379                 /**
78380                  * Sorts this nodes children using the supplied sort function.
78381                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
78382                  * @param {Boolean} [recursive=false] True to apply this sort recursively
78383                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
78384                  */
78385                 sort : function(sortFn, recursive, suppressEvent) {
78386                     var cs  = this.childNodes,
78387                         ln = cs.length,
78388                         i, n;
78389
78390                     if (ln > 0) {
78391                         Ext.Array.sort(cs, sortFn);
78392                         for (i = 0; i < ln; i++) {
78393                             n = cs[i];
78394                             n.previousSibling = cs[i-1];
78395                             n.nextSibling = cs[i+1];
78396
78397                             if (i === 0) {
78398                                 this.setFirstChild(n);
78399                                 n.updateInfo();
78400                             }
78401                             if (i == ln - 1) {
78402                                 this.setLastChild(n);
78403                                 n.updateInfo();
78404                             }
78405                             if (recursive && !n.isLeaf()) {
78406                                 n.sort(sortFn, true, true);
78407                             }
78408                         }
78409
78410                         if (suppressEvent !== true) {
78411                             this.fireEvent('sort', this, cs);
78412                         }
78413                     }
78414                 },
78415
78416                 /**
78417                  * Returns true if this node is expaned
78418                  * @return {Boolean}
78419                  */
78420                 isExpanded: function() {
78421                     return this.get('expanded');
78422                 },
78423
78424                 /**
78425                  * Returns true if this node is loaded
78426                  * @return {Boolean}
78427                  */
78428                 isLoaded: function() {
78429                     return this.get('loaded');
78430                 },
78431
78432                 /**
78433                  * Returns true if this node is loading
78434                  * @return {Boolean}
78435                  */
78436                 isLoading: function() {
78437                     return this.get('loading');
78438                 },
78439
78440                 /**
78441                  * Returns true if this node is the root node
78442                  * @return {Boolean}
78443                  */
78444                 isRoot: function() {
78445                     return !this.parentNode;
78446                 },
78447
78448                 /**
78449                  * Returns true if this node is visible
78450                  * @return {Boolean}
78451                  */
78452                 isVisible: function() {
78453                     var parent = this.parentNode;
78454                     while (parent) {
78455                         if (!parent.isExpanded()) {
78456                             return false;
78457                         }
78458                         parent = parent.parentNode;
78459                     }
78460                     return true;
78461                 },
78462
78463                 /**
78464                  * Expand this node.
78465                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78466                  * @param {Function} [callback] The function to execute once the expand completes
78467                  * @param {Object} [scope] The scope to run the callback in
78468                  */
78469                 expand: function(recursive, callback, scope) {
78470                     var me = this;
78471
78472                     // all paths must call the callback (eventually) or things like
78473                     // selectPath fail
78474
78475                     // First we start by checking if this node is a parent
78476                     if (!me.isLeaf()) {
78477                         // If it's loaded, wait until it loads before proceeding
78478                         if (me.isLoading()) {
78479                             me.on('expand', function(){
78480                                 me.expand(recursive, callback, scope);
78481                             }, me, {single: true});
78482                         } else {
78483                             // Now we check if this record is already expanding or expanded
78484                             if (!me.isExpanded()) {
78485                                 // The TreeStore actually listens for the beforeexpand method and checks
78486                                 // whether we have to asynchronously load the children from the server
78487                                 // first. Thats why we pass a callback function to the event that the
78488                                 // store can call once it has loaded and parsed all the children.
78489                                 me.fireEvent('beforeexpand', me, function(){
78490                                     me.set('expanded', true);
78491                                     me.fireEvent('expand', me, me.childNodes, false);
78492
78493                                     // Call the expandChildren method if recursive was set to true
78494                                     if (recursive) {
78495                                         me.expandChildren(true, callback, scope);
78496                                     } else {
78497                                         Ext.callback(callback, scope || me, [me.childNodes]);
78498                                     }
78499                                 }, me);
78500                             } else if (recursive) {
78501                                 // If it is is already expanded but we want to recursively expand then call expandChildren
78502                                 me.expandChildren(true, callback, scope);
78503                             } else {
78504                                 Ext.callback(callback, scope || me, [me.childNodes]);
78505                             }
78506                         }
78507                     } else {
78508                         // If it's not then we fire the callback right away
78509                         Ext.callback(callback, scope || me); // leaf = no childNodes
78510                     }
78511                 },
78512
78513                 /**
78514                  * Expand all the children of this node.
78515                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78516                  * @param {Function} [callback] The function to execute once all the children are expanded
78517                  * @param {Object} [scope] The scope to run the callback in
78518                  */
78519                 expandChildren: function(recursive, callback, scope) {
78520                     var me = this,
78521                         i = 0,
78522                         nodes = me.childNodes,
78523                         ln = nodes.length,
78524                         node,
78525                         expanding = 0;
78526
78527                     for (; i < ln; ++i) {
78528                         node = nodes[i];
78529                         if (!node.isLeaf() && !node.isExpanded()) {
78530                             expanding++;
78531                             nodes[i].expand(recursive, function () {
78532                                 expanding--;
78533                                 if (callback && !expanding) {
78534                                     Ext.callback(callback, scope || me, [me.childNodes]);
78535                                 }
78536                             });
78537                         }
78538                     }
78539
78540                     if (!expanding && callback) {
78541                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
78542                 },
78543
78544                 /**
78545                  * Collapse this node.
78546                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
78547                  * @param {Function} [callback] The function to execute once the collapse completes
78548                  * @param {Object} [scope] The scope to run the callback in
78549                  */
78550                 collapse: function(recursive, callback, scope) {
78551                     var me = this;
78552
78553                     // First we start by checking if this node is a parent
78554                     if (!me.isLeaf()) {
78555                         // Now we check if this record is already collapsing or collapsed
78556                         if (!me.collapsing && me.isExpanded()) {
78557                             me.fireEvent('beforecollapse', me, function() {
78558                                 me.set('expanded', false);
78559                                 me.fireEvent('collapse', me, me.childNodes, false);
78560
78561                                 // Call the collapseChildren method if recursive was set to true
78562                                 if (recursive) {
78563                                     me.collapseChildren(true, callback, scope);
78564                                 }
78565                                 else {
78566                                     Ext.callback(callback, scope || me, [me.childNodes]);
78567                                 }
78568                             }, me);
78569                         }
78570                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
78571                         else if (recursive) {
78572                             me.collapseChildren(true, callback, scope);
78573                         }
78574                     }
78575                     // If it's not then we fire the callback right away
78576                     else {
78577                         Ext.callback(callback, scope || me, [me.childNodes]);
78578                     }
78579                 },
78580
78581                 /**
78582                  * Collapse all the children of this node.
78583                  * @param {Function} [recursive=false] True to recursively collapse all the children
78584                  * @param {Function} [callback] The function to execute once all the children are collapsed
78585                  * @param {Object} [scope] The scope to run the callback in
78586                  */
78587                 collapseChildren: function(recursive, callback, scope) {
78588                     var me = this,
78589                         i = 0,
78590                         nodes = me.childNodes,
78591                         ln = nodes.length,
78592                         node,
78593                         collapsing = 0;
78594
78595                     for (; i < ln; ++i) {
78596                         node = nodes[i];
78597                         if (!node.isLeaf() && node.isExpanded()) {
78598                             collapsing++;
78599                             nodes[i].collapse(recursive, function () {
78600                                 collapsing--;
78601                                 if (callback && !collapsing) {
78602                                     Ext.callback(callback, scope || me, [me.childNodes]);
78603                                 }
78604                             });
78605                         }
78606                     }
78607
78608                     if (!collapsing && callback) {
78609                         Ext.callback(callback, scope || me, [me.childNodes]);
78610                     }
78611                 }
78612             };
78613         }
78614     }
78615 });
78616 /**
78617  * @class Ext.data.NodeStore
78618  * @extends Ext.data.AbstractStore
78619  * Node Store
78620  * @ignore
78621  */
78622 Ext.define('Ext.data.NodeStore', {
78623     extend: 'Ext.data.Store',
78624     alias: 'store.node',
78625     requires: ['Ext.data.NodeInterface'],
78626     
78627     /**
78628      * @cfg {Ext.data.Model} node
78629      * The Record you want to bind this Store to. Note that
78630      * this record will be decorated with the Ext.data.NodeInterface if this is not the
78631      * case yet.
78632      */
78633     node: null,
78634     
78635     /**
78636      * @cfg {Boolean} recursive
78637      * Set this to true if you want this NodeStore to represent
78638      * all the descendents of the node in its flat data collection. This is useful for
78639      * rendering a tree structure to a DataView and is being used internally by
78640      * the TreeView. Any records that are moved, removed, inserted or appended to the
78641      * node at any depth below the node this store is bound to will be automatically
78642      * updated in this Store's internal flat data structure.
78643      */
78644     recursive: false,
78645     
78646     /** 
78647      * @cfg {Boolean} rootVisible
78648      * False to not include the root node in this Stores collection.
78649      */    
78650     rootVisible: false,
78651     
78652     constructor: function(config) {
78653         var me = this,
78654             node;
78655             
78656         config = config || {};
78657         Ext.apply(me, config);
78658         
78659
78660         config.proxy = {type: 'proxy'};
78661         me.callParent([config]);
78662
78663         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
78664         
78665         node = me.node;
78666         if (node) {
78667             me.node = null;
78668             me.setNode(node);
78669         }
78670     },
78671     
78672     setNode: function(node) {
78673         var me = this;
78674         
78675         if (me.node && me.node != node) {
78676             // We want to unbind our listeners on the old node
78677             me.mun(me.node, {
78678                 expand: me.onNodeExpand,
78679                 collapse: me.onNodeCollapse,
78680                 append: me.onNodeAppend,
78681                 insert: me.onNodeInsert,
78682                 remove: me.onNodeRemove,
78683                 sort: me.onNodeSort,
78684                 scope: me
78685             });
78686             me.node = null;
78687         }
78688         
78689         if (node) {
78690             Ext.data.NodeInterface.decorate(node);
78691             me.removeAll();
78692             if (me.rootVisible) {
78693                 me.add(node);
78694             }
78695             me.mon(node, {
78696                 expand: me.onNodeExpand,
78697                 collapse: me.onNodeCollapse,
78698                 append: me.onNodeAppend,
78699                 insert: me.onNodeInsert,
78700                 remove: me.onNodeRemove,
78701                 sort: me.onNodeSort,
78702                 scope: me
78703             });
78704             me.node = node;
78705             if (node.isExpanded() && node.isLoaded()) {
78706                 me.onNodeExpand(node, node.childNodes, true);
78707             }
78708         }
78709     },
78710     
78711     onNodeSort: function(node, childNodes) {
78712         var me = this;
78713         
78714         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
78715             me.onNodeCollapse(node, childNodes, true);
78716             me.onNodeExpand(node, childNodes, true);
78717         }
78718     },
78719     
78720     onNodeExpand: function(parent, records, suppressEvent) {
78721         var me = this,
78722             insertIndex = me.indexOf(parent) + 1,
78723             ln = records ? records.length : 0,
78724             i, record;
78725             
78726         if (!me.recursive && parent !== me.node) {
78727             return;
78728         }
78729         
78730         if (!me.isVisible(parent)) {
78731             return;
78732         }
78733
78734         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
78735             return;
78736         }
78737         
78738         if (ln) {
78739             me.insert(insertIndex, records);
78740             for (i = 0; i < ln; i++) {
78741                 record = records[i];
78742                 if (record.isExpanded()) {
78743                     if (record.isLoaded()) {
78744                         // Take a shortcut                        
78745                         me.onNodeExpand(record, record.childNodes, true);
78746                     }
78747                     else {
78748                         record.set('expanded', false);
78749                         record.expand();
78750                     }
78751                 }
78752             }
78753         }
78754
78755         if (!suppressEvent) {
78756             me.fireEvent('expand', parent, records);
78757         }
78758     },
78759
78760     onNodeCollapse: function(parent, records, suppressEvent) {
78761         var me = this,
78762             ln = records.length,
78763             collapseIndex = me.indexOf(parent) + 1,
78764             i, record;
78765             
78766         if (!me.recursive && parent !== me.node) {
78767             return;
78768         }
78769         
78770         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
78771             return;
78772         }
78773
78774         for (i = 0; i < ln; i++) {
78775             record = records[i];
78776             me.remove(record);
78777             if (record.isExpanded()) {
78778                 me.onNodeCollapse(record, record.childNodes, true);
78779             }
78780         }
78781         
78782         if (!suppressEvent) {
78783             me.fireEvent('collapse', parent, records, collapseIndex);
78784         }
78785     },
78786     
78787     onNodeAppend: function(parent, node, index) {
78788         var me = this,
78789             refNode, sibling;
78790
78791         if (me.isVisible(node)) {
78792             if (index === 0) {
78793                 refNode = parent;
78794             } else {
78795                 sibling = node.previousSibling;
78796                 while (sibling.isExpanded() && sibling.lastChild) {
78797                     sibling = sibling.lastChild;
78798                 }
78799                 refNode = sibling;
78800             }
78801             me.insert(me.indexOf(refNode) + 1, node);
78802             if (!node.isLeaf() && node.isExpanded()) {
78803                 if (node.isLoaded()) {
78804                     // Take a shortcut                        
78805                     me.onNodeExpand(node, node.childNodes, true);
78806                 }
78807                 else {
78808                     node.set('expanded', false);
78809                     node.expand();
78810                 }
78811             }
78812         } 
78813     },
78814     
78815     onNodeInsert: function(parent, node, refNode) {
78816         var me = this,
78817             index = this.indexOf(refNode);
78818             
78819         if (index != -1 && me.isVisible(node)) {
78820             me.insert(index, node);
78821             if (!node.isLeaf() && node.isExpanded()) {
78822                 if (node.isLoaded()) {
78823                     // Take a shortcut                        
78824                     me.onNodeExpand(node, node.childNodes, true);
78825                 }
78826                 else {
78827                     node.set('expanded', false);
78828                     node.expand();
78829                 }
78830             }
78831         }
78832     },
78833     
78834     onNodeRemove: function(parent, node, index) {
78835         var me = this;
78836         if (me.indexOf(node) != -1) {
78837             if (!node.isLeaf() && node.isExpanded()) {
78838                 me.onNodeCollapse(node, node.childNodes, true);
78839             }            
78840             me.remove(node);
78841         }
78842     },
78843     
78844     isVisible: function(node) {
78845         var parent = node.parentNode;
78846         while (parent) {
78847             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
78848                 return true;
78849             }
78850             
78851             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
78852                 return false;
78853             }
78854             
78855             parent = parent.parentNode;
78856         }
78857         return true;
78858     }
78859 });
78860 /**
78861  * @author Ed Spencer
78862  * 
78863  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
78864  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
78865  * it does not contain any actual logic or perform the request itself.
78866  */
78867 Ext.define('Ext.data.Request', {
78868     /**
78869      * @cfg {String} action
78870      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
78871      */
78872     action: undefined,
78873     
78874     /**
78875      * @cfg {Object} params
78876      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
78877      */
78878     params: undefined,
78879     
78880     /**
78881      * @cfg {String} method
78882      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
78883      */
78884     method: 'GET',
78885     
78886     /**
78887      * @cfg {String} url
78888      * The url to access on this Request
78889      */
78890     url: undefined,
78891
78892     /**
78893      * Creates the Request object.
78894      * @param {Object} [config] Config object.
78895      */
78896     constructor: function(config) {
78897         Ext.apply(this, config);
78898     }
78899 });
78900 /**
78901  * @author Don Griffin
78902  *
78903  * This class is a sequential id generator. A simple use of this class would be like so:
78904  *
78905  *     Ext.define('MyApp.data.MyModel', {
78906  *         extend: 'Ext.data.Model',
78907  *         idgen: 'sequential'
78908  *     });
78909  *     // assign id's of 1, 2, 3, etc.
78910  *
78911  * An example of a configured generator would be:
78912  *
78913  *     Ext.define('MyApp.data.MyModel', {
78914  *         extend: 'Ext.data.Model',
78915  *         idgen: {
78916  *             type: 'sequential',
78917  *             prefix: 'ID_',
78918  *             seed: 1000
78919  *         }
78920  *     });
78921  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
78922  *
78923  */
78924 Ext.define('Ext.data.SequentialIdGenerator', {
78925     extend: 'Ext.data.IdGenerator',
78926     alias: 'idgen.sequential',
78927
78928     constructor: function() {
78929         var me = this;
78930
78931         me.callParent(arguments);
78932
78933         me.parts = [ me.prefix, ''];
78934     },
78935
78936     /**
78937      * @cfg {String} prefix
78938      * The string to place in front of the sequential number for each generated id. The
78939      * default is blank.
78940      */
78941     prefix: '',
78942
78943     /**
78944      * @cfg {Number} seed
78945      * The number at which to start generating sequential id's. The default is 1.
78946      */
78947     seed: 1,
78948
78949     /**
78950      * Generates and returns the next id.
78951      * @return {String} The next id.
78952      */
78953     generate: function () {
78954         var me = this,
78955             parts = me.parts;
78956
78957         parts[1] = me.seed++;
78958         return parts.join('');
78959     }
78960 });
78961
78962 /**
78963  * @class Ext.data.Tree
78964  *
78965  * This class is used as a container for a series of nodes. The nodes themselves maintain
78966  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
78967  * to retrieve a node by its identifier: {@link #getNodeById}.
78968  *
78969  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
78970  * centralized fashion. In general this class is not used directly, rather used internally
78971  * by other parts of the framework.
78972  *
78973  */
78974 Ext.define('Ext.data.Tree', {
78975     alias: 'data.tree',
78976
78977     mixins: {
78978         observable: "Ext.util.Observable"
78979     },
78980
78981     /**
78982      * @property {Ext.data.NodeInterface}
78983      * The root node for this tree
78984      */
78985     root: null,
78986
78987     /**
78988      * Creates new Tree object.
78989      * @param {Ext.data.NodeInterface} root (optional) The root node
78990      */
78991     constructor: function(root) {
78992         var me = this;
78993
78994         
78995
78996         me.mixins.observable.constructor.call(me);
78997
78998         if (root) {
78999             me.setRootNode(root);
79000         }
79001     },
79002
79003     /**
79004      * Returns the root node for this tree.
79005      * @return {Ext.data.NodeInterface}
79006      */
79007     getRootNode : function() {
79008         return this.root;
79009     },
79010
79011     /**
79012      * Sets the root node for this tree.
79013      * @param {Ext.data.NodeInterface} node
79014      * @return {Ext.data.NodeInterface} The root node
79015      */
79016     setRootNode : function(node) {
79017         var me = this;
79018
79019         me.root = node;
79020         Ext.data.NodeInterface.decorate(node);
79021
79022         if (me.fireEvent('beforeappend', null, node) !== false) {
79023             node.set('root', true);
79024             node.updateInfo();
79025
79026             me.relayEvents(node, [
79027                 /**
79028                  * @event append
79029                  * @alias Ext.data.NodeInterface#append
79030                  */
79031                 "append",
79032
79033                 /**
79034                  * @event remove
79035                  * @alias Ext.data.NodeInterface#remove
79036                  */
79037                 "remove",
79038
79039                 /**
79040                  * @event move
79041                  * @alias Ext.data.NodeInterface#move
79042                  */
79043                 "move",
79044
79045                 /**
79046                  * @event insert
79047                  * @alias Ext.data.NodeInterface#insert
79048                  */
79049                 "insert",
79050
79051                 /**
79052                  * @event beforeappend
79053                  * @alias Ext.data.NodeInterface#beforeappend
79054                  */
79055                 "beforeappend",
79056
79057                 /**
79058                  * @event beforeremove
79059                  * @alias Ext.data.NodeInterface#beforeremove
79060                  */
79061                 "beforeremove",
79062
79063                 /**
79064                  * @event beforemove
79065                  * @alias Ext.data.NodeInterface#beforemove
79066                  */
79067                 "beforemove",
79068
79069                 /**
79070                  * @event beforeinsert
79071                  * @alias Ext.data.NodeInterface#beforeinsert
79072                  */
79073                 "beforeinsert",
79074
79075                  /**
79076                   * @event expand
79077                   * @alias Ext.data.NodeInterface#expand
79078                   */
79079                  "expand",
79080
79081                  /**
79082                   * @event collapse
79083                   * @alias Ext.data.NodeInterface#collapse
79084                   */
79085                  "collapse",
79086
79087                  /**
79088                   * @event beforeexpand
79089                   * @alias Ext.data.NodeInterface#beforeexpand
79090                   */
79091                  "beforeexpand",
79092
79093                  /**
79094                   * @event beforecollapse
79095                   * @alias Ext.data.NodeInterface#beforecollapse
79096                   */
79097                  "beforecollapse" ,
79098
79099                  /**
79100                   * @event rootchange
79101                   * Fires whenever the root node is changed in the tree.
79102                   * @param {Ext.data.Model} root The new root
79103                   */
79104                  "rootchange"
79105             ]);
79106
79107             node.on({
79108                 scope: me,
79109                 insert: me.onNodeInsert,
79110                 append: me.onNodeAppend,
79111                 remove: me.onNodeRemove
79112             });
79113
79114             me.nodeHash = {};
79115             me.registerNode(node);
79116             me.fireEvent('append', null, node);
79117             me.fireEvent('rootchange', node);
79118         }
79119
79120         return node;
79121     },
79122
79123     /**
79124      * Flattens all the nodes in the tree into an array.
79125      * @private
79126      * @return {Ext.data.NodeInterface[]} The flattened nodes.
79127      */
79128     flatten: function(){
79129         var nodes = [],
79130             hash = this.nodeHash,
79131             key;
79132
79133         for (key in hash) {
79134             if (hash.hasOwnProperty(key)) {
79135                 nodes.push(hash[key]);
79136             }
79137         }
79138         return nodes;
79139     },
79140
79141     /**
79142      * Fired when a node is inserted into the root or one of it's children
79143      * @private
79144      * @param {Ext.data.NodeInterface} parent The parent node
79145      * @param {Ext.data.NodeInterface} node The inserted node
79146      */
79147     onNodeInsert: function(parent, node) {
79148         this.registerNode(node, true);
79149     },
79150
79151     /**
79152      * Fired when a node is appended into the root or one of it's children
79153      * @private
79154      * @param {Ext.data.NodeInterface} parent The parent node
79155      * @param {Ext.data.NodeInterface} node The appended node
79156      */
79157     onNodeAppend: function(parent, node) {
79158         this.registerNode(node, true);
79159     },
79160
79161     /**
79162      * Fired when a node is removed from the root or one of it's children
79163      * @private
79164      * @param {Ext.data.NodeInterface} parent The parent node
79165      * @param {Ext.data.NodeInterface} node The removed node
79166      */
79167     onNodeRemove: function(parent, node) {
79168         this.unregisterNode(node, true);
79169     },
79170
79171     /**
79172      * Gets a node in this tree by its id.
79173      * @param {String} id
79174      * @return {Ext.data.NodeInterface} The match node.
79175      */
79176     getNodeById : function(id) {
79177         return this.nodeHash[id];
79178     },
79179
79180     /**
79181      * Registers a node with the tree
79182      * @private
79183      * @param {Ext.data.NodeInterface} The node to register
79184      * @param {Boolean} [includeChildren] True to unregister any child nodes
79185      */
79186     registerNode : function(node, includeChildren) {
79187         this.nodeHash[node.getId() || node.internalId] = node;
79188         if (includeChildren === true) {
79189             node.eachChild(function(child){
79190                 this.registerNode(child, true);
79191             }, this);
79192         }
79193     },
79194
79195     /**
79196      * Unregisters a node with the tree
79197      * @private
79198      * @param {Ext.data.NodeInterface} The node to unregister
79199      * @param {Boolean} [includeChildren] True to unregister any child nodes
79200      */
79201     unregisterNode : function(node, includeChildren) {
79202         delete this.nodeHash[node.getId() || node.internalId];
79203         if (includeChildren === true) {
79204             node.eachChild(function(child){
79205                 this.unregisterNode(child, true);
79206             }, this);
79207         }
79208     },
79209
79210     /**
79211      * Sorts this tree
79212      * @private
79213      * @param {Function} sorterFn The function to use for sorting
79214      * @param {Boolean} recursive True to perform recursive sorting
79215      */
79216     sort: function(sorterFn, recursive) {
79217         this.getRootNode().sort(sorterFn, recursive);
79218     },
79219
79220      /**
79221      * Filters this tree
79222      * @private
79223      * @param {Function} sorterFn The function to use for filtering
79224      * @param {Boolean} recursive True to perform recursive filtering
79225      */
79226     filter: function(filters, recursive) {
79227         this.getRootNode().filter(filters, recursive);
79228     }
79229 });
79230 /**
79231  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
79232  * It provides convenience methods for loading nodes, as well as the ability to use
79233  * the hierarchical tree structure combined with a store. This class is generally used
79234  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
79235  * the Tree for convenience.
79236  *
79237  * # Using Models
79238  *
79239  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
79240  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
79241  * in the {@link Ext.data.NodeInterface} documentation.
79242  *
79243  * # Reading Nested Data
79244  *
79245  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
79246  * so the reader can find nested data for each node. If a root is not specified, it will default to
79247  * 'children'.
79248  */
79249 Ext.define('Ext.data.TreeStore', {
79250     extend: 'Ext.data.AbstractStore',
79251     alias: 'store.tree',
79252     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
79253
79254     /**
79255      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
79256      * The root node for this store. For example:
79257      *
79258      *     root: {
79259      *         expanded: true,
79260      *         text: "My Root",
79261      *         children: [
79262      *             { text: "Child 1", leaf: true },
79263      *             { text: "Child 2", expanded: true, children: [
79264      *                 { text: "GrandChild", leaf: true }
79265      *             ] }
79266      *         ]
79267      *     }
79268      *
79269      * Setting the `root` config option is the same as calling {@link #setRootNode}.
79270      */
79271
79272     /**
79273      * @cfg {Boolean} clearOnLoad
79274      * Remove previously existing child nodes before loading. Default to true.
79275      */
79276     clearOnLoad : true,
79277
79278     /**
79279      * @cfg {String} nodeParam
79280      * The name of the parameter sent to the server which contains the identifier of the node.
79281      * Defaults to 'node'.
79282      */
79283     nodeParam: 'node',
79284
79285     /**
79286      * @cfg {String} defaultRootId
79287      * The default root id. Defaults to 'root'
79288      */
79289     defaultRootId: 'root',
79290
79291     /**
79292      * @cfg {String} defaultRootProperty
79293      * The root property to specify on the reader if one is not explicitly defined.
79294      */
79295     defaultRootProperty: 'children',
79296
79297     /**
79298      * @cfg {Boolean} folderSort
79299      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
79300      */
79301     folderSort: false,
79302
79303     constructor: function(config) {
79304         var me = this,
79305             root,
79306             fields;
79307
79308         config = Ext.apply({}, config);
79309
79310         /**
79311          * If we have no fields declare for the store, add some defaults.
79312          * These will be ignored if a model is explicitly specified.
79313          */
79314         fields = config.fields || me.fields;
79315         if (!fields) {
79316             config.fields = [{name: 'text', type: 'string'}];
79317         }
79318
79319         me.callParent([config]);
79320
79321         // We create our data tree.
79322         me.tree = Ext.create('Ext.data.Tree');
79323
79324         me.relayEvents(me.tree, [
79325             /**
79326              * @event append
79327              * @alias Ext.data.Tree#append
79328              */
79329             "append",
79330
79331             /**
79332              * @event remove
79333              * @alias Ext.data.Tree#remove
79334              */
79335             "remove",
79336
79337             /**
79338              * @event move
79339              * @alias Ext.data.Tree#move
79340              */
79341             "move",
79342
79343             /**
79344              * @event insert
79345              * @alias Ext.data.Tree#insert
79346              */
79347             "insert",
79348
79349             /**
79350              * @event beforeappend
79351              * @alias Ext.data.Tree#beforeappend
79352              */
79353             "beforeappend",
79354
79355             /**
79356              * @event beforeremove
79357              * @alias Ext.data.Tree#beforeremove
79358              */
79359             "beforeremove",
79360
79361             /**
79362              * @event beforemove
79363              * @alias Ext.data.Tree#beforemove
79364              */
79365             "beforemove",
79366
79367             /**
79368              * @event beforeinsert
79369              * @alias Ext.data.Tree#beforeinsert
79370              */
79371             "beforeinsert",
79372
79373              /**
79374               * @event expand
79375               * @alias Ext.data.Tree#expand
79376               */
79377              "expand",
79378
79379              /**
79380               * @event collapse
79381               * @alias Ext.data.Tree#collapse
79382               */
79383              "collapse",
79384
79385              /**
79386               * @event beforeexpand
79387               * @alias Ext.data.Tree#beforeexpand
79388               */
79389              "beforeexpand",
79390
79391              /**
79392               * @event beforecollapse
79393               * @alias Ext.data.Tree#beforecollapse
79394               */
79395              "beforecollapse",
79396
79397              /**
79398               * @event rootchange
79399               * @alias Ext.data.Tree#rootchange
79400               */
79401              "rootchange"
79402         ]);
79403
79404         me.tree.on({
79405             scope: me,
79406             remove: me.onNodeRemove,
79407             // this event must follow the relay to beforeitemexpand to allow users to
79408             // cancel the expand:
79409             beforeexpand: me.onBeforeNodeExpand,
79410             beforecollapse: me.onBeforeNodeCollapse,
79411             append: me.onNodeAdded,
79412             insert: me.onNodeAdded
79413         });
79414
79415         me.onBeforeSort();
79416
79417         root = me.root;
79418         if (root) {
79419             delete me.root;
79420             me.setRootNode(root);
79421         }
79422
79423         me.addEvents(
79424             /**
79425              * @event sort
79426              * Fires when this TreeStore is sorted.
79427              * @param {Ext.data.NodeInterface} node The node that is sorted.
79428              */
79429             'sort'
79430         );
79431
79432         if (Ext.isDefined(me.nodeParameter)) {
79433             if (Ext.isDefined(Ext.global.console)) {
79434                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
79435             }
79436             me.nodeParam = me.nodeParameter;
79437             delete me.nodeParameter;
79438         }
79439     },
79440
79441     // inherit docs
79442     setProxy: function(proxy) {
79443         var reader,
79444             needsRoot;
79445
79446         if (proxy instanceof Ext.data.proxy.Proxy) {
79447             // proxy instance, check if a root was set
79448             needsRoot = Ext.isEmpty(proxy.getReader().root);
79449         } else if (Ext.isString(proxy)) {
79450             // string type, means a reader can't be set
79451             needsRoot = true;
79452         } else {
79453             // object, check if a reader and a root were specified.
79454             reader = proxy.reader;
79455             needsRoot = !(reader && !Ext.isEmpty(reader.root));
79456         }
79457         proxy = this.callParent(arguments);
79458         if (needsRoot) {
79459             reader = proxy.getReader();
79460             reader.root = this.defaultRootProperty;
79461             // force rebuild
79462             reader.buildExtractors(true);
79463         }
79464     },
79465
79466     // inherit docs
79467     onBeforeSort: function() {
79468         if (this.folderSort) {
79469             this.sort({
79470                 property: 'leaf',
79471                 direction: 'ASC'
79472             }, 'prepend', false);
79473         }
79474     },
79475
79476     /**
79477      * Called before a node is expanded.
79478      * @private
79479      * @param {Ext.data.NodeInterface} node The node being expanded.
79480      * @param {Function} callback The function to run after the expand finishes
79481      * @param {Object} scope The scope in which to run the callback function
79482      */
79483     onBeforeNodeExpand: function(node, callback, scope) {
79484         if (node.isLoaded()) {
79485             Ext.callback(callback, scope || node, [node.childNodes]);
79486         }
79487         else if (node.isLoading()) {
79488             this.on('load', function() {
79489                 Ext.callback(callback, scope || node, [node.childNodes]);
79490             }, this, {single: true});
79491         }
79492         else {
79493             this.read({
79494                 node: node,
79495                 callback: function() {
79496                     Ext.callback(callback, scope || node, [node.childNodes]);
79497                 }
79498             });
79499         }
79500     },
79501
79502     //inherit docs
79503     getNewRecords: function() {
79504         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
79505     },
79506
79507     //inherit docs
79508     getUpdatedRecords: function() {
79509         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
79510     },
79511
79512     /**
79513      * Called before a node is collapsed.
79514      * @private
79515      * @param {Ext.data.NodeInterface} node The node being collapsed.
79516      * @param {Function} callback The function to run after the collapse finishes
79517      * @param {Object} scope The scope in which to run the callback function
79518      */
79519     onBeforeNodeCollapse: function(node, callback, scope) {
79520         callback.call(scope || node, node.childNodes);
79521     },
79522
79523     onNodeRemove: function(parent, node) {
79524         var removed = this.removed;
79525
79526         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
79527             removed.push(node);
79528         }
79529     },
79530
79531     onNodeAdded: function(parent, node) {
79532         var proxy = this.getProxy(),
79533             reader = proxy.getReader(),
79534             data = node.raw || node.data,
79535             dataRoot, children;
79536
79537         Ext.Array.remove(this.removed, node);
79538
79539         if (!node.isLeaf() && !node.isLoaded()) {
79540             dataRoot = reader.getRoot(data);
79541             if (dataRoot) {
79542                 this.fillNode(node, reader.extractData(dataRoot));
79543                 delete data[reader.root];
79544             }
79545         }
79546     },
79547
79548     /**
79549      * Sets the root node for this store.  See also the {@link #root} config option.
79550      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
79551      * @return {Ext.data.NodeInterface} The new root
79552      */
79553     setRootNode: function(root) {
79554         var me = this;
79555
79556         root = root || {};
79557         if (!root.isNode) {
79558             // create a default rootNode and create internal data struct.
79559             Ext.applyIf(root, {
79560                 id: me.defaultRootId,
79561                 text: 'Root',
79562                 allowDrag: false
79563             });
79564             root = Ext.ModelManager.create(root, me.model);
79565         }
79566         Ext.data.NodeInterface.decorate(root);
79567
79568         // Because we have decorated the model with new fields,
79569         // we need to build new extactor functions on the reader.
79570         me.getProxy().getReader().buildExtractors(true);
79571
79572         // When we add the root to the tree, it will automaticaly get the NodeInterface
79573         me.tree.setRootNode(root);
79574
79575         // If the user has set expanded: true on the root, we want to call the expand function
79576         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
79577             me.load({
79578                 node: root
79579             });
79580         }
79581
79582         return root;
79583     },
79584
79585     /**
79586      * Returns the root node for this tree.
79587      * @return {Ext.data.NodeInterface}
79588      */
79589     getRootNode: function() {
79590         return this.tree.getRootNode();
79591     },
79592
79593     /**
79594      * Returns the record node by id
79595      * @return {Ext.data.NodeInterface}
79596      */
79597     getNodeById: function(id) {
79598         return this.tree.getNodeById(id);
79599     },
79600
79601     /**
79602      * Loads the Store using its configured {@link #proxy}.
79603      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
79604      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
79605      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
79606      * default to the root node.
79607      */
79608     load: function(options) {
79609         options = options || {};
79610         options.params = options.params || {};
79611
79612         var me = this,
79613             node = options.node || me.tree.getRootNode(),
79614             root;
79615
79616         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
79617         // create one for them.
79618         if (!node) {
79619             node = me.setRootNode({
79620                 expanded: true
79621             });
79622         }
79623
79624         if (me.clearOnLoad) {
79625             node.removeAll(true);
79626         }
79627
79628         Ext.applyIf(options, {
79629             node: node
79630         });
79631         options.params[me.nodeParam] = node ? node.getId() : 'root';
79632
79633         if (node) {
79634             node.set('loading', true);
79635         }
79636
79637         return me.callParent([options]);
79638     },
79639
79640
79641     /**
79642      * Fills a node with a series of child records.
79643      * @private
79644      * @param {Ext.data.NodeInterface} node The node to fill
79645      * @param {Ext.data.Model[]} records The records to add
79646      */
79647     fillNode: function(node, records) {
79648         var me = this,
79649             ln = records ? records.length : 0,
79650             i = 0, sortCollection;
79651
79652         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
79653             sortCollection = Ext.create('Ext.util.MixedCollection');
79654             sortCollection.addAll(records);
79655             sortCollection.sort(me.sorters.items);
79656             records = sortCollection.items;
79657         }
79658
79659         node.set('loaded', true);
79660         for (; i < ln; i++) {
79661             node.appendChild(records[i], undefined, true);
79662         }
79663
79664         return records;
79665     },
79666
79667     // inherit docs
79668     onProxyLoad: function(operation) {
79669         var me = this,
79670             successful = operation.wasSuccessful(),
79671             records = operation.getRecords(),
79672             node = operation.node;
79673
79674         me.loading = false;
79675         node.set('loading', false);
79676         if (successful) {
79677             records = me.fillNode(node, records);
79678         }
79679         // The load event has an extra node parameter
79680         // (differing from the load event described in AbstractStore)
79681         /**
79682          * @event load
79683          * Fires whenever the store reads data from a remote data source.
79684          * @param {Ext.data.TreeStore} this
79685          * @param {Ext.data.NodeInterface} node The node that was loaded.
79686          * @param {Ext.data.Model[]} records An array of records.
79687          * @param {Boolean} successful True if the operation was successful.
79688          */
79689         // deprecate read?
79690         me.fireEvent('read', me, operation.node, records, successful);
79691         me.fireEvent('load', me, operation.node, records, successful);
79692         //this is a callback that would have been passed to the 'read' function and is optional
79693         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
79694     },
79695
79696     /**
79697      * Creates any new records when a write is returned from the server.
79698      * @private
79699      * @param {Ext.data.Model[]} records The array of new records
79700      * @param {Ext.data.Operation} operation The operation that just completed
79701      * @param {Boolean} success True if the operation was successful
79702      */
79703     onCreateRecords: function(records, operation, success) {
79704         if (success) {
79705             var i = 0,
79706                 length = records.length,
79707                 originalRecords = operation.records,
79708                 parentNode,
79709                 record,
79710                 original,
79711                 index;
79712
79713             /*
79714              * Loop over each record returned from the server. Assume they are
79715              * returned in order of how they were sent. If we find a matching
79716              * record, replace it with the newly created one.
79717              */
79718             for (; i < length; ++i) {
79719                 record = records[i];
79720                 original = originalRecords[i];
79721                 if (original) {
79722                     parentNode = original.parentNode;
79723                     if (parentNode) {
79724                         // prevent being added to the removed cache
79725                         original.isReplace = true;
79726                         parentNode.replaceChild(record, original);
79727                         delete original.isReplace;
79728                     }
79729                     record.phantom = false;
79730                 }
79731             }
79732         }
79733     },
79734
79735     /**
79736      * Updates any records when a write is returned from the server.
79737      * @private
79738      * @param {Ext.data.Model[]} records The array of updated records
79739      * @param {Ext.data.Operation} operation The operation that just completed
79740      * @param {Boolean} success True if the operation was successful
79741      */
79742     onUpdateRecords: function(records, operation, success){
79743         if (success) {
79744             var me = this,
79745                 i = 0,
79746                 length = records.length,
79747                 data = me.data,
79748                 original,
79749                 parentNode,
79750                 record;
79751
79752             for (; i < length; ++i) {
79753                 record = records[i];
79754                 original = me.tree.getNodeById(record.getId());
79755                 parentNode = original.parentNode;
79756                 if (parentNode) {
79757                     // prevent being added to the removed cache
79758                     original.isReplace = true;
79759                     parentNode.replaceChild(record, original);
79760                     original.isReplace = false;
79761                 }
79762             }
79763         }
79764     },
79765
79766     /**
79767      * Removes any records when a write is returned from the server.
79768      * @private
79769      * @param {Ext.data.Model[]} records The array of removed records
79770      * @param {Ext.data.Operation} operation The operation that just completed
79771      * @param {Boolean} success True if the operation was successful
79772      */
79773     onDestroyRecords: function(records, operation, success){
79774         if (success) {
79775             this.removed = [];
79776         }
79777     },
79778
79779     // inherit docs
79780     removeAll: function() {
79781         this.getRootNode().destroy(true);
79782         this.fireEvent('clear', this);
79783     },
79784
79785     // inherit docs
79786     doSort: function(sorterFn) {
79787         var me = this;
79788         if (me.remoteSort) {
79789             //the load function will pick up the new sorters and request the sorted data from the proxy
79790             me.load();
79791         } else {
79792             me.tree.sort(sorterFn, true);
79793             me.fireEvent('datachanged', me);
79794         }
79795         me.fireEvent('sort', me);
79796     }
79797 });
79798
79799 /**
79800  * @extend Ext.data.IdGenerator
79801  * @author Don Griffin
79802  *
79803  * This class generates UUID's according to RFC 4122. This class has a default id property.
79804  * This means that a single instance is shared unless the id property is overridden. Thus,
79805  * two {@link Ext.data.Model} instances configured like the following share one generator:
79806  *
79807  *     Ext.define('MyApp.data.MyModelX', {
79808  *         extend: 'Ext.data.Model',
79809  *         idgen: 'uuid'
79810  *     });
79811  *
79812  *     Ext.define('MyApp.data.MyModelY', {
79813  *         extend: 'Ext.data.Model',
79814  *         idgen: 'uuid'
79815  *     });
79816  *
79817  * This allows all models using this class to share a commonly configured instance.
79818  *
79819  * # Using Version 1 ("Sequential") UUID's
79820  *
79821  * If a server can provide a proper timestamp and a "cryptographic quality random number"
79822  * (as described in RFC 4122), the shared instance can be configured as follows:
79823  *
79824  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79825  *         version: 1,
79826  *         clockSeq: clock, // 14 random bits
79827  *         salt: salt,      // 48 secure random bits (the Node field)
79828  *         timestamp: ts    // timestamp per Section 4.1.4
79829  *     });
79830  *
79831  *     // or these values can be split into 32-bit chunks:
79832  *
79833  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79834  *         version: 1,
79835  *         clockSeq: clock,
79836  *         salt: { lo: saltLow32, hi: saltHigh32 },
79837  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
79838  *     });
79839  *
79840  * This approach improves the generator's uniqueness by providing a valid timestamp and
79841  * higher quality random data. Version 1 UUID's should not be used unless this information
79842  * can be provided by a server and care should be taken to avoid caching of this data.
79843  *
79844  * See http://www.ietf.org/rfc/rfc4122.txt for details.
79845  */
79846 Ext.define('Ext.data.UuidGenerator', function () {
79847     var twoPow14 = Math.pow(2, 14),
79848         twoPow16 = Math.pow(2, 16),
79849         twoPow28 = Math.pow(2, 28),
79850         twoPow32 = Math.pow(2, 32);
79851
79852     function toHex (value, length) {
79853         var ret = value.toString(16);
79854         if (ret.length > length) {
79855             ret = ret.substring(ret.length - length); // right-most digits
79856         } else if (ret.length < length) {
79857             ret = Ext.String.leftPad(ret, length, '0');
79858         }
79859         return ret;
79860     }
79861
79862     function rand (lo, hi) {
79863         var v = Math.random() * (hi - lo + 1);
79864         return Math.floor(v) + lo;
79865     }
79866
79867     function split (bignum) {
79868         if (typeof(bignum) == 'number') {
79869             var hi = Math.floor(bignum / twoPow32);
79870             return {
79871                 lo: Math.floor(bignum - hi * twoPow32),
79872                 hi: hi
79873             };
79874         }
79875         return bignum;
79876     }
79877
79878     return {
79879         extend: 'Ext.data.IdGenerator',
79880
79881         alias: 'idgen.uuid',
79882
79883         id: 'uuid', // shared by default
79884
79885         /**
79886          * @property {Number/Object} salt
79887          * When created, this value is a 48-bit number. For computation, this value is split
79888          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79889          */
79890
79891         /**
79892          * @property {Number/Object} timestamp
79893          * When created, this value is a 60-bit number. For computation, this value is split
79894          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79895          */
79896
79897         /**
79898          * @cfg {Number} version
79899          * The Version of UUID. Supported values are:
79900          *
79901          *  * 1 : Time-based, "sequential" UUID.
79902          *  * 4 : Pseudo-random UUID.
79903          *
79904          * The default is 4.
79905          */
79906         version: 4,
79907
79908         constructor: function() {
79909             var me = this;
79910
79911             me.callParent(arguments);
79912
79913             me.parts = [];
79914             me.init();
79915         },
79916
79917         generate: function () {
79918             var me = this,
79919                 parts = me.parts,
79920                 ts = me.timestamp;
79921
79922             /*
79923                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
79924
79925                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79926                |                          time_low                             |
79927                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79928                |           time_mid            |  ver  |        time_hi        |
79929                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79930                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
79931                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79932                |                         salt (2-5)                            |
79933                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79934
79935                          time_mid      clock_hi (low 6 bits)
79936                 time_low     | time_hi |clock_lo
79937                     |        |     |   || salt[0]
79938                     |        |     |   ||   | salt[1..5]
79939                     v        v     v   vv   v v
79940                     0badf00d-aced-1def-b123-dfad0badbeef
79941                                   ^    ^     ^
79942                             version    |     multicast (low bit)
79943                                        |
79944                                     reserved (upper 2 bits)
79945             */
79946             parts[0] = toHex(ts.lo, 8);
79947             parts[1] = toHex(ts.hi & 0xFFFF, 4);
79948             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
79949             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
79950                        toHex(me.clockSeq & 0xFF, 2);
79951             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
79952
79953             if (me.version == 4) {
79954                 me.init(); // just regenerate all the random values...
79955             } else {
79956                 // sequentially increment the timestamp...
79957                 ++ts.lo;
79958                 if (ts.lo >= twoPow32) { // if (overflow)
79959                     ts.lo = 0;
79960                     ++ts.hi;
79961                 }
79962             }
79963
79964             return parts.join('-').toLowerCase();
79965         },
79966
79967         getRecId: function (rec) {
79968             return rec.getId();
79969         },
79970
79971         /**
79972          * @private
79973          */
79974         init: function () {
79975             var me = this,
79976                 salt, time;
79977
79978             if (me.version == 4) {
79979                 // See RFC 4122 (Secion 4.4)
79980                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
79981                 //      or the saved node ID is different than the current node ID,
79982                 //      generate a random clock sequence value.
79983                 me.clockSeq = rand(0, twoPow14-1);
79984
79985                 // we run this on every id generation...
79986                 salt = me.salt || (me.salt = {});
79987                 time = me.timestamp || (me.timestamp = {});
79988
79989                 // See RFC 4122 (Secion 4.4)
79990                 salt.lo = rand(0, twoPow32-1);
79991                 salt.hi = rand(0, twoPow16-1);
79992                 time.lo = rand(0, twoPow32-1);
79993                 time.hi = rand(0, twoPow28-1);
79994             } else {
79995                 // this is run only once per-instance
79996                 me.salt = split(me.salt);
79997                 me.timestamp = split(me.timestamp);
79998
79999                 // Set multicast bit: "the least significant bit of the first octet of the
80000                 // node ID" (nodeId = salt for this implementation):
80001                 me.salt.hi |= 0x100;
80002             }
80003         },
80004
80005         /**
80006          * Reconfigures this generator given new config properties.
80007          */
80008         reconfigure: function (config) {
80009             Ext.apply(this, config);
80010             this.init();
80011         }
80012     };
80013 }());
80014
80015 /**
80016  * @author Ed Spencer
80017  * @class Ext.data.XmlStore
80018  * @extends Ext.data.Store
80019  * @private
80020  * @ignore
80021  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
80022  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
80023  * <p>A store configuration would be something like:<pre><code>
80024 var store = new Ext.data.XmlStore({
80025     // store configs
80026     autoDestroy: true,
80027     storeId: 'myStore',
80028     url: 'sheldon.xml', // automatically configures a HttpProxy
80029     // reader configs
80030     record: 'Item', // records will have an "Item" tag
80031     idPath: 'ASIN',
80032     totalRecords: '@TotalResults'
80033     fields: [
80034         // set up the fields mapping into the xml doc
80035         // The first needs mapping, the others are very basic
80036         {name: 'Author', mapping: 'ItemAttributes > Author'},
80037         'Title', 'Manufacturer', 'ProductGroup'
80038     ]
80039 });
80040  * </code></pre></p>
80041  * <p>This store is configured to consume a returned object of the form:<pre><code>
80042 &#60?xml version="1.0" encoding="UTF-8"?>
80043 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
80044     &#60Items>
80045         &#60Request>
80046             &#60IsValid>True&#60/IsValid>
80047             &#60ItemSearchRequest>
80048                 &#60Author>Sidney Sheldon&#60/Author>
80049                 &#60SearchIndex>Books&#60/SearchIndex>
80050             &#60/ItemSearchRequest>
80051         &#60/Request>
80052         &#60TotalResults>203&#60/TotalResults>
80053         &#60TotalPages>21&#60/TotalPages>
80054         &#60Item>
80055             &#60ASIN>0446355453&#60/ASIN>
80056             &#60DetailPageURL>
80057                 http://www.amazon.com/
80058             &#60/DetailPageURL>
80059             &#60ItemAttributes>
80060                 &#60Author>Sidney Sheldon&#60/Author>
80061                 &#60Manufacturer>Warner Books&#60/Manufacturer>
80062                 &#60ProductGroup>Book&#60/ProductGroup>
80063                 &#60Title>Master of the Game&#60/Title>
80064             &#60/ItemAttributes>
80065         &#60/Item>
80066     &#60/Items>
80067 &#60/ItemSearchResponse>
80068  * </code></pre>
80069  * An object literal of this form could also be used as the {@link #data} config option.</p>
80070  * <p><b>Note:</b> This class accepts all of the configuration options of
80071  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
80072  * @xtype xmlstore
80073  */
80074 Ext.define('Ext.data.XmlStore', {
80075     extend: 'Ext.data.Store',
80076     alternateClassName: 'Ext.data.XmlStore',
80077     alias: 'store.xml',
80078
80079     /**
80080      * @cfg {Ext.data.DataReader} reader @hide
80081      */
80082     constructor: function(config){
80083         config = config || {};
80084         config = config || {};
80085
80086         Ext.applyIf(config, {
80087             proxy: {
80088                 type: 'ajax',
80089                 reader: 'xml',
80090                 writer: 'xml'
80091             }
80092         });
80093
80094         this.callParent([config]);
80095     }
80096 });
80097
80098 /**
80099  * @author Ed Spencer
80100  *
80101  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
80102  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
80103  * @private
80104  */
80105 Ext.define('Ext.data.proxy.Client', {
80106     extend: 'Ext.data.proxy.Proxy',
80107     alternateClassName: 'Ext.data.ClientProxy',
80108
80109     /**
80110      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
80111      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
80112      */
80113     clear: function() {
80114     }
80115 });
80116 /**
80117  * @author Ed Spencer
80118  *
80119  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
80120  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
80121  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
80122  *
80123  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
80124  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
80125  * injected might look like this:
80126  *
80127  *     <script src="http://domainB.com/users?callback=someCallback"></script>
80128  *
80129  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
80130  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
80131  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
80132  * long as the server formats the response to look like this, everything will work:
80133  *
80134  *     someCallback({
80135  *         users: [
80136  *             {
80137  *                 id: 1,
80138  *                 name: "Ed Spencer",
80139  *                 email: "ed@sencha.com"
80140  *             }
80141  *         ]
80142  *     });
80143  *
80144  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
80145  * object that the server returned.
80146  *
80147  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
80148  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
80149  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
80150  * we might set that up:
80151  *
80152  *     Ext.define('User', {
80153  *         extend: 'Ext.data.Model',
80154  *         fields: ['id', 'name', 'email']
80155  *     });
80156  *
80157  *     var store = Ext.create('Ext.data.Store', {
80158  *         model: 'User',
80159  *         proxy: {
80160  *             type: 'jsonp',
80161  *             url : 'http://domainB.com/users'
80162  *         }
80163  *     });
80164  *
80165  *     store.load();
80166  *
80167  * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have injected a script tag
80168  * like this:
80169  *
80170  *     <script src="http://domainB.com/users?callback=callback1"></script>
80171  *
80172  * # Customization
80173  *
80174  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
80175  *
80176  *     var store = Ext.create('Ext.data.Store', {
80177  *         model: 'User',
80178  *         proxy: {
80179  *             type: 'jsonp',
80180  *             url : 'http://domainB.com/users',
80181  *             callbackKey: 'theCallbackFunction'
80182  *         }
80183  *     });
80184  *
80185  *     store.load();
80186  *
80187  * Would inject a script tag like this:
80188  *
80189  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
80190  *
80191  * # Implementing on the server side
80192  *
80193  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
80194  * achieve this using Java, PHP and ASP.net:
80195  *
80196  * Java:
80197  *
80198  *     boolean jsonP = false;
80199  *     String cb = request.getParameter("callback");
80200  *     if (cb != null) {
80201  *         jsonP = true;
80202  *         response.setContentType("text/javascript");
80203  *     } else {
80204  *         response.setContentType("application/x-json");
80205  *     }
80206  *     Writer out = response.getWriter();
80207  *     if (jsonP) {
80208  *         out.write(cb + "(");
80209  *     }
80210  *     out.print(dataBlock.toJsonString());
80211  *     if (jsonP) {
80212  *         out.write(");");
80213  *     }
80214  *
80215  * PHP:
80216  *
80217  *     $callback = $_REQUEST['callback'];
80218  *
80219  *     // Create the output object.
80220  *     $output = array('a' => 'Apple', 'b' => 'Banana');
80221  *
80222  *     //start output
80223  *     if ($callback) {
80224  *         header('Content-Type: text/javascript');
80225  *         echo $callback . '(' . json_encode($output) . ');';
80226  *     } else {
80227  *         header('Content-Type: application/x-json');
80228  *         echo json_encode($output);
80229  *     }
80230  *
80231  * ASP.net:
80232  *
80233  *     String jsonString = "{success: true}";
80234  *     String cb = Request.Params.Get("callback");
80235  *     String responseString = "";
80236  *     if (!String.IsNullOrEmpty(cb)) {
80237  *         responseString = cb + "(" + jsonString + ")";
80238  *     } else {
80239  *         responseString = jsonString;
80240  *     }
80241  *     Response.Write(responseString);
80242  */
80243 Ext.define('Ext.data.proxy.JsonP', {
80244     extend: 'Ext.data.proxy.Server',
80245     alternateClassName: 'Ext.data.ScriptTagProxy',
80246     alias: ['proxy.jsonp', 'proxy.scripttag'],
80247     requires: ['Ext.data.JsonP'],
80248
80249     defaultWriterType: 'base',
80250
80251     /**
80252      * @cfg {String} callbackKey
80253      * See {@link Ext.data.JsonP#callbackKey}.
80254      */
80255     callbackKey : 'callback',
80256
80257     /**
80258      * @cfg {String} recordParam
80259      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
80260      * 'records'
80261      */
80262     recordParam: 'records',
80263
80264     /**
80265      * @cfg {Boolean} autoAppendParams
80266      * True to automatically append the request's params to the generated url. Defaults to true
80267      */
80268     autoAppendParams: true,
80269
80270     constructor: function(){
80271         this.addEvents(
80272             /**
80273              * @event
80274              * Fires when the server returns an exception
80275              * @param {Ext.data.proxy.Proxy} this
80276              * @param {Ext.data.Request} request The request that was sent
80277              * @param {Ext.data.Operation} operation The operation that triggered the request
80278              */
80279             'exception'
80280         );
80281         this.callParent(arguments);
80282     },
80283
80284     /**
80285      * @private
80286      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
80287      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
80288      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
80289      * @param {Function} callback A callback function to execute when the Operation has been completed
80290      * @param {Object} scope The scope to execute the callback in
80291      */
80292     doRequest: function(operation, callback, scope) {
80293         //generate the unique IDs for this request
80294         var me      = this,
80295             writer  = me.getWriter(),
80296             request = me.buildRequest(operation),
80297             params = request.params;
80298
80299         if (operation.allowWrite()) {
80300             request = writer.write(request);
80301         }
80302
80303         // apply JsonP proxy-specific attributes to the Request
80304         Ext.apply(request, {
80305             callbackKey: me.callbackKey,
80306             timeout: me.timeout,
80307             scope: me,
80308             disableCaching: false, // handled by the proxy
80309             callback: me.createRequestCallback(request, operation, callback, scope)
80310         });
80311
80312         // prevent doubling up
80313         if (me.autoAppendParams) {
80314             request.params = {};
80315         }
80316
80317         request.jsonp = Ext.data.JsonP.request(request);
80318         // restore on the request
80319         request.params = params;
80320         operation.setStarted();
80321         me.lastRequest = request;
80322
80323         return request;
80324     },
80325
80326     /**
80327      * @private
80328      * Creates and returns the function that is called when the request has completed. The returned function
80329      * should accept a Response object, which contains the response to be read by the configured Reader.
80330      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
80331      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
80332      * theCallback refers to the callback argument received by this function.
80333      * See {@link #doRequest} for details.
80334      * @param {Ext.data.Request} request The Request object
80335      * @param {Ext.data.Operation} operation The Operation being executed
80336      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
80337      * passed to doRequest
80338      * @param {Object} scope The scope in which to execute the callback function
80339      * @return {Function} The callback function
80340      */
80341     createRequestCallback: function(request, operation, callback, scope) {
80342         var me = this;
80343
80344         return function(success, response, errorType) {
80345             delete me.lastRequest;
80346             me.processResponse(success, operation, request, response, callback, scope);
80347         };
80348     },
80349
80350     // inherit docs
80351     setException: function(operation, response) {
80352         operation.setException(operation.request.jsonp.errorType);
80353     },
80354
80355
80356     /**
80357      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
80358      * @param {Ext.data.Request} request The request object
80359      * @return {String} The url
80360      */
80361     buildUrl: function(request) {
80362         var me      = this,
80363             url     = me.callParent(arguments),
80364             params  = Ext.apply({}, request.params),
80365             filters = params.filters,
80366             records,
80367             filter, i;
80368
80369         delete params.filters;
80370
80371         if (me.autoAppendParams) {
80372             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
80373         }
80374
80375         if (filters && filters.length) {
80376             for (i = 0; i < filters.length; i++) {
80377                 filter = filters[i];
80378
80379                 if (filter.value) {
80380                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
80381                 }
80382             }
80383         }
80384
80385         //if there are any records present, append them to the url also
80386         records = request.records;
80387
80388         if (Ext.isArray(records) && records.length > 0) {
80389             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
80390         }
80391
80392         return url;
80393     },
80394
80395     //inherit docs
80396     destroy: function() {
80397         this.abort();
80398         this.callParent();
80399     },
80400
80401     /**
80402      * Aborts the current server request if one is currently running
80403      */
80404     abort: function() {
80405         var lastRequest = this.lastRequest;
80406         if (lastRequest) {
80407             Ext.data.JsonP.abort(lastRequest.jsonp);
80408         }
80409     },
80410
80411     /**
80412      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
80413      * its own function so that it can be easily overridden.
80414      * @param {Ext.data.Model[]} records The records array
80415      * @return {String} The encoded records string
80416      */
80417     encodeRecords: function(records) {
80418         var encoded = "",
80419             i = 0,
80420             len = records.length;
80421
80422         for (; i < len; i++) {
80423             encoded += Ext.Object.toQueryString(records[i].data);
80424         }
80425
80426         return encoded;
80427     }
80428 });
80429
80430 /**
80431  * @author Ed Spencer
80432  *
80433  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
80434  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
80435  * save {@link Ext.data.Model model instances} for offline use.
80436  * @private
80437  */
80438 Ext.define('Ext.data.proxy.WebStorage', {
80439     extend: 'Ext.data.proxy.Client',
80440     alternateClassName: 'Ext.data.WebStorageProxy',
80441
80442     /**
80443      * @cfg {String} id
80444      * The unique ID used as the key in which all record data are stored in the local storage object.
80445      */
80446     id: undefined,
80447
80448     /**
80449      * Creates the proxy, throws an error if local storage is not supported in the current browser.
80450      * @param {Object} config (optional) Config object.
80451      */
80452     constructor: function(config) {
80453         this.callParent(arguments);
80454
80455         /**
80456          * @property {Object} cache
80457          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
80458          */
80459         this.cache = {};
80460
80461
80462         //if an id is not given, try to use the store's id instead
80463         this.id = this.id || (this.store ? this.store.storeId : undefined);
80464
80465
80466         this.initialize();
80467     },
80468
80469     //inherit docs
80470     create: function(operation, callback, scope) {
80471         var records = operation.records,
80472             length  = records.length,
80473             ids     = this.getIds(),
80474             id, record, i;
80475
80476         operation.setStarted();
80477
80478         for (i = 0; i < length; i++) {
80479             record = records[i];
80480
80481             if (record.phantom) {
80482                 record.phantom = false;
80483                 id = this.getNextId();
80484             } else {
80485                 id = record.getId();
80486             }
80487
80488             this.setRecord(record, id);
80489             ids.push(id);
80490         }
80491
80492         this.setIds(ids);
80493
80494         operation.setCompleted();
80495         operation.setSuccessful();
80496
80497         if (typeof callback == 'function') {
80498             callback.call(scope || this, operation);
80499         }
80500     },
80501
80502     //inherit docs
80503     read: function(operation, callback, scope) {
80504         //TODO: respect sorters, filters, start and limit options on the Operation
80505
80506         var records = [],
80507             ids     = this.getIds(),
80508             length  = ids.length,
80509             i, recordData, record;
80510
80511         //read a single record
80512         if (operation.id) {
80513             record = this.getRecord(operation.id);
80514
80515             if (record) {
80516                 records.push(record);
80517                 operation.setSuccessful();
80518             }
80519         } else {
80520             for (i = 0; i < length; i++) {
80521                 records.push(this.getRecord(ids[i]));
80522             }
80523             operation.setSuccessful();
80524         }
80525
80526         operation.setCompleted();
80527
80528         operation.resultSet = Ext.create('Ext.data.ResultSet', {
80529             records: records,
80530             total  : records.length,
80531             loaded : true
80532         });
80533
80534         if (typeof callback == 'function') {
80535             callback.call(scope || this, operation);
80536         }
80537     },
80538
80539     //inherit docs
80540     update: function(operation, callback, scope) {
80541         var records = operation.records,
80542             length  = records.length,
80543             ids     = this.getIds(),
80544             record, id, i;
80545
80546         operation.setStarted();
80547
80548         for (i = 0; i < length; i++) {
80549             record = records[i];
80550             this.setRecord(record);
80551
80552             //we need to update the set of ids here because it's possible that a non-phantom record was added
80553             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
80554             id = record.getId();
80555             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
80556                 ids.push(id);
80557             }
80558         }
80559         this.setIds(ids);
80560
80561         operation.setCompleted();
80562         operation.setSuccessful();
80563
80564         if (typeof callback == 'function') {
80565             callback.call(scope || this, operation);
80566         }
80567     },
80568
80569     //inherit
80570     destroy: function(operation, callback, scope) {
80571         var records = operation.records,
80572             length  = records.length,
80573             ids     = this.getIds(),
80574
80575             //newIds is a copy of ids, from which we remove the destroyed records
80576             newIds  = [].concat(ids),
80577             i;
80578
80579         for (i = 0; i < length; i++) {
80580             Ext.Array.remove(newIds, records[i].getId());
80581             this.removeRecord(records[i], false);
80582         }
80583
80584         this.setIds(newIds);
80585
80586         operation.setCompleted();
80587         operation.setSuccessful();
80588
80589         if (typeof callback == 'function') {
80590             callback.call(scope || this, operation);
80591         }
80592     },
80593
80594     /**
80595      * @private
80596      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
80597      * @param {String} id The record's unique ID
80598      * @return {Ext.data.Model} The model instance
80599      */
80600     getRecord: function(id) {
80601         if (this.cache[id] === undefined) {
80602             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
80603                 data    = {},
80604                 Model   = this.model,
80605                 fields  = Model.prototype.fields.items,
80606                 length  = fields.length,
80607                 i, field, name, record;
80608
80609             for (i = 0; i < length; i++) {
80610                 field = fields[i];
80611                 name  = field.name;
80612
80613                 if (typeof field.decode == 'function') {
80614                     data[name] = field.decode(rawData[name]);
80615                 } else {
80616                     data[name] = rawData[name];
80617                 }
80618             }
80619
80620             record = new Model(data, id);
80621             record.phantom = false;
80622
80623             this.cache[id] = record;
80624         }
80625
80626         return this.cache[id];
80627     },
80628
80629     /**
80630      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
80631      * @param {Ext.data.Model} record The model instance
80632      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
80633      */
80634     setRecord: function(record, id) {
80635         if (id) {
80636             record.setId(id);
80637         } else {
80638             id = record.getId();
80639         }
80640
80641         var me = this,
80642             rawData = record.data,
80643             data    = {},
80644             model   = me.model,
80645             fields  = model.prototype.fields.items,
80646             length  = fields.length,
80647             i = 0,
80648             field, name, obj, key;
80649
80650         for (; i < length; i++) {
80651             field = fields[i];
80652             name  = field.name;
80653
80654             if (typeof field.encode == 'function') {
80655                 data[name] = field.encode(rawData[name], record);
80656             } else {
80657                 data[name] = rawData[name];
80658             }
80659         }
80660
80661         obj = me.getStorageObject();
80662         key = me.getRecordKey(id);
80663
80664         //keep the cache up to date
80665         me.cache[id] = record;
80666
80667         //iPad bug requires that we remove the item before setting it
80668         obj.removeItem(key);
80669         obj.setItem(key, Ext.encode(data));
80670     },
80671
80672     /**
80673      * @private
80674      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
80675      * use instead because it updates the list of currently-stored record ids
80676      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
80677      */
80678     removeRecord: function(id, updateIds) {
80679         var me = this,
80680             ids;
80681
80682         if (id.isModel) {
80683             id = id.getId();
80684         }
80685
80686         if (updateIds !== false) {
80687             ids = me.getIds();
80688             Ext.Array.remove(ids, id);
80689             me.setIds(ids);
80690         }
80691
80692         me.getStorageObject().removeItem(me.getRecordKey(id));
80693     },
80694
80695     /**
80696      * @private
80697      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
80698      * storing data in the local storage object and should prevent naming collisions.
80699      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
80700      * @return {String} The unique key for this record
80701      */
80702     getRecordKey: function(id) {
80703         if (id.isModel) {
80704             id = id.getId();
80705         }
80706
80707         return Ext.String.format("{0}-{1}", this.id, id);
80708     },
80709
80710     /**
80711      * @private
80712      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
80713      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
80714      * @return {String} The counter key
80715      */
80716     getRecordCounterKey: function() {
80717         return Ext.String.format("{0}-counter", this.id);
80718     },
80719
80720     /**
80721      * @private
80722      * Returns the array of record IDs stored in this Proxy
80723      * @return {Number[]} The record IDs. Each is cast as a Number
80724      */
80725     getIds: function() {
80726         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
80727             length = ids.length,
80728             i;
80729
80730         if (length == 1 && ids[0] === "") {
80731             ids = [];
80732         } else {
80733             for (i = 0; i < length; i++) {
80734                 ids[i] = parseInt(ids[i], 10);
80735             }
80736         }
80737
80738         return ids;
80739     },
80740
80741     /**
80742      * @private
80743      * Saves the array of ids representing the set of all records in the Proxy
80744      * @param {Number[]} ids The ids to set
80745      */
80746     setIds: function(ids) {
80747         var obj = this.getStorageObject(),
80748             str = ids.join(",");
80749
80750         obj.removeItem(this.id);
80751
80752         if (!Ext.isEmpty(str)) {
80753             obj.setItem(this.id, str);
80754         }
80755     },
80756
80757     /**
80758      * @private
80759      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
80760      * Increments the counter.
80761      * @return {Number} The id
80762      */
80763     getNextId: function() {
80764         var obj  = this.getStorageObject(),
80765             key  = this.getRecordCounterKey(),
80766             last = obj.getItem(key),
80767             ids, id;
80768
80769         if (last === null) {
80770             ids = this.getIds();
80771             last = ids[ids.length - 1] || 0;
80772         }
80773
80774         id = parseInt(last, 10) + 1;
80775         obj.setItem(key, id);
80776
80777         return id;
80778     },
80779
80780     /**
80781      * @private
80782      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
80783      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
80784      */
80785     initialize: function() {
80786         var storageObject = this.getStorageObject();
80787         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
80788     },
80789
80790     /**
80791      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
80792      * storage object.
80793      */
80794     clear: function() {
80795         var obj = this.getStorageObject(),
80796             ids = this.getIds(),
80797             len = ids.length,
80798             i;
80799
80800         //remove all the records
80801         for (i = 0; i < len; i++) {
80802             this.removeRecord(ids[i]);
80803         }
80804
80805         //remove the supporting objects
80806         obj.removeItem(this.getRecordCounterKey());
80807         obj.removeItem(this.id);
80808     },
80809
80810     /**
80811      * @private
80812      * Abstract function which should return the storage object that data will be saved to. This must be implemented
80813      * in each subclass.
80814      * @return {Object} The storage object
80815      */
80816     getStorageObject: function() {
80817     }
80818 });
80819 /**
80820  * @author Ed Spencer
80821  *
80822  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
80823  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
80824  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
80825  *
80826  * localStorage is extremely useful for saving user-specific information without needing to build server-side
80827  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
80828  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
80829  *
80830  *     Ext.define('Search', {
80831  *         fields: ['id', 'query'],
80832  *         extend: 'Ext.data.Model',
80833  *         proxy: {
80834  *             type: 'localstorage',
80835  *             id  : 'twitter-Searches'
80836  *         }
80837  *     });
80838  *
80839  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
80840  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
80841  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
80842  * LocalStorageProxy to manage the saved Search data.
80843  *
80844  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
80845  *
80846  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
80847  *     var store = Ext.create('Ext.data.Store', {
80848  *         model: "Search"
80849  *     });
80850  *
80851  *     //loads any existing Search data from localStorage
80852  *     store.load();
80853  *
80854  *     //now add some Searches
80855  *     store.add({query: 'Sencha Touch'});
80856  *     store.add({query: 'Ext JS'});
80857  *
80858  *     //finally, save our Search data to localStorage
80859  *     store.sync();
80860  *
80861  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
80862  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
80863  *
80864  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
80865  *
80866  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
80867  *     search.save();
80868  *
80869  * # Limitations
80870  *
80871  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
80872  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
80873  * object.
80874  *
80875  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
80876  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
80877  */
80878 Ext.define('Ext.data.proxy.LocalStorage', {
80879     extend: 'Ext.data.proxy.WebStorage',
80880     alias: 'proxy.localstorage',
80881     alternateClassName: 'Ext.data.LocalStorageProxy',
80882     
80883     //inherit docs
80884     getStorageObject: function() {
80885         return window.localStorage;
80886     }
80887 });
80888 /**
80889  * @author Ed Spencer
80890  *
80891  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
80892  * every page refresh.
80893  *
80894  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
80895  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
80896  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
80897  * Store:
80898  *
80899  *     //this is the model we will be using in the store
80900  *     Ext.define('User', {
80901  *         extend: 'Ext.data.Model',
80902  *         fields: [
80903  *             {name: 'id',    type: 'int'},
80904  *             {name: 'name',  type: 'string'},
80905  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
80906  *         ]
80907  *     });
80908  *
80909  *     //this data does not line up to our model fields - the phone field is called phoneNumber
80910  *     var data = {
80911  *         users: [
80912  *             {
80913  *                 id: 1,
80914  *                 name: 'Ed Spencer',
80915  *                 phoneNumber: '555 1234'
80916  *             },
80917  *             {
80918  *                 id: 2,
80919  *                 name: 'Abe Elias',
80920  *                 phoneNumber: '666 1234'
80921  *             }
80922  *         ]
80923  *     };
80924  *
80925  *     //note how we set the 'root' in the reader to match the data structure above
80926  *     var store = Ext.create('Ext.data.Store', {
80927  *         autoLoad: true,
80928  *         model: 'User',
80929  *         data : data,
80930  *         proxy: {
80931  *             type: 'memory',
80932  *             reader: {
80933  *                 type: 'json',
80934  *                 root: 'users'
80935  *             }
80936  *         }
80937  *     });
80938  */
80939 Ext.define('Ext.data.proxy.Memory', {
80940     extend: 'Ext.data.proxy.Client',
80941     alias: 'proxy.memory',
80942     alternateClassName: 'Ext.data.MemoryProxy',
80943
80944     /**
80945      * @cfg {Ext.data.Model[]} data
80946      * Optional array of Records to load into the Proxy
80947      */
80948
80949     constructor: function(config) {
80950         this.callParent([config]);
80951
80952         //ensures that the reader has been instantiated properly
80953         this.setReader(this.reader);
80954     },
80955
80956     /**
80957      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
80958      * @param {Ext.data.Operation} operation The read Operation
80959      * @param {Function} callback The callback to call when reading has completed
80960      * @param {Object} scope The scope to call the callback function in
80961      */
80962     read: function(operation, callback, scope) {
80963         var me     = this,
80964             reader = me.getReader(),
80965             result = reader.read(me.data);
80966
80967         Ext.apply(operation, {
80968             resultSet: result
80969         });
80970
80971         operation.setCompleted();
80972         operation.setSuccessful();
80973         Ext.callback(callback, scope || me, [operation]);
80974     },
80975
80976     clear: Ext.emptyFn
80977 });
80978
80979 /**
80980  * @author Ed Spencer
80981  *
80982  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
80983  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
80984  * with an inline Rest proxy
80985  *
80986  *     Ext.define('User', {
80987  *         extend: 'Ext.data.Model',
80988  *         fields: ['id', 'name', 'email'],
80989  *
80990  *         proxy: {
80991  *             type: 'rest',
80992  *             url : '/users'
80993  *         }
80994  *     });
80995  *
80996  * Now we can create a new User instance and save it via the Rest proxy. Doing this will cause the Proxy to send a POST
80997  * request to '/users':
80998  *
80999  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
81000  *
81001  *     user.save(); //POST /users
81002  *
81003  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
81004  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
81005  *
81006  *     user.save({
81007  *         success: function(user) {
81008  *             user.set('name', 'Khan Noonien Singh');
81009  *
81010  *             user.save(); //PUT /users/123
81011  *         }
81012  *     });
81013  *
81014  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
81015  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
81016  *
81017  *         user.destroy(); //DELETE /users/123
81018  *
81019  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
81020  *
81021  *     //1. Load via Store
81022  *
81023  *     //the Store automatically picks up the Proxy from the User model
81024  *     var store = Ext.create('Ext.data.Store', {
81025  *         model: 'User'
81026  *     });
81027  *
81028  *     store.load(); //GET /users
81029  *
81030  *     //2. Load directly from the Model
81031  *
81032  *     //GET /users/123
81033  *     Ext.ModelManager.getModel('User').load(123, {
81034  *         success: function(user) {
81035  *             console.log(user.getId()); //outputs 123
81036  *         }
81037  *     });
81038  *
81039  * # Url generation
81040  *
81041  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
81042  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
81043  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
81044  *
81045  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
81046  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
81047  *
81048  *     new Ext.data.proxy.Rest({
81049  *         url: '/users',
81050  *         appendId: true //default
81051  *     });
81052  *
81053  *     // Collection url: /users
81054  *     // Instance url  : /users/123
81055  *
81056  * The Rest proxy can also optionally append a format string to the end of any generated url:
81057  *
81058  *     new Ext.data.proxy.Rest({
81059  *         url: '/users',
81060  *         format: 'json'
81061  *     });
81062  *
81063  *     // Collection url: /users.json
81064  *     // Instance url  : /users/123.json
81065  *
81066  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
81067  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
81068  * an example of how to achieve this.
81069  *
81070  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
81071  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
81072  * details.
81073  *
81074  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
81075  */
81076 Ext.define('Ext.data.proxy.Rest', {
81077     extend: 'Ext.data.proxy.Ajax',
81078     alternateClassName: 'Ext.data.RestProxy',
81079     alias : 'proxy.rest',
81080     
81081     /**
81082      * @cfg {Boolean} appendId
81083      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
81084      * See Rest proxy intro docs for more details. Defaults to true.
81085      */
81086     appendId: true,
81087     
81088     /**
81089      * @cfg {String} format
81090      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
81091      * for full details. Defaults to undefined.
81092      */
81093     
81094     /**
81095      * @cfg {Boolean} batchActions
81096      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
81097      */
81098     batchActions: false,
81099     
81100     /**
81101      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
81102      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
81103      * that additional parameters like the cache buster string are appended.
81104      * @param {Object} request
81105      */
81106     buildUrl: function(request) {
81107         var me        = this,
81108             operation = request.operation,
81109             records   = operation.records || [],
81110             record    = records[0],
81111             format    = me.format,
81112             url       = me.getUrl(request),
81113             id        = record ? record.getId() : operation.id;
81114         
81115         if (me.appendId && id) {
81116             if (!url.match(/\/$/)) {
81117                 url += '/';
81118             }
81119             
81120             url += id;
81121         }
81122         
81123         if (format) {
81124             if (!url.match(/\.$/)) {
81125                 url += '.';
81126             }
81127             
81128             url += format;
81129         }
81130         
81131         request.url = url;
81132         
81133         return me.callParent(arguments);
81134     }
81135 }, function() {
81136     Ext.apply(this.prototype, {
81137         /**
81138          * @property {Object} actionMethods
81139          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
81140          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
81141          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
81142          * can be overridden instead.
81143          */
81144         actionMethods: {
81145             create : 'POST',
81146             read   : 'GET',
81147             update : 'PUT',
81148             destroy: 'DELETE'
81149         }
81150     });
81151 });
81152
81153 /**
81154  * @author Ed Spencer
81155  *
81156  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
81157  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
81158  * unique ID which is used as a key in which all record data are stored in the session storage object.
81159  *
81160  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81161  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81162  *
81163  * Proxies are almost always used with a {@link Ext.data.Store store}:
81164  *
81165  *     new Ext.data.Store({
81166  *         proxy: {
81167  *             type: 'sessionstorage',
81168  *             id  : 'myProxyKey'
81169  *         }
81170  *     });
81171  *
81172  * Alternatively you can instantiate the Proxy directly:
81173  *
81174  *     new Ext.data.proxy.SessionStorage({
81175  *         id  : 'myOtherProxyKey'
81176  *     });
81177  *
81178  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
81179  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
81180  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
81181  */
81182 Ext.define('Ext.data.proxy.SessionStorage', {
81183     extend: 'Ext.data.proxy.WebStorage',
81184     alias: 'proxy.sessionstorage',
81185     alternateClassName: 'Ext.data.SessionStorageProxy',
81186     
81187     //inherit docs
81188     getStorageObject: function() {
81189         return window.sessionStorage;
81190     }
81191 });
81192
81193 /**
81194  * @author Ed Spencer
81195  * @class Ext.data.reader.Array
81196  * @extends Ext.data.reader.Json
81197  * 
81198  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
81199  * Each element of that Array represents a row of data fields. The
81200  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
81201  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
81202  * 
81203  * <p><u>Example code:</u></p>
81204  * 
81205 <pre><code>
81206 Employee = Ext.define('Employee', {
81207     extend: 'Ext.data.Model',
81208     fields: [
81209         'id',
81210         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
81211         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
81212     ]
81213 });
81214
81215 var myReader = new Ext.data.reader.Array({
81216     model: 'Employee'
81217 }, Employee);
81218 </code></pre>
81219  * 
81220  * <p>This would consume an Array like this:</p>
81221  * 
81222 <pre><code>
81223 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
81224 </code></pre>
81225  * 
81226  * @constructor
81227  * Create a new ArrayReader
81228  * @param {Object} meta Metadata configuration options.
81229  */
81230 Ext.define('Ext.data.reader.Array', {
81231     extend: 'Ext.data.reader.Json',
81232     alternateClassName: 'Ext.data.ArrayReader',
81233     alias : 'reader.array',
81234
81235     /**
81236      * @private
81237      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
81238      * reference the correct position in the array.
81239      */
81240     buildExtractors: function() {
81241         this.callParent(arguments);
81242         
81243         var fields = this.model.prototype.fields.items,
81244             i = 0,
81245             length = fields.length,
81246             extractorFunctions = [],
81247             map;
81248         
81249         for (; i < length; i++) {
81250             map = fields[i].mapping;
81251             extractorFunctions.push(function(index) {
81252                 return function(data) {
81253                     return data[index];
81254                 };
81255             }(map !== null ? map : i));
81256         }
81257         
81258         this.extractorFunctions = extractorFunctions;
81259     }
81260 });
81261
81262 /**
81263  * @author Ed Spencer
81264  * @class Ext.data.reader.Xml
81265  * @extends Ext.data.reader.Reader
81266  *
81267  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
81268  * happens as a result of loading a Store - for example we might create something like this:</p>
81269  *
81270 <pre><code>
81271 Ext.define('User', {
81272     extend: 'Ext.data.Model',
81273     fields: ['id', 'name', 'email']
81274 });
81275
81276 var store = Ext.create('Ext.data.Store', {
81277     model: 'User',
81278     proxy: {
81279         type: 'ajax',
81280         url : 'users.xml',
81281         reader: {
81282             type: 'xml',
81283             record: 'user'
81284         }
81285     }
81286 });
81287 </code></pre>
81288  *
81289  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
81290  * not already familiar with them.</p>
81291  *
81292  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
81293  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
81294  * Store, so it is as if we passed this instead:
81295  *
81296 <pre><code>
81297 reader: {
81298     type : 'xml',
81299     model: 'User',
81300     record: 'user'
81301 }
81302 </code></pre>
81303  *
81304  * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
81305  *
81306 <pre><code>
81307 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81308 &lt;user&gt;
81309     &lt;id&gt;1&lt;/id&gt;
81310     &lt;name&gt;Ed Spencer&lt;/name&gt;
81311     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81312 &lt;/user&gt;
81313 &lt;user&gt;
81314     &lt;id&gt;2&lt;/id&gt;
81315     &lt;name&gt;Abe Elias&lt;/name&gt;
81316     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81317 &lt;/user&gt;
81318 </code></pre>
81319  *
81320  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
81321  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
81322  *
81323  * <p><u>Reading other XML formats</u></p>
81324  *
81325  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
81326  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
81327  * {@link #root} configuration to parse data that comes back like this:</p>
81328  *
81329 <pre><code>
81330 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81331 &lt;users&gt;
81332     &lt;user&gt;
81333         &lt;id&gt;1&lt;/id&gt;
81334         &lt;name&gt;Ed Spencer&lt;/name&gt;
81335         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81336     &lt;/user&gt;
81337     &lt;user&gt;
81338         &lt;id&gt;2&lt;/id&gt;
81339         &lt;name&gt;Abe Elias&lt;/name&gt;
81340         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81341     &lt;/user&gt;
81342 &lt;/users&gt;
81343 </code></pre>
81344  *
81345  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
81346  *
81347 <pre><code>
81348 reader: {
81349     type  : 'xml',
81350     root  : 'users',
81351     record: 'user'
81352 }
81353 </code></pre>
81354  *
81355  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
81356  * a larger structure, so a response like this will still work:
81357  *
81358 <pre><code>
81359 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81360 &lt;deeply&gt;
81361     &lt;nested&gt;
81362         &lt;xml&gt;
81363             &lt;users&gt;
81364                 &lt;user&gt;
81365                     &lt;id&gt;1&lt;/id&gt;
81366                     &lt;name&gt;Ed Spencer&lt;/name&gt;
81367                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81368                 &lt;/user&gt;
81369                 &lt;user&gt;
81370                     &lt;id&gt;2&lt;/id&gt;
81371                     &lt;name&gt;Abe Elias&lt;/name&gt;
81372                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81373                 &lt;/user&gt;
81374             &lt;/users&gt;
81375         &lt;/xml&gt;
81376     &lt;/nested&gt;
81377 &lt;/deeply&gt;
81378 </code></pre>
81379  *
81380  * <p><u>Response metadata</u></p>
81381  *
81382  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
81383  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
81384  * like this:</p>
81385  *
81386 <pre><code>
81387 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81388 &lt;total&gt;100&lt;/total&gt;
81389 &lt;success&gt;true&lt;/success&gt;
81390 &lt;users&gt;
81391     &lt;user&gt;
81392         &lt;id&gt;1&lt;/id&gt;
81393         &lt;name&gt;Ed Spencer&lt;/name&gt;
81394         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81395     &lt;/user&gt;
81396     &lt;user&gt;
81397         &lt;id&gt;2&lt;/id&gt;
81398         &lt;name&gt;Abe Elias&lt;/name&gt;
81399         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81400     &lt;/user&gt;
81401 &lt;/users&gt;
81402 </code></pre>
81403  *
81404  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
81405  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
81406  * options:</p>
81407  *
81408 <pre><code>
81409 reader: {
81410     type: 'xml',
81411     root: 'users',
81412     totalProperty  : 'total',
81413     successProperty: 'success'
81414 }
81415 </code></pre>
81416  *
81417  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
81418  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
81419  * returned.</p>
81420  *
81421  * <p><u>Response format</u></p>
81422  *
81423  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
81424  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
81425  * work correctly otherwise.</p>
81426  */
81427 Ext.define('Ext.data.reader.Xml', {
81428     extend: 'Ext.data.reader.Reader',
81429     alternateClassName: 'Ext.data.XmlReader',
81430     alias : 'reader.xml',
81431
81432     /**
81433      * @cfg {String} record (required)
81434      * The DomQuery path to the repeated element which contains record information.
81435      */
81436
81437     /**
81438      * @private
81439      * Creates a function to return some particular key of data from a response. The totalProperty and
81440      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
81441      * @param {String} key
81442      * @return {Function}
81443      */
81444     createAccessor: function(expr) {
81445         var me = this;
81446
81447         if (Ext.isEmpty(expr)) {
81448             return Ext.emptyFn;
81449         }
81450
81451         if (Ext.isFunction(expr)) {
81452             return expr;
81453         }
81454
81455         return function(root) {
81456             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
81457         };
81458     },
81459
81460     getNodeValue: function(node) {
81461         if (node && node.firstChild) {
81462             return node.firstChild.nodeValue;
81463         }
81464         return undefined;
81465     },
81466
81467     //inherit docs
81468     getResponseData: function(response) {
81469         var xml = response.responseXML;
81470
81471
81472         return xml;
81473     },
81474
81475     /**
81476      * Normalizes the data object
81477      * @param {Object} data The raw data object
81478      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
81479      */
81480     getData: function(data) {
81481         return data.documentElement || data;
81482     },
81483
81484     /**
81485      * @private
81486      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
81487      * @param {Object} data The XML data object
81488      * @return {XMLElement} The root node element
81489      */
81490     getRoot: function(data) {
81491         var nodeName = data.nodeName,
81492             root     = this.root;
81493
81494         if (!root || (nodeName && nodeName == root)) {
81495             return data;
81496         } else if (Ext.DomQuery.isXml(data)) {
81497             // This fix ensures we have XML data
81498             // Related to TreeStore calling getRoot with the root node, which isn't XML
81499             // Probably should be resolved in TreeStore at some point
81500             return Ext.DomQuery.selectNode(root, data);
81501         }
81502     },
81503
81504     /**
81505      * @private
81506      * We're just preparing the data for the superclass by pulling out the record nodes we want
81507      * @param {XMLElement} root The XML root node
81508      * @return {Ext.data.Model[]} The records
81509      */
81510     extractData: function(root) {
81511         var recordName = this.record;
81512
81513
81514         if (recordName != root.nodeName) {
81515             root = Ext.DomQuery.select(recordName, root);
81516         } else {
81517             root = [root];
81518         }
81519         return this.callParent([root]);
81520     },
81521
81522     /**
81523      * @private
81524      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
81525      * @param {Object} data The raw data object
81526      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
81527      * @return {XMLElement} The root
81528      */
81529     getAssociatedDataRoot: function(data, associationName) {
81530         return Ext.DomQuery.select(associationName, data)[0];
81531     },
81532
81533     /**
81534      * Parses an XML document and returns a ResultSet containing the model instances
81535      * @param {Object} doc Parsed XML document
81536      * @return {Ext.data.ResultSet} The parsed result set
81537      */
81538     readRecords: function(doc) {
81539         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
81540         if (Ext.isArray(doc)) {
81541             doc = doc[0];
81542         }
81543
81544         /**
81545          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
81546          * @property xmlData
81547          * @type Object
81548          */
81549         this.xmlData = doc;
81550         return this.callParent([doc]);
81551     }
81552 });
81553 /**
81554  * @author Ed Spencer
81555  * @class Ext.data.writer.Xml
81556  * @extends Ext.data.writer.Writer
81557
81558 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
81559 The {@link #documentRoot} property is used to specify the root element in the XML document.
81560 The {@link #record} option is used to specify the element name for each record that will make
81561 up the XML document.
81562
81563  * @markdown
81564  */
81565 Ext.define('Ext.data.writer.Xml', {
81566     
81567     /* Begin Definitions */
81568     
81569     extend: 'Ext.data.writer.Writer',
81570     alternateClassName: 'Ext.data.XmlWriter',
81571     
81572     alias: 'writer.xml',
81573     
81574     /* End Definitions */
81575     
81576     /**
81577      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
81578      * If there is more than 1 record and the root is not specified, the default document root will still be used
81579      * to ensure a valid XML document is created.
81580      */
81581     documentRoot: 'xmlData',
81582     
81583     /**
81584      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
81585      * to form a valid XML document.
81586      */
81587     defaultDocumentRoot: 'xmlData',
81588
81589     /**
81590      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
81591      * Defaults to <tt>''</tt>.
81592      */
81593     header: '',
81594
81595     /**
81596      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
81597      */
81598     record: 'record',
81599
81600     //inherit docs
81601     writeRecords: function(request, data) {
81602         var me = this,
81603             xml = [],
81604             i = 0,
81605             len = data.length,
81606             root = me.documentRoot,
81607             record = me.record,
81608             needsRoot = data.length !== 1,
81609             item,
81610             key;
81611             
81612         // may not exist
81613         xml.push(me.header || '');
81614         
81615         if (!root && needsRoot) {
81616             root = me.defaultDocumentRoot;
81617         }
81618         
81619         if (root) {
81620             xml.push('<', root, '>');
81621         }
81622             
81623         for (; i < len; ++i) {
81624             item = data[i];
81625             xml.push('<', record, '>');
81626             for (key in item) {
81627                 if (item.hasOwnProperty(key)) {
81628                     xml.push('<', key, '>', item[key], '</', key, '>');
81629                 }
81630             }
81631             xml.push('</', record, '>');
81632         }
81633         
81634         if (root) {
81635             xml.push('</', root, '>');
81636         }
81637             
81638         request.xmlData = xml.join('');
81639         return request;
81640     }
81641 });
81642
81643 /**
81644  * @class Ext.direct.Event
81645  * A base class for all Ext.direct events. An event is
81646  * created after some kind of interaction with the server.
81647  * The event class is essentially just a data structure
81648  * to hold a Direct response.
81649  */
81650 Ext.define('Ext.direct.Event', {
81651
81652     /* Begin Definitions */
81653
81654     alias: 'direct.event',
81655
81656     requires: ['Ext.direct.Manager'],
81657
81658     /* End Definitions */
81659
81660     status: true,
81661
81662     /**
81663      * Creates new Event.
81664      * @param {Object} config (optional) Config object.
81665      */
81666     constructor: function(config) {
81667         Ext.apply(this, config);
81668     },
81669
81670     /**
81671      * Return the raw data for this event.
81672      * @return {Object} The data from the event
81673      */
81674     getData: function(){
81675         return this.data;
81676     }
81677 });
81678
81679 /**
81680  * @class Ext.direct.RemotingEvent
81681  * @extends Ext.direct.Event
81682  * An event that is fired when data is received from a 
81683  * {@link Ext.direct.RemotingProvider}. Contains a method to the
81684  * related transaction for the direct request, see {@link #getTransaction}
81685  */
81686 Ext.define('Ext.direct.RemotingEvent', {
81687     
81688     /* Begin Definitions */
81689    
81690     extend: 'Ext.direct.Event',
81691     
81692     alias: 'direct.rpc',
81693     
81694     /* End Definitions */
81695     
81696     /**
81697      * Get the transaction associated with this event.
81698      * @return {Ext.direct.Transaction} The transaction
81699      */
81700     getTransaction: function(){
81701         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
81702     }
81703 });
81704
81705 /**
81706  * @class Ext.direct.ExceptionEvent
81707  * @extends Ext.direct.RemotingEvent
81708  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
81709  */
81710 Ext.define('Ext.direct.ExceptionEvent', {
81711     
81712     /* Begin Definitions */
81713    
81714     extend: 'Ext.direct.RemotingEvent',
81715     
81716     alias: 'direct.exception',
81717     
81718     /* End Definitions */
81719    
81720    status: false
81721 });
81722
81723 /**
81724  * @class Ext.direct.Provider
81725  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
81726  *
81727  * <p>For example Ext JS implements the following subclasses:</p>
81728  * <pre><code>
81729 Provider
81730 |
81731 +---{@link Ext.direct.JsonProvider JsonProvider}
81732     |
81733     +---{@link Ext.direct.PollingProvider PollingProvider}
81734     |
81735     +---{@link Ext.direct.RemotingProvider RemotingProvider}
81736  * </code></pre>
81737  * @abstract
81738  */
81739 Ext.define('Ext.direct.Provider', {
81740
81741     /* Begin Definitions */
81742
81743    alias: 'direct.provider',
81744
81745     mixins: {
81746         observable: 'Ext.util.Observable'
81747     },
81748
81749     /* End Definitions */
81750
81751    /**
81752      * @cfg {String} id
81753      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
81754      * You should assign an id if you need to be able to access the provider later and you do
81755      * not have an object reference available, for example:
81756      * <pre><code>
81757 Ext.direct.Manager.addProvider({
81758     type: 'polling',
81759     url:  'php/poll.php',
81760     id:   'poll-provider'
81761 });
81762 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
81763 p.disconnect();
81764      * </code></pre>
81765      */
81766
81767     constructor : function(config){
81768         var me = this;
81769
81770         Ext.apply(me, config);
81771         me.addEvents(
81772             /**
81773              * @event connect
81774              * Fires when the Provider connects to the server-side
81775              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81776              */
81777             'connect',
81778             /**
81779              * @event disconnect
81780              * Fires when the Provider disconnects from the server-side
81781              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81782              */
81783             'disconnect',
81784             /**
81785              * @event data
81786              * Fires when the Provider receives data from the server-side
81787              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81788              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
81789              */
81790             'data',
81791             /**
81792              * @event exception
81793              * Fires when the Provider receives an exception from the server-side
81794              */
81795             'exception'
81796         );
81797         me.mixins.observable.constructor.call(me, config);
81798     },
81799
81800     /**
81801      * Returns whether or not the server-side is currently connected.
81802      * Abstract method for subclasses to implement.
81803      */
81804     isConnected: function(){
81805         return false;
81806     },
81807
81808     /**
81809      * Abstract methods for subclasses to implement.
81810      * @method
81811      */
81812     connect: Ext.emptyFn,
81813
81814     /**
81815      * Abstract methods for subclasses to implement.
81816      * @method
81817      */
81818     disconnect: Ext.emptyFn
81819 });
81820
81821 /**
81822  * @class Ext.direct.JsonProvider
81823  * @extends Ext.direct.Provider
81824
81825 A base provider for communicating using JSON. This is an abstract class
81826 and should not be instanced directly.
81827
81828  * @markdown
81829  * @abstract
81830  */
81831
81832 Ext.define('Ext.direct.JsonProvider', {
81833
81834     /* Begin Definitions */
81835
81836     extend: 'Ext.direct.Provider',
81837
81838     alias: 'direct.jsonprovider',
81839
81840     uses: ['Ext.direct.ExceptionEvent'],
81841
81842     /* End Definitions */
81843
81844    /**
81845     * Parse the JSON response
81846     * @private
81847     * @param {Object} response The XHR response object
81848     * @return {Object} The data in the response.
81849     */
81850    parseResponse: function(response){
81851         if (!Ext.isEmpty(response.responseText)) {
81852             if (Ext.isObject(response.responseText)) {
81853                 return response.responseText;
81854             }
81855             return Ext.decode(response.responseText);
81856         }
81857         return null;
81858     },
81859
81860     /**
81861      * Creates a set of events based on the XHR response
81862      * @private
81863      * @param {Object} response The XHR response
81864      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
81865      */
81866     createEvents: function(response){
81867         var data = null,
81868             events = [],
81869             event,
81870             i = 0,
81871             len;
81872
81873         try{
81874             data = this.parseResponse(response);
81875         } catch(e) {
81876             event = Ext.create('Ext.direct.ExceptionEvent', {
81877                 data: e,
81878                 xhr: response,
81879                 code: Ext.direct.Manager.self.exceptions.PARSE,
81880                 message: 'Error parsing json response: \n\n ' + data
81881             });
81882             return [event];
81883         }
81884
81885         if (Ext.isArray(data)) {
81886             for (len = data.length; i < len; ++i) {
81887                 events.push(this.createEvent(data[i]));
81888             }
81889         } else {
81890             events.push(this.createEvent(data));
81891         }
81892         return events;
81893     },
81894
81895     /**
81896      * Create an event from a response object
81897      * @param {Object} response The XHR response object
81898      * @return {Ext.direct.Event} The event
81899      */
81900     createEvent: function(response){
81901         return Ext.create('direct.' + response.type, response);
81902     }
81903 });
81904 /**
81905  * @class Ext.direct.PollingProvider
81906  * @extends Ext.direct.JsonProvider
81907  *
81908  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
81909  * The initial request for data originates from the client, and then is responded to by the
81910  * server.</p>
81911  * 
81912  * <p>All configurations for the PollingProvider should be generated by the server-side
81913  * API portion of the Ext.Direct stack.</p>
81914  *
81915  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
81916  * specifying <tt>type = 'polling'</tt>.  For example:</p>
81917  * <pre><code>
81918 var pollA = new Ext.direct.PollingProvider({
81919     type:'polling',
81920     url: 'php/pollA.php',
81921 });
81922 Ext.direct.Manager.addProvider(pollA);
81923 pollA.disconnect();
81924
81925 Ext.direct.Manager.addProvider(
81926     {
81927         type:'polling',
81928         url: 'php/pollB.php',
81929         id: 'pollB-provider'
81930     }
81931 );
81932 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
81933  * </code></pre>
81934  */
81935 Ext.define('Ext.direct.PollingProvider', {
81936     
81937     /* Begin Definitions */
81938     
81939     extend: 'Ext.direct.JsonProvider',
81940     
81941     alias: 'direct.pollingprovider',
81942     
81943     uses: ['Ext.direct.ExceptionEvent'],
81944     
81945     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
81946     
81947     /* End Definitions */
81948     
81949     /**
81950      * @cfg {Number} interval
81951      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
81952      */
81953     interval: 3000,
81954
81955     /**
81956      * @cfg {Object} baseParams
81957      * An object containing properties which are to be sent as parameters on every polling request
81958      */
81959     
81960     /**
81961      * @cfg {String/Function} url
81962      * The url which the PollingProvider should contact with each request. This can also be
81963      * an imported Ext.Direct method which will accept the baseParams as its only argument.
81964      */
81965
81966     // private
81967     constructor : function(config){
81968         this.callParent(arguments);
81969         this.addEvents(
81970             /**
81971              * @event beforepoll
81972              * Fired immediately before a poll takes place, an event handler can return false
81973              * in order to cancel the poll.
81974              * @param {Ext.direct.PollingProvider} this
81975              */
81976             'beforepoll',            
81977             /**
81978              * @event poll
81979              * This event has not yet been implemented.
81980              * @param {Ext.direct.PollingProvider} this
81981              */
81982             'poll'
81983         );
81984     },
81985
81986     // inherited
81987     isConnected: function(){
81988         return !!this.pollTask;
81989     },
81990
81991     /**
81992      * Connect to the server-side and begin the polling process. To handle each
81993      * response subscribe to the data event.
81994      */
81995     connect: function(){
81996         var me = this, url = me.url;
81997         
81998         if (url && !me.pollTask) {
81999             me.pollTask = Ext.TaskManager.start({
82000                 run: function(){
82001                     if (me.fireEvent('beforepoll', me) !== false) {
82002                         if (Ext.isFunction(url)) {
82003                             url(me.baseParams);
82004                         } else {
82005                             Ext.Ajax.request({
82006                                 url: url,
82007                                 callback: me.onData,
82008                                 scope: me,
82009                                 params: me.baseParams
82010                             });
82011                         }
82012                     }
82013                 },
82014                 interval: me.interval,
82015                 scope: me
82016             });
82017             me.fireEvent('connect', me);
82018         } else if (!url) {
82019         }
82020     },
82021
82022     /**
82023      * Disconnect from the server-side and stop the polling process. The disconnect
82024      * event will be fired on a successful disconnect.
82025      */
82026     disconnect: function(){
82027         var me = this;
82028         
82029         if (me.pollTask) {
82030             Ext.TaskManager.stop(me.pollTask);
82031             delete me.pollTask;
82032             me.fireEvent('disconnect', me);
82033         }
82034     },
82035
82036     // private
82037     onData: function(opt, success, response){
82038         var me = this, 
82039             i = 0, 
82040             len,
82041             events;
82042         
82043         if (success) {
82044             events = me.createEvents(response);
82045             for (len = events.length; i < len; ++i) {
82046                 me.fireEvent('data', me, events[i]);
82047             }
82048         } else {
82049             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
82050                 data: null,
82051                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82052                 message: 'Unable to connect to the server.',
82053                 xhr: response
82054             }));
82055         }
82056     }
82057 });
82058 /**
82059  * Small utility class used internally to represent a Direct method.
82060  * @class Ext.direct.RemotingMethod
82061  * @ignore
82062  */
82063 Ext.define('Ext.direct.RemotingMethod', {
82064
82065     constructor: function(config){
82066         var me = this,
82067             params = Ext.isDefined(config.params) ? config.params : config.len,
82068             name;
82069
82070         me.name = config.name;
82071         me.formHandler = config.formHandler;
82072         if (Ext.isNumber(params)) {
82073             // given only the number of parameters
82074             me.len = params;
82075             me.ordered = true;
82076         } else {
82077             /*
82078              * Given an array of either
82079              * a) String
82080              * b) Objects with a name property. We may want to encode extra info in here later
82081              */
82082             me.params = [];
82083             Ext.each(params, function(param){
82084                 name = Ext.isObject(param) ? param.name : param;
82085                 me.params.push(name);
82086             });
82087         }
82088     },
82089
82090     /**
82091      * Takes the arguments for the Direct function and splits the arguments
82092      * from the scope and the callback.
82093      * @param {Array} args The arguments passed to the direct call
82094      * @return {Object} An object with 3 properties, args, callback & scope.
82095      */
82096     getCallData: function(args){
82097         var me = this,
82098             data = null,
82099             len  = me.len,
82100             params = me.params,
82101             callback,
82102             scope,
82103             name;
82104
82105         if (me.ordered) {
82106             callback = args[len];
82107             scope = args[len + 1];
82108             if (len !== 0) {
82109                 data = args.slice(0, len);
82110             }
82111         } else {
82112             data = Ext.apply({}, args[0]);
82113             callback = args[1];
82114             scope = args[2];
82115
82116             // filter out any non-existent properties
82117             for (name in data) {
82118                 if (data.hasOwnProperty(name)) {
82119                     if (!Ext.Array.contains(params, name)) {
82120                         delete data[name];
82121                     }
82122                 }
82123             }
82124         }
82125
82126         return {
82127             data: data,
82128             callback: callback,
82129             scope: scope
82130         };
82131     }
82132 });
82133
82134 /**
82135  * Supporting Class for Ext.Direct (not intended to be used directly).
82136  */
82137 Ext.define('Ext.direct.Transaction', {
82138     
82139     /* Begin Definitions */
82140    
82141     alias: 'direct.transaction',
82142     alternateClassName: 'Ext.Direct.Transaction',
82143    
82144     statics: {
82145         TRANSACTION_ID: 0
82146     },
82147    
82148     /* End Definitions */
82149
82150     /**
82151      * Creates new Transaction.
82152      * @param {Object} [config] Config object.
82153      */
82154     constructor: function(config){
82155         var me = this;
82156         
82157         Ext.apply(me, config);
82158         me.id = ++me.self.TRANSACTION_ID;
82159         me.retryCount = 0;
82160     },
82161    
82162     send: function(){
82163          this.provider.queueTransaction(this);
82164     },
82165
82166     retry: function(){
82167         this.retryCount++;
82168         this.send();
82169     },
82170
82171     getProvider: function(){
82172         return this.provider;
82173     }
82174 });
82175
82176 /**
82177  * @class Ext.direct.RemotingProvider
82178  * @extends Ext.direct.JsonProvider
82179  * 
82180  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
82181  * server side methods on the client (a remote procedure call (RPC) type of
82182  * connection where the client can initiate a procedure on the server).</p>
82183  * 
82184  * <p>This allows for code to be organized in a fashion that is maintainable,
82185  * while providing a clear path between client and server, something that is
82186  * not always apparent when using URLs.</p>
82187  * 
82188  * <p>To accomplish this the server-side needs to describe what classes and methods
82189  * are available on the client-side. This configuration will typically be
82190  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
82191  */
82192 Ext.define('Ext.direct.RemotingProvider', {
82193     
82194     /* Begin Definitions */
82195    
82196     alias: 'direct.remotingprovider',
82197     
82198     extend: 'Ext.direct.JsonProvider', 
82199     
82200     requires: [
82201         'Ext.util.MixedCollection', 
82202         'Ext.util.DelayedTask', 
82203         'Ext.direct.Transaction',
82204         'Ext.direct.RemotingMethod'
82205     ],
82206    
82207     /* End Definitions */
82208    
82209    /**
82210      * @cfg {Object} actions
82211      * Object literal defining the server side actions and methods. For example, if
82212      * the Provider is configured with:
82213      * <pre><code>
82214 "actions":{ // each property within the 'actions' object represents a server side Class 
82215     "TestAction":[ // array of methods within each server side Class to be   
82216     {              // stubbed out on client
82217         "name":"doEcho", 
82218         "len":1            
82219     },{
82220         "name":"multiply",// name of method
82221         "len":2           // The number of parameters that will be used to create an
82222                           // array of data to send to the server side function.
82223                           // Ensure the server sends back a Number, not a String. 
82224     },{
82225         "name":"doForm",
82226         "formHandler":true, // direct the client to use specialized form handling method 
82227         "len":1
82228     }]
82229 }
82230      * </code></pre>
82231      * <p>Note that a Store is not required, a server method can be called at any time.
82232      * In the following example a <b>client side</b> handler is used to call the
82233      * server side method "multiply" in the server-side "TestAction" Class:</p>
82234      * <pre><code>
82235 TestAction.multiply(
82236     2, 4, // pass two arguments to server, so specify len=2
82237     // callback function after the server is called
82238     // result: the result returned by the server
82239     //      e: Ext.direct.RemotingEvent object
82240     function(result, e){
82241         var t = e.getTransaction();
82242         var action = t.action; // server side Class called
82243         var method = t.method; // server side method called
82244         if(e.status){
82245             var answer = Ext.encode(result); // 8
82246     
82247         }else{
82248             var msg = e.message; // failure message
82249         }
82250     }
82251 );
82252      * </code></pre>
82253      * In the example above, the server side "multiply" function will be passed two
82254      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
82255      * available as the <tt>result</tt> in the example above. 
82256      */
82257     
82258     /**
82259      * @cfg {String/Object} namespace
82260      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
82261      * Explicitly specify the namespace Object, or specify a String to have a
82262      * {@link Ext#namespace namespace created} implicitly.
82263      */
82264     
82265     /**
82266      * @cfg {String} url
82267      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
82268      */
82269     
82270     /**
82271      * @cfg {String} enableUrlEncode
82272      * Specify which param will hold the arguments for the method.
82273      * Defaults to <tt>'data'</tt>.
82274      */
82275     
82276     /**
82277      * @cfg {Number/Boolean} enableBuffer
82278      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
82279      * calls. If a number is specified this is the amount of time in milliseconds
82280      * to wait before sending a batched request.</p>
82281      * <br><p>Calls which are received within the specified timeframe will be
82282      * concatenated together and sent in a single request, optimizing the
82283      * application by reducing the amount of round trips that have to be made
82284      * to the server.</p>
82285      */
82286     enableBuffer: 10,
82287     
82288     /**
82289      * @cfg {Number} maxRetries
82290      * Number of times to re-attempt delivery on failure of a call.
82291      */
82292     maxRetries: 1,
82293     
82294     /**
82295      * @cfg {Number} timeout
82296      * The timeout to use for each request.
82297      */
82298     timeout: undefined,
82299     
82300     constructor : function(config){
82301         var me = this;
82302         me.callParent(arguments);
82303         me.addEvents(
82304             /**
82305              * @event beforecall
82306              * Fires immediately before the client-side sends off the RPC call.
82307              * By returning false from an event handler you can prevent the call from
82308              * executing.
82309              * @param {Ext.direct.RemotingProvider} provider
82310              * @param {Ext.direct.Transaction} transaction
82311              * @param {Object} meta The meta data
82312              */            
82313             'beforecall',            
82314             /**
82315              * @event call
82316              * Fires immediately after the request to the server-side is sent. This does
82317              * NOT fire after the response has come back from the call.
82318              * @param {Ext.direct.RemotingProvider} provider
82319              * @param {Ext.direct.Transaction} transaction
82320              * @param {Object} meta The meta data
82321              */            
82322             'call'
82323         );
82324         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
82325         me.transactions = Ext.create('Ext.util.MixedCollection');
82326         me.callBuffer = [];
82327     },
82328     
82329     /**
82330      * Initialize the API
82331      * @private
82332      */
82333     initAPI : function(){
82334         var actions = this.actions,
82335             namespace = this.namespace,
82336             action,
82337             cls,
82338             methods,
82339             i,
82340             len,
82341             method;
82342             
82343         for (action in actions) {
82344             cls = namespace[action];
82345             if (!cls) {
82346                 cls = namespace[action] = {};
82347             }
82348             methods = actions[action];
82349             
82350             for (i = 0, len = methods.length; i < len; ++i) {
82351                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
82352                 cls[method.name] = this.createHandler(action, method);
82353             }
82354         }
82355     },
82356     
82357     /**
82358      * Create a handler function for a direct call.
82359      * @private
82360      * @param {String} action The action the call is for
82361      * @param {Object} method The details of the method
82362      * @return {Function} A JS function that will kick off the call
82363      */
82364     createHandler : function(action, method){
82365         var me = this,
82366             handler;
82367         
82368         if (!method.formHandler) {
82369             handler = function(){
82370                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
82371             };
82372         } else {
82373             handler = function(form, callback, scope){
82374                 me.configureFormRequest(action, method, form, callback, scope);
82375             };
82376         }
82377         handler.directCfg = {
82378             action: action,
82379             method: method
82380         };
82381         return handler;
82382     },
82383     
82384     // inherit docs
82385     isConnected: function(){
82386         return !!this.connected;
82387     },
82388
82389     // inherit docs
82390     connect: function(){
82391         var me = this;
82392         
82393         if (me.url) {
82394             me.initAPI();
82395             me.connected = true;
82396             me.fireEvent('connect', me);
82397         } else if(!me.url) {
82398         }
82399     },
82400
82401     // inherit docs
82402     disconnect: function(){
82403         var me = this;
82404         
82405         if (me.connected) {
82406             me.connected = false;
82407             me.fireEvent('disconnect', me);
82408         }
82409     },
82410     
82411     /**
82412      * Run any callbacks related to the transaction.
82413      * @private
82414      * @param {Ext.direct.Transaction} transaction The transaction
82415      * @param {Ext.direct.Event} event The event
82416      */
82417     runCallback: function(transaction, event){
82418         var funcName = event.status ? 'success' : 'failure',
82419             callback,
82420             result;
82421         
82422         if (transaction && transaction.callback) {
82423             callback = transaction.callback;
82424             result = Ext.isDefined(event.result) ? event.result : event.data;
82425         
82426             if (Ext.isFunction(callback)) {
82427                 callback(result, event);
82428             } else {
82429                 Ext.callback(callback[funcName], callback.scope, [result, event]);
82430                 Ext.callback(callback.callback, callback.scope, [result, event]);
82431             }
82432         }
82433     },
82434     
82435     /**
82436      * React to the ajax request being completed
82437      * @private
82438      */
82439     onData: function(options, success, response){
82440         var me = this,
82441             i = 0,
82442             len,
82443             events,
82444             event,
82445             transaction,
82446             transactions;
82447             
82448         if (success) {
82449             events = me.createEvents(response);
82450             for (len = events.length; i < len; ++i) {
82451                 event = events[i];
82452                 transaction = me.getTransaction(event);
82453                 me.fireEvent('data', me, event);
82454                 if (transaction) {
82455                     me.runCallback(transaction, event, true);
82456                     Ext.direct.Manager.removeTransaction(transaction);
82457                 }
82458             }
82459         } else {
82460             transactions = [].concat(options.transaction);
82461             for (len = transactions.length; i < len; ++i) {
82462                 transaction = me.getTransaction(transactions[i]);
82463                 if (transaction && transaction.retryCount < me.maxRetries) {
82464                     transaction.retry();
82465                 } else {
82466                     event = Ext.create('Ext.direct.ExceptionEvent', {
82467                         data: null,
82468                         transaction: transaction,
82469                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82470                         message: 'Unable to connect to the server.',
82471                         xhr: response
82472                     });
82473                     me.fireEvent('data', me, event);
82474                     if (transaction) {
82475                         me.runCallback(transaction, event, false);
82476                         Ext.direct.Manager.removeTransaction(transaction);
82477                     }
82478                 }
82479             }
82480         }
82481     },
82482     
82483     /**
82484      * Get transaction from XHR options
82485      * @private
82486      * @param {Object} options The options sent to the Ajax request
82487      * @return {Ext.direct.Transaction} The transaction, null if not found
82488      */
82489     getTransaction: function(options){
82490         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
82491     },
82492     
82493     /**
82494      * Configure a direct request
82495      * @private
82496      * @param {String} action The action being executed
82497      * @param {Object} method The being executed
82498      */
82499     configureRequest: function(action, method, args){
82500         var me = this,
82501             callData = method.getCallData(args),
82502             data = callData.data, 
82503             callback = callData.callback, 
82504             scope = callData.scope,
82505             transaction;
82506
82507         transaction = Ext.create('Ext.direct.Transaction', {
82508             provider: me,
82509             args: args,
82510             action: action,
82511             method: method.name,
82512             data: data,
82513             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
82514         });
82515
82516         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82517             Ext.direct.Manager.addTransaction(transaction);
82518             me.queueTransaction(transaction);
82519             me.fireEvent('call', me, transaction, method);
82520         }
82521     },
82522     
82523     /**
82524      * Gets the Ajax call info for a transaction
82525      * @private
82526      * @param {Ext.direct.Transaction} transaction The transaction
82527      * @return {Object} The call params
82528      */
82529     getCallData: function(transaction){
82530         return {
82531             action: transaction.action,
82532             method: transaction.method,
82533             data: transaction.data,
82534             type: 'rpc',
82535             tid: transaction.id
82536         };
82537     },
82538     
82539     /**
82540      * Sends a request to the server
82541      * @private
82542      * @param {Object/Array} data The data to send
82543      */
82544     sendRequest : function(data){
82545         var me = this,
82546             request = {
82547                 url: me.url,
82548                 callback: me.onData,
82549                 scope: me,
82550                 transaction: data,
82551                 timeout: me.timeout
82552             }, callData,
82553             enableUrlEncode = me.enableUrlEncode,
82554             i = 0,
82555             len,
82556             params;
82557             
82558
82559         if (Ext.isArray(data)) {
82560             callData = [];
82561             for (len = data.length; i < len; ++i) {
82562                 callData.push(me.getCallData(data[i]));
82563             }
82564         } else {
82565             callData = me.getCallData(data);
82566         }
82567
82568         if (enableUrlEncode) {
82569             params = {};
82570             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
82571             request.params = params;
82572         } else {
82573             request.jsonData = callData;
82574         }
82575         Ext.Ajax.request(request);
82576     },
82577     
82578     /**
82579      * Add a new transaction to the queue
82580      * @private
82581      * @param {Ext.direct.Transaction} transaction The transaction
82582      */
82583     queueTransaction: function(transaction){
82584         var me = this,
82585             enableBuffer = me.enableBuffer;
82586         
82587         if (transaction.form) {
82588             me.sendFormRequest(transaction);
82589             return;
82590         }
82591         
82592         me.callBuffer.push(transaction);
82593         if (enableBuffer) {
82594             if (!me.callTask) {
82595                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
82596             }
82597             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
82598         } else {
82599             me.combineAndSend();
82600         }
82601     },
82602     
82603     /**
82604      * Combine any buffered requests and send them off
82605      * @private
82606      */
82607     combineAndSend : function(){
82608         var buffer = this.callBuffer,
82609             len = buffer.length;
82610             
82611         if (len > 0) {
82612             this.sendRequest(len == 1 ? buffer[0] : buffer);
82613             this.callBuffer = [];
82614         }
82615     },
82616     
82617     /**
82618      * Configure a form submission request
82619      * @private
82620      * @param {String} action The action being executed
82621      * @param {Object} method The method being executed
82622      * @param {HTMLElement} form The form being submitted
82623      * @param {Function} callback (optional) A callback to run after the form submits
82624      * @param {Object} scope (optional) A scope to execute the callback in
82625      */
82626     configureFormRequest : function(action, method, form, callback, scope){
82627         var me = this,
82628             transaction = Ext.create('Ext.direct.Transaction', {
82629                 provider: me,
82630                 action: action,
82631                 method: method.name,
82632                 args: [form, callback, scope],
82633                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
82634                 isForm: true
82635             }),
82636             isUpload,
82637             params;
82638
82639         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82640             Ext.direct.Manager.addTransaction(transaction);
82641             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
82642             
82643             params = {
82644                 extTID: transaction.id,
82645                 extAction: action,
82646                 extMethod: method.name,
82647                 extType: 'rpc',
82648                 extUpload: String(isUpload)
82649             };
82650             
82651             // change made from typeof callback check to callback.params
82652             // to support addl param passing in DirectSubmit EAC 6/2
82653             Ext.apply(transaction, {
82654                 form: Ext.getDom(form),
82655                 isUpload: isUpload,
82656                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
82657             });
82658             me.fireEvent('call', me, transaction, method);
82659             me.sendFormRequest(transaction);
82660         }
82661     },
82662     
82663     /**
82664      * Sends a form request
82665      * @private
82666      * @param {Ext.direct.Transaction} transaction The transaction to send
82667      */
82668     sendFormRequest: function(transaction){
82669         Ext.Ajax.request({
82670             url: this.url,
82671             params: transaction.params,
82672             callback: this.onData,
82673             scope: this,
82674             form: transaction.form,
82675             isUpload: transaction.isUpload,
82676             transaction: transaction
82677         });
82678     }
82679     
82680 });
82681
82682 /*
82683  * @class Ext.draw.Matrix
82684  * @private
82685  */
82686 Ext.define('Ext.draw.Matrix', {
82687
82688     /* Begin Definitions */
82689
82690     requires: ['Ext.draw.Draw'],
82691
82692     /* End Definitions */
82693
82694     constructor: function(a, b, c, d, e, f) {
82695         if (a != null) {
82696             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
82697         }
82698         else {
82699             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
82700         }
82701     },
82702
82703     add: function(a, b, c, d, e, f) {
82704         var me = this,
82705             out = [[], [], []],
82706             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82707             x,
82708             y,
82709             z,
82710             res;
82711
82712         for (x = 0; x < 3; x++) {
82713             for (y = 0; y < 3; y++) {
82714                 res = 0;
82715                 for (z = 0; z < 3; z++) {
82716                     res += me.matrix[x][z] * matrix[z][y];
82717                 }
82718                 out[x][y] = res;
82719             }
82720         }
82721         me.matrix = out;
82722     },
82723
82724     prepend: function(a, b, c, d, e, f) {
82725         var me = this,
82726             out = [[], [], []],
82727             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82728             x,
82729             y,
82730             z,
82731             res;
82732
82733         for (x = 0; x < 3; x++) {
82734             for (y = 0; y < 3; y++) {
82735                 res = 0;
82736                 for (z = 0; z < 3; z++) {
82737                     res += matrix[x][z] * me.matrix[z][y];
82738                 }
82739                 out[x][y] = res;
82740             }
82741         }
82742         me.matrix = out;
82743     },
82744
82745     invert: function() {
82746         var matrix = this.matrix,
82747             a = matrix[0][0],
82748             b = matrix[1][0],
82749             c = matrix[0][1],
82750             d = matrix[1][1],
82751             e = matrix[0][2],
82752             f = matrix[1][2],
82753             x = a * d - b * c;
82754         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
82755     },
82756
82757     clone: function() {
82758         var matrix = this.matrix,
82759             a = matrix[0][0],
82760             b = matrix[1][0],
82761             c = matrix[0][1],
82762             d = matrix[1][1],
82763             e = matrix[0][2],
82764             f = matrix[1][2];
82765         return new Ext.draw.Matrix(a, b, c, d, e, f);
82766     },
82767
82768     translate: function(x, y) {
82769         this.prepend(1, 0, 0, 1, x, y);
82770     },
82771
82772     scale: function(x, y, cx, cy) {
82773         var me = this;
82774         if (y == null) {
82775             y = x;
82776         }
82777         me.add(1, 0, 0, 1, cx, cy);
82778         me.add(x, 0, 0, y, 0, 0);
82779         me.add(1, 0, 0, 1, -cx, -cy);
82780     },
82781
82782     rotate: function(a, x, y) {
82783         a = Ext.draw.Draw.rad(a);
82784         var me = this,
82785             cos = +Math.cos(a).toFixed(9),
82786             sin = +Math.sin(a).toFixed(9);
82787         me.add(cos, sin, -sin, cos, x, y);
82788         me.add(1, 0, 0, 1, -x, -y);
82789     },
82790
82791     x: function(x, y) {
82792         var matrix = this.matrix;
82793         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
82794     },
82795
82796     y: function(x, y) {
82797         var matrix = this.matrix;
82798         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
82799     },
82800
82801     get: function(i, j) {
82802         return + this.matrix[i][j].toFixed(4);
82803     },
82804
82805     toString: function() {
82806         var me = this;
82807         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
82808     },
82809
82810     toSvg: function() {
82811         var me = this;
82812         return "matrix(" + [me.get(0, 0), me.get(1, 0), me.get(0, 1), me.get(1, 1), me.get(0, 2), me.get(1, 2)].join() + ")";
82813     },
82814
82815     toFilter: function() {
82816         var me = this;
82817         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
82818             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
82819             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
82820     },
82821
82822     offset: function() {
82823         var matrix = this.matrix;
82824         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
82825     },
82826
82827     // Split matrix into Translate Scale, Shear, and Rotate
82828     split: function () {
82829         function norm(a) {
82830             return a[0] * a[0] + a[1] * a[1];
82831         }
82832         function normalize(a) {
82833             var mag = Math.sqrt(norm(a));
82834             a[0] /= mag;
82835             a[1] /= mag;
82836         }
82837         var matrix = this.matrix,
82838             out = {
82839                 translateX: matrix[0][2],
82840                 translateY: matrix[1][2]
82841             },
82842             row;
82843
82844         // scale and shear
82845         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
82846         out.scaleX = Math.sqrt(norm(row[0]));
82847         normalize(row[0]);
82848
82849         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
82850         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
82851
82852         out.scaleY = Math.sqrt(norm(row[1]));
82853         normalize(row[1]);
82854         out.shear /= out.scaleY;
82855
82856         // rotation
82857         out.rotate = Math.asin(-row[0][1]);
82858
82859         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
82860
82861         return out;
82862     }
82863 });
82864
82865 // private - DD implementation for Panels
82866 Ext.define('Ext.draw.SpriteDD', {
82867     extend: 'Ext.dd.DragSource',
82868
82869     constructor : function(sprite, cfg){
82870         var me = this,
82871             el = sprite.el;
82872         me.sprite = sprite;
82873         me.el = el;
82874         me.dragData = {el: el, sprite: sprite};
82875         me.callParent([el, cfg]);
82876         me.sprite.setStyle('cursor', 'move');
82877     },
82878
82879     showFrame: Ext.emptyFn,
82880     createFrame : Ext.emptyFn,
82881
82882     getDragEl : function(e){
82883         return this.el;
82884     },
82885     
82886     getRegion: function() {
82887         var me = this,
82888             el = me.el,
82889             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
82890         
82891         sprite = me.sprite;
82892         bbox = sprite.getBBox();
82893         
82894         try {
82895             pos = Ext.Element.getXY(el);
82896         } catch (e) { }
82897
82898         if (!pos) {
82899             return null;
82900         }
82901
82902         x1 = pos[0];
82903         x2 = x1 + bbox.width;
82904         y1 = pos[1];
82905         y2 = y1 + bbox.height;
82906         
82907         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
82908     },
82909
82910     /*
82911       TODO(nico): Cumulative translations in VML are handled
82912       differently than in SVG. While in SVG we specify the translation
82913       relative to the original x, y position attributes, in VML the translation
82914       is a delta between the last position of the object (modified by the last
82915       translation) and the new one.
82916       
82917       In VML the translation alters the position
82918       of the object, we should change that or alter the SVG impl.
82919     */
82920      
82921     startDrag: function(x, y) {
82922         var me = this,
82923             attr = me.sprite.attr;
82924         me.prev = me.sprite.surface.transformToViewBox(x, y);
82925     },
82926
82927     onDrag: function(e) {
82928         var xy = e.getXY(),
82929             me = this,
82930             sprite = me.sprite,
82931             attr = sprite.attr, dx, dy;
82932         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
82933         dx = xy[0] - me.prev[0];
82934         dy = xy[1] - me.prev[1];
82935         sprite.setAttributes({
82936             translate: {
82937                 x: attr.translation.x + dx,
82938                 y: attr.translation.y + dy
82939             }
82940         }, true);
82941         me.prev = xy;
82942     },
82943
82944     setDragElPos: function () {
82945         // Disable automatic DOM move in DD that spoils layout of VML engine.
82946         return false;
82947     }
82948 });
82949 /**
82950  * A Sprite is an object rendered in a Drawing surface.
82951  *
82952  * # Translation
82953  *
82954  * For translate, the configuration object contains x and y attributes that indicate where to
82955  * translate the object. For example:
82956  *
82957  *     sprite.setAttributes({
82958  *       translate: {
82959  *        x: 10,
82960  *        y: 10
82961  *       }
82962  *     }, true);
82963  *
82964  *
82965  * # Rotation
82966  *
82967  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
82968  * and a `degrees` attribute that specifies the rotation in degrees. For example:
82969  *
82970  *     sprite.setAttributes({
82971  *       rotate: {
82972  *        degrees: 90
82973  *       }
82974  *     }, true);
82975  *
82976  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
82977  *
82978  *     sprite.setAttributes({
82979  *       rotate: {
82980  *        x: 0,
82981  *        y: 0,
82982  *        degrees: 90
82983  *       }
82984  *     }, true);
82985  *
82986  * will create a rotation around the `(0, 0)` axis.
82987  *
82988  *
82989  * # Scaling
82990  *
82991  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
82992  *
82993  *     sprite.setAttributes({
82994  *       scale: {
82995  *        x: 10,
82996  *        y: 3
82997  *       }
82998  *     }, true);
82999  *
83000  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
83001  *
83002  *     sprite.setAttributes({
83003  *       scale: {
83004  *        cx: 0,
83005  *        cy: 0,
83006  *        x: 10,
83007  *        y: 3
83008  *       }
83009  *     }, true);
83010  *
83011  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
83012  *
83013  *
83014  * # Creating and adding a Sprite to a Surface
83015  *
83016  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
83017  *
83018  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
83019  *
83020  *     var sprite = Ext.create('Ext.draw.Sprite', {
83021  *         type: 'circle',
83022  *         fill: '#ff0',
83023  *         surface: drawComponent.surface,
83024  *         radius: 5
83025  *     });
83026  *
83027  * Sprites can also be added to the surface as a configuration object:
83028  *
83029  *     var sprite = drawComponent.surface.add({
83030  *         type: 'circle',
83031  *         fill: '#ff0',
83032  *         radius: 5
83033  *     });
83034  *
83035  * In order to properly apply properties and render the sprite we have to
83036  * `show` the sprite setting the option `redraw` to `true`:
83037  *
83038  *     sprite.show(true);
83039  *
83040  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
83041  * add method to append a new sprite to the canvas. For example:
83042  *
83043  *     drawComponent.surface.add({
83044  *         type: 'circle',
83045  *         fill: '#ffc',
83046  *         radius: 100,
83047  *         x: 100,
83048  *         y: 100
83049  *     });
83050  */
83051 Ext.define('Ext.draw.Sprite', {
83052
83053     /* Begin Definitions */
83054
83055     mixins: {
83056         observable: 'Ext.util.Observable',
83057         animate: 'Ext.util.Animate'
83058     },
83059
83060     requires: ['Ext.draw.SpriteDD'],
83061
83062     /* End Definitions */
83063
83064     /**
83065      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
83066      */
83067
83068     /**
83069      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
83070      */
83071
83072     /**
83073      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
83074      */
83075
83076     /**
83077      * @cfg {Number} size Used in square sprites, the dimension of the square
83078      */
83079
83080     /**
83081      * @cfg {Number} radius Used in circle sprites, the radius of the circle
83082      */
83083
83084     /**
83085      * @cfg {Number} x The position along the x-axis
83086      */
83087
83088     /**
83089      * @cfg {Number} y The position along the y-axis
83090      */
83091
83092     /**
83093      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
83094      */
83095
83096     /**
83097      * @cfg {Number} opacity The opacity of the sprite
83098      */
83099
83100     /**
83101      * @cfg {String} fill The fill color
83102      */
83103
83104     /**
83105      * @cfg {String} stroke The stroke color
83106      */
83107
83108     /**
83109      * @cfg {Number} stroke-width The width of the stroke
83110      */
83111
83112     /**
83113      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
83114      */
83115
83116     /**
83117      * @cfg {String} text Used with text type sprites. The text itself
83118      */
83119
83120     /**
83121      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
83122      * {@link Ext.draw.Surface}
83123      */
83124
83125     /**
83126      * @cfg {Boolean} draggable True to make the sprite draggable.
83127      */
83128
83129     dirty: false,
83130     dirtyHidden: false,
83131     dirtyTransform: false,
83132     dirtyPath: true,
83133     dirtyFont: true,
83134     zIndexDirty: true,
83135     isSprite: true,
83136     zIndex: 0,
83137     fontProperties: [
83138         'font',
83139         'font-size',
83140         'font-weight',
83141         'font-style',
83142         'font-family',
83143         'text-anchor',
83144         'text'
83145     ],
83146     pathProperties: [
83147         'x',
83148         'y',
83149         'd',
83150         'path',
83151         'height',
83152         'width',
83153         'radius',
83154         'r',
83155         'rx',
83156         'ry',
83157         'cx',
83158         'cy'
83159     ],
83160     constructor: function(config) {
83161         var me = this;
83162         config = config || {};
83163         me.id = Ext.id(null, 'ext-sprite-');
83164         me.transformations = [];
83165         Ext.copyTo(this, config, 'surface,group,type,draggable');
83166         //attribute bucket
83167         me.bbox = {};
83168         me.attr = {
83169             zIndex: 0,
83170             translation: {
83171                 x: null,
83172                 y: null
83173             },
83174             rotation: {
83175                 degrees: null,
83176                 x: null,
83177                 y: null
83178             },
83179             scaling: {
83180                 x: null,
83181                 y: null,
83182                 cx: null,
83183                 cy: null
83184             }
83185         };
83186         //delete not bucket attributes
83187         delete config.surface;
83188         delete config.group;
83189         delete config.type;
83190         delete config.draggable;
83191         me.setAttributes(config);
83192         me.addEvents(
83193             'beforedestroy',
83194             'destroy',
83195             'render',
83196             'mousedown',
83197             'mouseup',
83198             'mouseover',
83199             'mouseout',
83200             'mousemove',
83201             'click'
83202         );
83203         me.mixins.observable.constructor.apply(this, arguments);
83204     },
83205
83206     /**
83207      * @property {Ext.dd.DragSource} dd
83208      * If this Sprite is configured {@link #draggable}, this property will contain
83209      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
83210      *
83211      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
83212      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
83213      */
83214
83215     initDraggable: function() {
83216         var me = this;
83217         me.draggable = true;
83218         //create element if it doesn't exist.
83219         if (!me.el) {
83220             me.surface.createSpriteElement(me);
83221         }
83222         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
83223         me.on('beforedestroy', me.dd.destroy, me.dd);
83224     },
83225
83226     /**
83227      * Change the attributes of the sprite.
83228      * @param {Object} attrs attributes to be changed on the sprite.
83229      * @param {Boolean} redraw Flag to immediatly draw the change.
83230      * @return {Ext.draw.Sprite} this
83231      */
83232     setAttributes: function(attrs, redraw) {
83233         var me = this,
83234             fontProps = me.fontProperties,
83235             fontPropsLength = fontProps.length,
83236             pathProps = me.pathProperties,
83237             pathPropsLength = pathProps.length,
83238             hasSurface = !!me.surface,
83239             custom = hasSurface && me.surface.customAttributes || {},
83240             spriteAttrs = me.attr,
83241             attr, i, translate, translation, rotate, rotation, scale, scaling;
83242
83243         attrs = Ext.apply({}, attrs);
83244         for (attr in custom) {
83245             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
83246                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
83247             }
83248         }
83249
83250         // Flag a change in hidden
83251         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
83252             me.dirtyHidden = true;
83253         }
83254
83255         // Flag path change
83256         for (i = 0; i < pathPropsLength; i++) {
83257             attr = pathProps[i];
83258             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83259                 me.dirtyPath = true;
83260                 break;
83261             }
83262         }
83263
83264         // Flag zIndex change
83265         if ('zIndex' in attrs) {
83266             me.zIndexDirty = true;
83267         }
83268
83269         // Flag font/text change
83270         for (i = 0; i < fontPropsLength; i++) {
83271             attr = fontProps[i];
83272             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83273                 me.dirtyFont = true;
83274                 break;
83275             }
83276         }
83277
83278         translate = attrs.translate;
83279         translation = spriteAttrs.translation;
83280         if (translate) {
83281             if ((translate.x && translate.x !== translation.x) ||
83282                 (translate.y && translate.y !== translation.y)) {
83283                 Ext.apply(translation, translate);
83284                 me.dirtyTransform = true;
83285             }
83286             delete attrs.translate;
83287         }
83288
83289         rotate = attrs.rotate;
83290         rotation = spriteAttrs.rotation;
83291         if (rotate) {
83292             if ((rotate.x && rotate.x !== rotation.x) ||
83293                 (rotate.y && rotate.y !== rotation.y) ||
83294                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
83295                 Ext.apply(rotation, rotate);
83296                 me.dirtyTransform = true;
83297             }
83298             delete attrs.rotate;
83299         }
83300
83301         scale = attrs.scale;
83302         scaling = spriteAttrs.scaling;
83303         if (scale) {
83304             if ((scale.x && scale.x !== scaling.x) ||
83305                 (scale.y && scale.y !== scaling.y) ||
83306                 (scale.cx && scale.cx !== scaling.cx) ||
83307                 (scale.cy && scale.cy !== scaling.cy)) {
83308                 Ext.apply(scaling, scale);
83309                 me.dirtyTransform = true;
83310             }
83311             delete attrs.scale;
83312         }
83313
83314         Ext.apply(spriteAttrs, attrs);
83315         me.dirty = true;
83316
83317         if (redraw === true && hasSurface) {
83318             me.redraw();
83319         }
83320         return this;
83321     },
83322
83323     /**
83324      * Retrieves the bounding box of the sprite.
83325      * This will be returned as an object with x, y, width, and height properties.
83326      * @return {Object} bbox
83327      */
83328     getBBox: function() {
83329         return this.surface.getBBox(this);
83330     },
83331
83332     setText: function(text) {
83333         return this.surface.setText(this, text);
83334     },
83335
83336     /**
83337      * Hides the sprite.
83338      * @param {Boolean} redraw Flag to immediatly draw the change.
83339      * @return {Ext.draw.Sprite} this
83340      */
83341     hide: function(redraw) {
83342         this.setAttributes({
83343             hidden: true
83344         }, redraw);
83345         return this;
83346     },
83347
83348     /**
83349      * Shows the sprite.
83350      * @param {Boolean} redraw Flag to immediatly draw the change.
83351      * @return {Ext.draw.Sprite} this
83352      */
83353     show: function(redraw) {
83354         this.setAttributes({
83355             hidden: false
83356         }, redraw);
83357         return this;
83358     },
83359
83360     /**
83361      * Removes the sprite.
83362      */
83363     remove: function() {
83364         if (this.surface) {
83365             this.surface.remove(this);
83366             return true;
83367         }
83368         return false;
83369     },
83370
83371     onRemove: function() {
83372         this.surface.onRemove(this);
83373     },
83374
83375     /**
83376      * Removes the sprite and clears all listeners.
83377      */
83378     destroy: function() {
83379         var me = this;
83380         if (me.fireEvent('beforedestroy', me) !== false) {
83381             me.remove();
83382             me.surface.onDestroy(me);
83383             me.clearListeners();
83384             me.fireEvent('destroy');
83385         }
83386     },
83387
83388     /**
83389      * Redraws the sprite.
83390      * @return {Ext.draw.Sprite} this
83391      */
83392     redraw: function() {
83393         this.surface.renderItem(this);
83394         return this;
83395     },
83396
83397     /**
83398      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
83399      * @param {String/Object} property The style property to be set, or an object of multiple styles.
83400      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
83401      * @return {Ext.draw.Sprite} this
83402      */
83403     setStyle: function() {
83404         this.el.setStyle.apply(this.el, arguments);
83405         return this;
83406     },
83407
83408     /**
83409      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
83410      * is severly limited in VML.
83411      * @param {String/String[]} className The CSS class to add, or an array of classes
83412      * @return {Ext.draw.Sprite} this
83413      */
83414     addCls: function(obj) {
83415         this.surface.addCls(this, obj);
83416         return this;
83417     },
83418
83419     /**
83420      * Removes one or more CSS classes from the element.
83421      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
83422      * is severly limited in VML.
83423      * @return {Ext.draw.Sprite} this
83424      */
83425     removeCls: function(obj) {
83426         this.surface.removeCls(this, obj);
83427         return this;
83428     }
83429 });
83430
83431 /**
83432  * @class Ext.draw.engine.Svg
83433  * @extends Ext.draw.Surface
83434  * Provides specific methods to draw with SVG.
83435  */
83436 Ext.define('Ext.draw.engine.Svg', {
83437
83438     /* Begin Definitions */
83439
83440     extend: 'Ext.draw.Surface',
83441
83442     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
83443
83444     /* End Definitions */
83445
83446     engine: 'Svg',
83447
83448     trimRe: /^\s+|\s+$/g,
83449     spacesRe: /\s+/,
83450     xlink: "http:/" + "/www.w3.org/1999/xlink",
83451
83452     translateAttrs: {
83453         radius: "r",
83454         radiusX: "rx",
83455         radiusY: "ry",
83456         path: "d",
83457         lineWidth: "stroke-width",
83458         fillOpacity: "fill-opacity",
83459         strokeOpacity: "stroke-opacity",
83460         strokeLinejoin: "stroke-linejoin"
83461     },
83462     
83463     parsers: {},
83464
83465     minDefaults: {
83466         circle: {
83467             cx: 0,
83468             cy: 0,
83469             r: 0,
83470             fill: "none",
83471             stroke: null,
83472             "stroke-width": null,
83473             opacity: null,
83474             "fill-opacity": null,
83475             "stroke-opacity": null
83476         },
83477         ellipse: {
83478             cx: 0,
83479             cy: 0,
83480             rx: 0,
83481             ry: 0,
83482             fill: "none",
83483             stroke: null,
83484             "stroke-width": null,
83485             opacity: null,
83486             "fill-opacity": null,
83487             "stroke-opacity": null
83488         },
83489         rect: {
83490             x: 0,
83491             y: 0,
83492             width: 0,
83493             height: 0,
83494             rx: 0,
83495             ry: 0,
83496             fill: "none",
83497             stroke: null,
83498             "stroke-width": null,
83499             opacity: null,
83500             "fill-opacity": null,
83501             "stroke-opacity": null
83502         },
83503         text: {
83504             x: 0,
83505             y: 0,
83506             "text-anchor": "start",
83507             "font-family": null,
83508             "font-size": null,
83509             "font-weight": null,
83510             "font-style": null,
83511             fill: "#000",
83512             stroke: null,
83513             "stroke-width": null,
83514             opacity: null,
83515             "fill-opacity": null,
83516             "stroke-opacity": null
83517         },
83518         path: {
83519             d: "M0,0",
83520             fill: "none",
83521             stroke: null,
83522             "stroke-width": null,
83523             opacity: null,
83524             "fill-opacity": null,
83525             "stroke-opacity": null
83526         },
83527         image: {
83528             x: 0,
83529             y: 0,
83530             width: 0,
83531             height: 0,
83532             preserveAspectRatio: "none",
83533             opacity: null
83534         }
83535     },
83536
83537     createSvgElement: function(type, attrs) {
83538         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
83539             key;
83540         if (attrs) {
83541             for (key in attrs) {
83542                 el.setAttribute(key, String(attrs[key]));
83543             }
83544         }
83545         return el;
83546     },
83547
83548     createSpriteElement: function(sprite) {
83549         // Create svg element and append to the DOM.
83550         var el = this.createSvgElement(sprite.type);
83551         el.id = sprite.id;
83552         if (el.style) {
83553             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
83554         }
83555         sprite.el = Ext.get(el);
83556         this.applyZIndex(sprite); //performs the insertion
83557         sprite.matrix = Ext.create('Ext.draw.Matrix');
83558         sprite.bbox = {
83559             plain: 0,
83560             transform: 0
83561         };
83562         sprite.fireEvent("render", sprite);
83563         return el;
83564     },
83565
83566     getBBox: function (sprite, isWithoutTransform) {
83567         var realPath = this["getPath" + sprite.type](sprite);
83568         if (isWithoutTransform) {
83569             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
83570             return sprite.bbox.plain;
83571         }
83572         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
83573         return sprite.bbox.transform;
83574     },
83575     
83576     getBBoxText: function (sprite) {
83577         var bbox = {},
83578             bb, height, width, i, ln, el;
83579
83580         if (sprite && sprite.el) {
83581             el = sprite.el.dom;
83582             try {
83583                 bbox = el.getBBox();
83584                 return bbox;
83585             } catch(e) {
83586                 // Firefox 3.0.x plays badly here
83587             }
83588             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
83589             ln = el.getNumberOfChars();
83590             for (i = 0; i < ln; i++) {
83591                 bb = el.getExtentOfChar(i);
83592                 bbox.y = Math.min(bb.y, bbox.y);
83593                 height = bb.y + bb.height - bbox.y;
83594                 bbox.height = Math.max(bbox.height, height);
83595                 width = bb.x + bb.width - bbox.x;
83596                 bbox.width = Math.max(bbox.width, width);
83597             }
83598             return bbox;
83599         }
83600     },
83601
83602     hide: function() {
83603         Ext.get(this.el).hide();
83604     },
83605
83606     show: function() {
83607         Ext.get(this.el).show();
83608     },
83609
83610     hidePrim: function(sprite) {
83611         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83612     },
83613
83614     showPrim: function(sprite) {
83615         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83616     },
83617
83618     getDefs: function() {
83619         return this._defs || (this._defs = this.createSvgElement("defs"));
83620     },
83621
83622     transform: function(sprite) {
83623         var me = this,
83624             matrix = Ext.create('Ext.draw.Matrix'),
83625             transforms = sprite.transformations,
83626             transformsLength = transforms.length,
83627             i = 0,
83628             transform, type;
83629             
83630         for (; i < transformsLength; i++) {
83631             transform = transforms[i];
83632             type = transform.type;
83633             if (type == "translate") {
83634                 matrix.translate(transform.x, transform.y);
83635             }
83636             else if (type == "rotate") {
83637                 matrix.rotate(transform.degrees, transform.x, transform.y);
83638             }
83639             else if (type == "scale") {
83640                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
83641             }
83642         }
83643         sprite.matrix = matrix;
83644         sprite.el.set({transform: matrix.toSvg()});
83645     },
83646
83647     setSize: function(w, h) {
83648         var me = this,
83649             el = me.el;
83650         
83651         w = +w || me.width;
83652         h = +h || me.height;
83653         me.width = w;
83654         me.height = h;
83655
83656         el.setSize(w, h);
83657         el.set({
83658             width: w,
83659             height: h
83660         });
83661         me.callParent([w, h]);
83662     },
83663
83664     /**
83665      * Get the region for the surface's canvas area
83666      * @returns {Ext.util.Region}
83667      */
83668     getRegion: function() {
83669         // Mozilla requires using the background rect because the svg element returns an
83670         // incorrect region. Webkit gives no region for the rect and must use the svg element.
83671         var svgXY = this.el.getXY(),
83672             rectXY = this.bgRect.getXY(),
83673             max = Math.max,
83674             x = max(svgXY[0], rectXY[0]),
83675             y = max(svgXY[1], rectXY[1]);
83676         return {
83677             left: x,
83678             top: y,
83679             right: x + this.width,
83680             bottom: y + this.height
83681         };
83682     },
83683
83684     onRemove: function(sprite) {
83685         if (sprite.el) {
83686             sprite.el.remove();
83687             delete sprite.el;
83688         }
83689         this.callParent(arguments);
83690     },
83691     
83692     setViewBox: function(x, y, width, height) {
83693         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
83694             this.callParent(arguments);
83695             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
83696         }
83697     },
83698
83699     render: function (container) {
83700         var me = this;
83701         if (!me.el) {
83702             var width = me.width || 10,
83703                 height = me.height || 10,
83704                 el = me.createSvgElement('svg', {
83705                     xmlns: "http:/" + "/www.w3.org/2000/svg",
83706                     version: 1.1,
83707                     width: width,
83708                     height: height
83709                 }),
83710                 defs = me.getDefs(),
83711
83712                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
83713                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
83714                 // use it rather than the svg element for retrieving the correct client rect of the
83715                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
83716                 bgRect = me.createSvgElement("rect", {
83717                     width: "100%",
83718                     height: "100%",
83719                     fill: "#000",
83720                     stroke: "none",
83721                     opacity: 0
83722                 }),
83723                 webkitRect;
83724             
83725                 if (Ext.isSafari3) {
83726                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
83727                     webkitRect = me.createSvgElement("rect", {
83728                         x: -10,
83729                         y: -10,
83730                         width: "110%",
83731                         height: "110%",
83732                         fill: "none",
83733                         stroke: "#000"
83734                     });
83735                 }
83736             el.appendChild(defs);
83737             if (Ext.isSafari3) {
83738                 el.appendChild(webkitRect);
83739             }
83740             el.appendChild(bgRect);
83741             container.appendChild(el);
83742             me.el = Ext.get(el);
83743             me.bgRect = Ext.get(bgRect);
83744             if (Ext.isSafari3) {
83745                 me.webkitRect = Ext.get(webkitRect);
83746                 me.webkitRect.hide();
83747             }
83748             me.el.on({
83749                 scope: me,
83750                 mouseup: me.onMouseUp,
83751                 mousedown: me.onMouseDown,
83752                 mouseover: me.onMouseOver,
83753                 mouseout: me.onMouseOut,
83754                 mousemove: me.onMouseMove,
83755                 mouseenter: me.onMouseEnter,
83756                 mouseleave: me.onMouseLeave,
83757                 click: me.onClick
83758             });
83759         }
83760         me.renderAll();
83761     },
83762
83763     // private
83764     onMouseEnter: function(e) {
83765         if (this.el.parent().getRegion().contains(e.getPoint())) {
83766             this.fireEvent('mouseenter', e);
83767         }
83768     },
83769
83770     // private
83771     onMouseLeave: function(e) {
83772         if (!this.el.parent().getRegion().contains(e.getPoint())) {
83773             this.fireEvent('mouseleave', e);
83774         }
83775     },
83776     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
83777     processEvent: function(name, e) {
83778         var target = e.getTarget(),
83779             surface = this.surface,
83780             sprite;
83781
83782         this.fireEvent(name, e);
83783         // We wrap text types in a tspan, sprite is the parent.
83784         if (target.nodeName == "tspan" && target.parentNode) {
83785             target = target.parentNode;
83786         }
83787         sprite = this.items.get(target.id);
83788         if (sprite) {
83789             sprite.fireEvent(name, sprite, e);
83790         }
83791     },
83792
83793     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
83794      * the baseline for text the vertical middle of the text to be the same as VML.
83795      */
83796     tuneText: function (sprite, attrs) {
83797         var el = sprite.el.dom,
83798             tspans = [],
83799             height, tspan, text, i, ln, texts, factor;
83800
83801         if (attrs.hasOwnProperty("text")) {
83802            tspans = this.setText(sprite, attrs.text);
83803         }
83804         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
83805         if (tspans.length) {
83806             height = this.getBBoxText(sprite).height;
83807             for (i = 0, ln = tspans.length; i < ln; i++) {
83808                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
83809                 // so we are going to normalize that here
83810                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
83811                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
83812             }
83813             sprite.dirty = true;
83814         }
83815     },
83816
83817     setText: function(sprite, textString) {
83818          var me = this,
83819              el = sprite.el.dom,
83820              x = el.getAttribute("x"),
83821              tspans = [],
83822              height, tspan, text, i, ln, texts;
83823         
83824         while (el.firstChild) {
83825             el.removeChild(el.firstChild);
83826         }
83827         // Wrap each row into tspan to emulate rows
83828         texts = String(textString).split("\n");
83829         for (i = 0, ln = texts.length; i < ln; i++) {
83830             text = texts[i];
83831             if (text) {
83832                 tspan = me.createSvgElement("tspan");
83833                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
83834                 tspan.setAttribute("x", x);
83835                 el.appendChild(tspan);
83836                 tspans[i] = tspan;
83837             }
83838         }
83839         return tspans;
83840     },
83841
83842     renderAll: function() {
83843         this.items.each(this.renderItem, this);
83844     },
83845
83846     renderItem: function (sprite) {
83847         if (!this.el) {
83848             return;
83849         }
83850         if (!sprite.el) {
83851             this.createSpriteElement(sprite);
83852         }
83853         if (sprite.zIndexDirty) {
83854             this.applyZIndex(sprite);
83855         }
83856         if (sprite.dirty) {
83857             this.applyAttrs(sprite);
83858             this.applyTransformations(sprite);
83859         }
83860     },
83861
83862     redraw: function(sprite) {
83863         sprite.dirty = sprite.zIndexDirty = true;
83864         this.renderItem(sprite);
83865     },
83866
83867     applyAttrs: function (sprite) {
83868         var me = this,
83869             el = sprite.el,
83870             group = sprite.group,
83871             sattr = sprite.attr,
83872             parsers = me.parsers,
83873             //Safari does not handle linear gradients correctly in quirksmode
83874             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83875             //ref: EXTJSIV-1472
83876             gradientsMap = me.gradientsMap || {},
83877             safariFix = Ext.isSafari && !Ext.isStrict,
83878             groups, i, ln, attrs, font, key, style, name, rect;
83879
83880         if (group) {
83881             groups = [].concat(group);
83882             ln = groups.length;
83883             for (i = 0; i < ln; i++) {
83884                 group = groups[i];
83885                 me.getGroup(group).add(sprite);
83886             }
83887             delete sprite.group;
83888         }
83889         attrs = me.scrubAttrs(sprite) || {};
83890
83891         // if (sprite.dirtyPath) {
83892             sprite.bbox.plain = 0;
83893             sprite.bbox.transform = 0;
83894             if (sprite.type == "circle" || sprite.type == "ellipse") {
83895                 attrs.cx = attrs.cx || attrs.x;
83896                 attrs.cy = attrs.cy || attrs.y;
83897             }
83898             else if (sprite.type == "rect") {
83899                 attrs.rx = attrs.ry = attrs.r;
83900             }
83901             else if (sprite.type == "path" && attrs.d) {
83902                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
83903             }
83904             sprite.dirtyPath = false;
83905         // }
83906         // else {
83907         //     delete attrs.d;
83908         // }
83909
83910         if (attrs['clip-rect']) {
83911             me.setClip(sprite, attrs);
83912             delete attrs['clip-rect'];
83913         }
83914         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
83915             el.set({ style: "font: " + attrs.font});
83916             sprite.dirtyFont = false;
83917         }
83918         if (sprite.type == "image") {
83919             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
83920         }
83921         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
83922
83923         if (sprite.dirtyHidden) {
83924             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
83925             sprite.dirtyHidden = false;
83926         }
83927         for (key in attrs) {
83928             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
83929                 //Safari does not handle linear gradients correctly in quirksmode
83930                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83931                 //ref: EXTJSIV-1472
83932                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
83933                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
83934                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
83935                     attrs[key] = gradientsMap[attrs[key]];
83936                 }
83937                 if (key in parsers) {
83938                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
83939                 } else {
83940                     el.dom.setAttribute(key, attrs[key]);
83941                 }
83942             }
83943         }
83944         
83945         if (sprite.type == 'text') {
83946             me.tuneText(sprite, attrs);
83947         }
83948
83949         //set styles
83950         style = sattr.style;
83951         if (style) {
83952             el.setStyle(style);
83953         }
83954
83955         sprite.dirty = false;
83956
83957         if (Ext.isSafari3) {
83958             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
83959             me.webkitRect.show();
83960             setTimeout(function () {
83961                 me.webkitRect.hide();
83962             });
83963         }
83964     },
83965
83966     setClip: function(sprite, params) {
83967         var me = this,
83968             rect = params["clip-rect"],
83969             clipEl, clipPath;
83970         if (rect) {
83971             if (sprite.clip) {
83972                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
83973             }
83974             clipEl = me.createSvgElement('clipPath');
83975             clipPath = me.createSvgElement('rect');
83976             clipEl.id = Ext.id(null, 'ext-clip-');
83977             clipPath.setAttribute("x", rect.x);
83978             clipPath.setAttribute("y", rect.y);
83979             clipPath.setAttribute("width", rect.width);
83980             clipPath.setAttribute("height", rect.height);
83981             clipEl.appendChild(clipPath);
83982             me.getDefs().appendChild(clipEl);
83983             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
83984             sprite.clip = clipPath;
83985         }
83986         // if (!attrs[key]) {
83987         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
83988         //     clip && clip.parentNode.removeChild(clip);
83989         //     sprite.el.setAttribute("clip-path", "");
83990         //     delete attrss.clip;
83991         // }
83992     },
83993
83994     /**
83995      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
83996      * @param {Ext.draw.Sprite} sprite
83997      */
83998     applyZIndex: function(sprite) {
83999         var me = this,
84000             items = me.items,
84001             idx = items.indexOf(sprite),
84002             el = sprite.el,
84003             prevEl;
84004         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
84005             if (idx > 0) {
84006                 // Find the first previous sprite which has its DOM element created already
84007                 do {
84008                     prevEl = items.getAt(--idx).el;
84009                 } while (!prevEl && idx > 0);
84010             }
84011             el.insertAfter(prevEl || me.bgRect);
84012         }
84013         sprite.zIndexDirty = false;
84014     },
84015
84016     createItem: function (config) {
84017         var sprite = Ext.create('Ext.draw.Sprite', config);
84018         sprite.surface = this;
84019         return sprite;
84020     },
84021
84022     addGradient: function(gradient) {
84023         gradient = Ext.draw.Draw.parseGradient(gradient);
84024         var me = this,
84025             ln = gradient.stops.length,
84026             vector = gradient.vector,
84027             //Safari does not handle linear gradients correctly in quirksmode
84028             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84029             //ref: EXTJSIV-1472
84030             usePlain = Ext.isSafari && !Ext.isStrict,
84031             gradientEl, stop, stopEl, i, gradientsMap;
84032             
84033         gradientsMap = me.gradientsMap || {};
84034         
84035         if (!usePlain) {
84036             if (gradient.type == "linear") {
84037                 gradientEl = me.createSvgElement("linearGradient");
84038                 gradientEl.setAttribute("x1", vector[0]);
84039                 gradientEl.setAttribute("y1", vector[1]);
84040                 gradientEl.setAttribute("x2", vector[2]);
84041                 gradientEl.setAttribute("y2", vector[3]);
84042             }
84043             else {
84044                 gradientEl = me.createSvgElement("radialGradient");
84045                 gradientEl.setAttribute("cx", gradient.centerX);
84046                 gradientEl.setAttribute("cy", gradient.centerY);
84047                 gradientEl.setAttribute("r", gradient.radius);
84048                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
84049                     gradientEl.setAttribute("fx", gradient.focalX);
84050                     gradientEl.setAttribute("fy", gradient.focalY);
84051                 }
84052             }
84053             gradientEl.id = gradient.id;
84054             me.getDefs().appendChild(gradientEl);
84055             for (i = 0; i < ln; i++) {
84056                 stop = gradient.stops[i];
84057                 stopEl = me.createSvgElement("stop");
84058                 stopEl.setAttribute("offset", stop.offset + "%");
84059                 stopEl.setAttribute("stop-color", stop.color);
84060                 stopEl.setAttribute("stop-opacity",stop.opacity);
84061                 gradientEl.appendChild(stopEl);
84062             }
84063         } else {
84064             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
84065         }
84066         me.gradientsMap = gradientsMap;
84067     },
84068
84069     /**
84070      * Checks if the specified CSS class exists on this element's DOM node.
84071      * @param {String} className The CSS class to check for
84072      * @return {Boolean} True if the class exists, else false
84073      */
84074     hasCls: function(sprite, className) {
84075         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
84076     },
84077
84078     addCls: function(sprite, className) {
84079         var el = sprite.el,
84080             i,
84081             len,
84082             v,
84083             cls = [],
84084             curCls =  el.getAttribute('class') || '';
84085         // Separate case is for speed
84086         if (!Ext.isArray(className)) {
84087             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
84088                 el.set({ 'class': curCls + ' ' + className });
84089             }
84090         }
84091         else {
84092             for (i = 0, len = className.length; i < len; i++) {
84093                 v = className[i];
84094                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
84095                     cls.push(v);
84096                 }
84097             }
84098             if (cls.length) {
84099                 el.set({ 'class': ' ' + cls.join(' ') });
84100             }
84101         }
84102     },
84103
84104     removeCls: function(sprite, className) {
84105         var me = this,
84106             el = sprite.el,
84107             curCls =  el.getAttribute('class') || '',
84108             i, idx, len, cls, elClasses;
84109         if (!Ext.isArray(className)){
84110             className = [className];
84111         }
84112         if (curCls) {
84113             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
84114             for (i = 0, len = className.length; i < len; i++) {
84115                 cls = className[i];
84116                 if (typeof cls == 'string') {
84117                     cls = cls.replace(me.trimRe, '');
84118                     idx = Ext.Array.indexOf(elClasses, cls);
84119                     if (idx != -1) {
84120                         Ext.Array.erase(elClasses, idx, 1);
84121                     }
84122                 }
84123             }
84124             el.set({ 'class': elClasses.join(' ') });
84125         }
84126     },
84127
84128     destroy: function() {
84129         var me = this;
84130         
84131         me.callParent();
84132         if (me.el) {
84133             me.el.remove();
84134         }
84135         delete me.el;
84136     }
84137 });
84138 /**
84139  * @class Ext.draw.engine.Vml
84140  * @extends Ext.draw.Surface
84141  * Provides specific methods to draw with VML.
84142  */
84143
84144 Ext.define('Ext.draw.engine.Vml', {
84145
84146     /* Begin Definitions */
84147
84148     extend: 'Ext.draw.Surface',
84149
84150     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84151
84152     /* End Definitions */
84153
84154     engine: 'Vml',
84155
84156     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
84157     bitesRe: /([clmz]),?([^clmz]*)/gi,
84158     valRe: /-?[^,\s-]+/g,
84159     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
84160     pathlike: /^(path|rect)$/,
84161     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
84162     partialPathRe: /[clmz]/g,
84163     fontFamilyRe: /^['"]+|['"]+$/g,
84164     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
84165     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
84166     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
84167     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
84168     zoom: 21600,
84169     coordsize: 1000,
84170     coordorigin: '0 0',
84171
84172     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
84173     orderSpritesByZIndex: false,
84174
84175     // @private
84176     // Convert an SVG standard path into a VML path
84177     path2vml: function (path) {
84178         var me = this,
84179             nonVML =  me.NonVmlPathRe,
84180             map = me.map,
84181             val = me.valRe,
84182             zoom = me.zoom,
84183             bites = me.bitesRe,
84184             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
84185             res, pa, p, r, i, ii, j, jj;
84186         if (String(path).match(nonVML)) {
84187             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
84188         } else if (!String(path).match(me.partialPathRe)) {
84189             res = String(path).replace(bites, function (all, command, args) {
84190                 var vals = [],
84191                     isMove = command.toLowerCase() == "m",
84192                     res = map[command];
84193                 args.replace(val, function (value) {
84194                     if (isMove && vals[length] == 2) {
84195                         res += vals + map[command == "m" ? "l" : "L"];
84196                         vals = [];
84197                     }
84198                     vals.push(Math.round(value * zoom));
84199                 });
84200                 return res + vals;
84201             });
84202             return res;
84203         }
84204         pa = command(path);
84205         res = [];
84206         for (i = 0, ii = pa.length; i < ii; i++) {
84207             p = pa[i];
84208             r = pa[i][0].toLowerCase();
84209             if (r == "z") {
84210                 r = "x";
84211             }
84212             for (j = 1, jj = p.length; j < jj; j++) {
84213                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
84214             }
84215             res.push(r);
84216         }
84217         return res.join(" ");
84218     },
84219
84220     // @private - set of attributes which need to be translated from the sprite API to the native browser API
84221     translateAttrs: {
84222         radius: "r",
84223         radiusX: "rx",
84224         radiusY: "ry",
84225         lineWidth: "stroke-width",
84226         fillOpacity: "fill-opacity",
84227         strokeOpacity: "stroke-opacity",
84228         strokeLinejoin: "stroke-linejoin"
84229     },
84230
84231     // @private - Minimun set of defaults for different types of sprites.
84232     minDefaults: {
84233         circle: {
84234             fill: "none",
84235             stroke: null,
84236             "stroke-width": null,
84237             opacity: null,
84238             "fill-opacity": null,
84239             "stroke-opacity": null
84240         },
84241         ellipse: {
84242             cx: 0,
84243             cy: 0,
84244             rx: 0,
84245             ry: 0,
84246             fill: "none",
84247             stroke: null,
84248             "stroke-width": null,
84249             opacity: null,
84250             "fill-opacity": null,
84251             "stroke-opacity": null
84252         },
84253         rect: {
84254             x: 0,
84255             y: 0,
84256             width: 0,
84257             height: 0,
84258             rx: 0,
84259             ry: 0,
84260             fill: "none",
84261             stroke: null,
84262             "stroke-width": null,
84263             opacity: null,
84264             "fill-opacity": null,
84265             "stroke-opacity": null
84266         },
84267         text: {
84268             x: 0,
84269             y: 0,
84270             "text-anchor": "start",
84271             font: '10px "Arial"',
84272             fill: "#000",
84273             stroke: null,
84274             "stroke-width": null,
84275             opacity: null,
84276             "fill-opacity": null,
84277             "stroke-opacity": null
84278         },
84279         path: {
84280             d: "M0,0",
84281             fill: "none",
84282             stroke: null,
84283             "stroke-width": null,
84284             opacity: null,
84285             "fill-opacity": null,
84286             "stroke-opacity": null
84287         },
84288         image: {
84289             x: 0,
84290             y: 0,
84291             width: 0,
84292             height: 0,
84293             preserveAspectRatio: "none",
84294             opacity: null
84295         }
84296     },
84297
84298     // private
84299     onMouseEnter: function(e) {
84300         this.fireEvent("mouseenter", e);
84301     },
84302
84303     // private
84304     onMouseLeave: function(e) {
84305         this.fireEvent("mouseleave", e);
84306     },
84307
84308     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84309     processEvent: function(name, e) {
84310         var target = e.getTarget(),
84311             surface = this.surface,
84312             sprite;
84313         this.fireEvent(name, e);
84314         sprite = this.items.get(target.id);
84315         if (sprite) {
84316             sprite.fireEvent(name, sprite, e);
84317         }
84318     },
84319
84320     // Create the VML element/elements and append them to the DOM
84321     createSpriteElement: function(sprite) {
84322         var me = this,
84323             attr = sprite.attr,
84324             type = sprite.type,
84325             zoom = me.zoom,
84326             vml = sprite.vml || (sprite.vml = {}),
84327             round = Math.round,
84328             el = me.createNode('shape'),
84329             path, skew, textPath;
84330
84331         el.coordsize = zoom + ' ' + zoom;
84332         el.coordorigin = attr.coordorigin || "0 0";
84333         Ext.get(el).addCls(me.spriteCls);
84334         if (type == "text") {
84335             vml.path = path = me.createNode("path");
84336             path.textpathok = true;
84337             vml.textpath = textPath = me.createNode("textpath");
84338             textPath.on = true;
84339             el.appendChild(textPath);
84340             el.appendChild(path);
84341         }
84342         el.id = sprite.id;
84343         sprite.el = Ext.get(el);
84344         me.el.appendChild(el);
84345         if (type !== 'image') {
84346             skew = me.createNode("skew");
84347             skew.on = true;
84348             el.appendChild(skew);
84349             sprite.skew = skew;
84350         }
84351         sprite.matrix = Ext.create('Ext.draw.Matrix');
84352         sprite.bbox = {
84353             plain: null,
84354             transform: null
84355         };
84356         sprite.fireEvent("render", sprite);
84357         return sprite.el;
84358     },
84359
84360     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
84361     getBBox: function (sprite, isWithoutTransform) {
84362         var realPath = this["getPath" + sprite.type](sprite);
84363         if (isWithoutTransform) {
84364             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
84365             return sprite.bbox.plain;
84366         }
84367         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
84368         return sprite.bbox.transform;
84369     },
84370
84371     getBBoxText: function (sprite) {
84372         var vml = sprite.vml;
84373         return {
84374             x: vml.X + (vml.bbx || 0) - vml.W / 2,
84375             y: vml.Y - vml.H / 2,
84376             width: vml.W,
84377             height: vml.H
84378         };
84379     },
84380
84381     applyAttrs: function (sprite) {
84382         var me = this,
84383             vml = sprite.vml,
84384             group = sprite.group,
84385             spriteAttr = sprite.attr,
84386             el = sprite.el,
84387             dom = el.dom,
84388             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
84389
84390         if (group) {
84391             groups = [].concat(group);
84392             ln = groups.length;
84393             for (i = 0; i < ln; i++) {
84394                 group = groups[i];
84395                 me.getGroup(group).add(sprite);
84396             }
84397             delete sprite.group;
84398         }
84399         scrubbedAttrs = me.scrubAttrs(sprite) || {};
84400
84401         if (sprite.zIndexDirty) {
84402             me.setZIndex(sprite);
84403         }
84404
84405         // Apply minimum default attributes
84406         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
84407
84408         if (dom.href) {
84409             dom.href = scrubbedAttrs.href;
84410         }
84411         if (dom.title) {
84412             dom.title = scrubbedAttrs.title;
84413         }
84414         if (dom.target) {
84415             dom.target = scrubbedAttrs.target;
84416         }
84417         if (dom.cursor) {
84418             dom.cursor = scrubbedAttrs.cursor;
84419         }
84420
84421         // Change visibility
84422         if (sprite.dirtyHidden) {
84423             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
84424             sprite.dirtyHidden = false;
84425         }
84426
84427         // Update path
84428         if (sprite.dirtyPath) {
84429             if (sprite.type == "circle" || sprite.type == "ellipse") {
84430                 var cx = scrubbedAttrs.x,
84431                     cy = scrubbedAttrs.y,
84432                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
84433                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
84434                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
84435                             Math.round((cx - rx) * me.zoom),
84436                             Math.round((cy - ry) * me.zoom),
84437                             Math.round((cx + rx) * me.zoom),
84438                             Math.round((cy + ry) * me.zoom),
84439                             Math.round(cx * me.zoom));
84440                 sprite.dirtyPath = false;
84441             }
84442             else if (sprite.type !== "text") {
84443                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
84444                 dom.path = me.path2vml(scrubbedAttrs.path);
84445                 sprite.dirtyPath = false;
84446             }
84447         }
84448
84449         // Apply clipping
84450         if ("clip-rect" in scrubbedAttrs) {
84451             me.setClip(sprite, scrubbedAttrs);
84452         }
84453
84454         // Handle text (special handling required)
84455         if (sprite.type == "text") {
84456             me.setTextAttributes(sprite, scrubbedAttrs);
84457         }
84458
84459         // Handle fill and opacity
84460         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
84461             me.setFill(sprite, scrubbedAttrs);
84462         }
84463
84464         // Handle stroke (all fills require a stroke element)
84465         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
84466             me.setStroke(sprite, scrubbedAttrs);
84467         }
84468         
84469         //set styles
84470         style = spriteAttr.style;
84471         if (style) {
84472             el.setStyle(style);
84473         }
84474
84475         sprite.dirty = false;
84476     },
84477
84478     setZIndex: function(sprite) {
84479         if (sprite.el) {
84480             if (sprite.attr.zIndex != undefined) {
84481                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
84482             }
84483             sprite.zIndexDirty = false;
84484         }
84485     },
84486
84487     // Normalize all virtualized types into paths.
84488     setPaths: function(sprite, params) {
84489         var spriteAttr = sprite.attr;
84490         // Clear bbox cache
84491         sprite.bbox.plain = null;
84492         sprite.bbox.transform = null;
84493         if (sprite.type == 'circle') {
84494             spriteAttr.rx = spriteAttr.ry = params.r;
84495             return Ext.draw.Draw.ellipsePath(sprite);
84496         }
84497         else if (sprite.type == 'ellipse') {
84498             spriteAttr.rx = params.rx;
84499             spriteAttr.ry = params.ry;
84500             return Ext.draw.Draw.ellipsePath(sprite);
84501         }
84502         else if (sprite.type == 'rect' || sprite.type == 'image') {
84503             spriteAttr.rx = spriteAttr.ry = params.r;
84504             return Ext.draw.Draw.rectPath(sprite);
84505         }
84506         else if (sprite.type == 'path' && spriteAttr.path) {
84507             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
84508         }
84509         return false;
84510     },
84511
84512     setFill: function(sprite, params) {
84513         var me = this,
84514             el = sprite.el,
84515             dom = el.dom,
84516             fillEl = dom.getElementsByTagName('fill')[0],
84517             opacity, gradient, fillUrl, rotation, angle;
84518
84519         if (fillEl) {
84520             dom.removeChild(fillEl);
84521         } else {
84522             fillEl = me.createNode('fill');
84523         }
84524         if (Ext.isArray(params.fill)) {
84525             params.fill = params.fill[0];
84526         }
84527         if (sprite.type == 'image') {
84528             fillEl.on = true;
84529             fillEl.src = params.src;
84530             fillEl.type = "tile";
84531             fillEl.rotate = true;
84532         } else if (params.fill == "none") {
84533             fillEl.on = false;
84534         } else {
84535             if (typeof params.opacity == "number") {
84536                 fillEl.opacity = params.opacity;
84537             }
84538             if (typeof params["fill-opacity"] == "number") {
84539                 fillEl.opacity = params["fill-opacity"];
84540             }
84541             fillEl.on = true;
84542             if (typeof params.fill == "string") {
84543                 fillUrl = params.fill.match(me.fillUrlRe);
84544                 if (fillUrl) {
84545                     fillUrl = fillUrl[1];
84546                     // If the URL matches one of the registered gradients, render that gradient
84547                     if (fillUrl.charAt(0) == "#") {
84548                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
84549                     }
84550                     if (gradient) {
84551                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
84552                         rotation = params.rotation;
84553                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
84554                         // IE will flip the angle at 0 degrees...
84555                         if (angle === 0) {
84556                             angle = 180;
84557                         }
84558                         fillEl.angle = angle;
84559                         fillEl.type = "gradient";
84560                         fillEl.method = "sigma";
84561                         fillEl.colors = gradient.colors;
84562                     }
84563                     // Otherwise treat it as an image
84564                     else {
84565                         fillEl.src = fillUrl;
84566                         fillEl.type = "tile";
84567                         fillEl.rotate = true;
84568                     }
84569                 }
84570                 else {
84571                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
84572                     fillEl.src = "";
84573                     fillEl.type = "solid";
84574                 }
84575             }
84576         }
84577         dom.appendChild(fillEl);
84578     },
84579
84580     setStroke: function(sprite, params) {
84581         var me = this,
84582             el = sprite.el.dom,
84583             strokeEl = sprite.strokeEl,
84584             newStroke = false,
84585             width, opacity;
84586
84587         if (!strokeEl) {
84588             strokeEl = sprite.strokeEl = me.createNode("stroke");
84589             newStroke = true;
84590         }
84591         if (Ext.isArray(params.stroke)) {
84592             params.stroke = params.stroke[0];
84593         }
84594         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
84595             strokeEl.on = false;
84596         }
84597         else {
84598             strokeEl.on = true;
84599             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
84600                 // VML does NOT support a gradient stroke :(
84601                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
84602             }
84603             strokeEl.joinstyle = params["stroke-linejoin"];
84604             strokeEl.endcap = params["stroke-linecap"] || "round";
84605             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
84606             width = parseFloat(params["stroke-width"] || 1) * 0.75;
84607             opacity = params["stroke-opacity"] || 1;
84608             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
84609             if (Ext.isNumber(width) && width < 1) {
84610                 strokeEl.weight = 1;
84611                 strokeEl.opacity = opacity * width;
84612             }
84613             else {
84614                 strokeEl.weight = width;
84615                 strokeEl.opacity = opacity;
84616             }
84617         }
84618         if (newStroke) {
84619             el.appendChild(strokeEl);
84620         }
84621     },
84622
84623     setClip: function(sprite, params) {
84624         var me = this,
84625             el = sprite.el,
84626             clipEl = sprite.clipEl,
84627             rect = String(params["clip-rect"]).split(me.separatorRe);
84628         if (!clipEl) {
84629             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
84630             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
84631         }
84632         if (rect.length == 4) {
84633             rect[2] = +rect[2] + (+rect[0]);
84634             rect[3] = +rect[3] + (+rect[1]);
84635             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
84636             clipEl.setSize(me.el.width, me.el.height);
84637         }
84638         else {
84639             clipEl.setStyle("clip", "");
84640         }
84641     },
84642
84643     setTextAttributes: function(sprite, params) {
84644         var me = this,
84645             vml = sprite.vml,
84646             textStyle = vml.textpath.style,
84647             spanCacheStyle = me.span.style,
84648             zoom = me.zoom,
84649             round = Math.round,
84650             fontObj = {
84651                 fontSize: "font-size",
84652                 fontWeight: "font-weight",
84653                 fontStyle: "font-style"
84654             },
84655             fontProp,
84656             paramProp;
84657         if (sprite.dirtyFont) {
84658             if (params.font) {
84659                 textStyle.font = spanCacheStyle.font = params.font;
84660             }
84661             if (params["font-family"]) {
84662                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
84663                 spanCacheStyle.fontFamily = params["font-family"];
84664             }
84665
84666             for (fontProp in fontObj) {
84667                 paramProp = params[fontObj[fontProp]];
84668                 if (paramProp) {
84669                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
84670                 }
84671             }
84672
84673             me.setText(sprite, params.text);
84674             
84675             if (vml.textpath.string) {
84676                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
84677             }
84678             vml.W = me.span.offsetWidth;
84679             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
84680
84681             // text-anchor emulation
84682             if (params["text-anchor"] == "middle") {
84683                 textStyle["v-text-align"] = "center";
84684             }
84685             else if (params["text-anchor"] == "end") {
84686                 textStyle["v-text-align"] = "right";
84687                 vml.bbx = -Math.round(vml.W / 2);
84688             }
84689             else {
84690                 textStyle["v-text-align"] = "left";
84691                 vml.bbx = Math.round(vml.W / 2);
84692             }
84693         }
84694         vml.X = params.x;
84695         vml.Y = params.y;
84696         vml.path.v = Ext.String.format("m{0},{1}l{2},{1}", Math.round(vml.X * zoom), Math.round(vml.Y * zoom), Math.round(vml.X * zoom) + 1);
84697         // Clear bbox cache
84698         sprite.bbox.plain = null;
84699         sprite.bbox.transform = null;
84700         sprite.dirtyFont = false;
84701     },
84702     
84703     setText: function(sprite, text) {
84704         sprite.vml.textpath.string = Ext.htmlDecode(text);
84705     },
84706
84707     hide: function() {
84708         this.el.hide();
84709     },
84710
84711     show: function() {
84712         this.el.show();
84713     },
84714
84715     hidePrim: function(sprite) {
84716         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
84717     },
84718
84719     showPrim: function(sprite) {
84720         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
84721     },
84722
84723     setSize: function(width, height) {
84724         var me = this;
84725         width = width || me.width;
84726         height = height || me.height;
84727         me.width = width;
84728         me.height = height;
84729
84730         if (me.el) {
84731             // Size outer div
84732             if (width != undefined) {
84733                 me.el.setWidth(width);
84734             }
84735             if (height != undefined) {
84736                 me.el.setHeight(height);
84737             }
84738
84739             // Handle viewBox sizing
84740             me.applyViewBox();
84741
84742             me.callParent(arguments);
84743         }
84744     },
84745
84746     setViewBox: function(x, y, width, height) {
84747         this.callParent(arguments);
84748         this.viewBox = {
84749             x: x,
84750             y: y,
84751             width: width,
84752             height: height
84753         };
84754         this.applyViewBox();
84755     },
84756
84757     /**
84758      * @private Using the current viewBox property and the surface's width and height, calculate the
84759      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
84760      */
84761     applyViewBox: function() {
84762         var me = this,
84763             viewBox = me.viewBox,
84764             width = me.width,
84765             height = me.height,
84766             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
84767             relativeHeight, relativeWidth, size;
84768
84769         if (viewBox && (width || height)) {
84770             viewBoxX = viewBox.x;
84771             viewBoxY = viewBox.y;
84772             viewBoxWidth = viewBox.width;
84773             viewBoxHeight = viewBox.height;
84774             relativeHeight = height / viewBoxHeight;
84775             relativeWidth = width / viewBoxWidth;
84776
84777             if (viewBoxWidth * relativeHeight < width) {
84778                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
84779             }
84780             if (viewBoxHeight * relativeWidth < height) {
84781                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
84782             }
84783
84784             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
84785
84786             me.viewBoxShift = {
84787                 dx: -viewBoxX,
84788                 dy: -viewBoxY,
84789                 scale: size
84790             };
84791             me.items.each(function(item) {
84792                 me.transform(item);
84793             });
84794         }
84795     },
84796
84797     onAdd: function(item) {
84798         this.callParent(arguments);
84799         if (this.el) {
84800             this.renderItem(item);
84801         }
84802     },
84803
84804     onRemove: function(sprite) {
84805         if (sprite.el) {
84806             sprite.el.remove();
84807             delete sprite.el;
84808         }
84809         this.callParent(arguments);
84810     },
84811
84812     // VML Node factory method (createNode)
84813     createNode : (function () {
84814         try {
84815             var doc = Ext.getDoc().dom;
84816             if (!doc.namespaces.rvml) {
84817                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
84818             }
84819             return function (tagName) {
84820                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
84821             };
84822         } catch (e) {
84823             return function (tagName) {
84824                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
84825             };
84826         }
84827     })(),
84828
84829     render: function (container) {
84830         var me = this,
84831             doc = Ext.getDoc().dom;
84832
84833         if (!me.el) {
84834             var el = doc.createElement("div");
84835             me.el = Ext.get(el);
84836             me.el.addCls(me.baseVmlCls);
84837
84838             // Measuring span (offscrren)
84839             me.span = doc.createElement("span");
84840             Ext.get(me.span).addCls(me.measureSpanCls);
84841             el.appendChild(me.span);
84842             me.el.setSize(me.width || 10, me.height || 10);
84843             container.appendChild(el);
84844             me.el.on({
84845                 scope: me,
84846                 mouseup: me.onMouseUp,
84847                 mousedown: me.onMouseDown,
84848                 mouseover: me.onMouseOver,
84849                 mouseout: me.onMouseOut,
84850                 mousemove: me.onMouseMove,
84851                 mouseenter: me.onMouseEnter,
84852                 mouseleave: me.onMouseLeave,
84853                 click: me.onClick
84854             });
84855         }
84856         me.renderAll();
84857     },
84858
84859     renderAll: function() {
84860         this.items.each(this.renderItem, this);
84861     },
84862
84863     redraw: function(sprite) {
84864         sprite.dirty = true;
84865         this.renderItem(sprite);
84866     },
84867
84868     renderItem: function (sprite) {
84869         // Does the surface element exist?
84870         if (!this.el) {
84871             return;
84872         }
84873
84874         // Create sprite element if necessary
84875         if (!sprite.el) {
84876             this.createSpriteElement(sprite);
84877         }
84878
84879         if (sprite.dirty) {
84880             this.applyAttrs(sprite);
84881             if (sprite.dirtyTransform) {
84882                 this.applyTransformations(sprite);
84883             }
84884         }
84885     },
84886
84887     rotationCompensation: function (deg, dx, dy) {
84888         var matrix = Ext.create('Ext.draw.Matrix');
84889         matrix.rotate(-deg, 0.5, 0.5);
84890         return {
84891             x: matrix.x(dx, dy),
84892             y: matrix.y(dx, dy)
84893         };
84894     },
84895
84896     extractTransform: function (sprite) {
84897         var me = this,
84898             matrix = Ext.create('Ext.draw.Matrix'), scale,
84899             transformstions, tranformationsLength,
84900             transform, i = 0,
84901             shift = me.viewBoxShift;
84902
84903         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
84904             i < tranformationsLength; i ++) {
84905             transform = transformstions[i];
84906             switch (transform.type) {
84907                 case 'translate' :
84908                     matrix.translate(transform.x, transform.y);
84909                     break;
84910                 case 'rotate':
84911                     matrix.rotate(transform.degrees, transform.x, transform.y);
84912                     break;
84913                 case 'scale':
84914                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
84915                     break;
84916             }
84917         }
84918
84919         if (shift) {
84920             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
84921             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
84922         }
84923         
84924         return sprite.matrix = matrix;
84925     },
84926
84927     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
84928         var me = this,
84929             matrix = sprite.matrix,
84930             dom = sprite.el.dom,
84931             style = dom.style,
84932             yFlipper = 1,
84933             flip = "",
84934             fill = dom.getElementsByTagName('fill')[0],
84935             kx = me.zoom / sx,
84936             ky = me.zoom / sy,
84937             rotationCompensation;
84938         if (!sx || !sy) {
84939             return;
84940         }
84941         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
84942         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
84943         if (rotate) {
84944             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
84945             dx = rotationCompensation.x;
84946             dy = rotationCompensation.y;
84947         }
84948         if (sx < 0) {
84949             flip += "x"
84950         }
84951         if (sy < 0) {
84952             flip += " y";
84953             yFlipper = -1;
84954         }
84955         style.flip = flip;
84956         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
84957         if (fill) {
84958             dom.removeChild(fill);
84959             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
84960             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
84961             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
84962             dom.appendChild(fill);
84963         }
84964     },
84965
84966     transform : function (sprite) {
84967         var me = this,
84968             el = sprite.el,
84969             skew = sprite.skew,
84970             dom = el.dom,
84971             domStyle = dom.style,
84972             matrix = me.extractTransform(sprite).clone(),
84973             split, zoom = me.zoom,
84974             fill = dom.getElementsByTagName('fill')[0],
84975             isPatt = !String(sprite.fill).indexOf("url("),
84976             offset, c;
84977
84978
84979         // Hide element while we transform
84980
84981         if (sprite.type != "image" && skew && !isPatt) {
84982             // matrix transform via VML skew
84983             skew.matrix = matrix.toString();
84984             // skew.offset = '32767,1' OK
84985             // skew.offset = '32768,1' Crash
84986             // M$, R U kidding??
84987             offset = matrix.offset();
84988             if (offset[0] > 32767) {
84989                 offset[0] = 32767;
84990             } else if (offset[0] < -32768) {
84991                 offset[0] = -32768
84992             }
84993             if (offset[1] > 32767) {
84994                 offset[1] = 32767;
84995             } else if (offset[1] < -32768) {
84996                 offset[1] = -32768
84997             }
84998             skew.offset = offset;
84999         } else {
85000             if (skew) {
85001                 skew.matrix = "1 0 0 1";
85002                 skew.offset = "0 0";
85003             }
85004             split = matrix.split();
85005             if (split.isSimple) {
85006                 domStyle.filter = '';
85007                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
85008             } else {
85009                 domStyle.filter = matrix.toFilter();
85010                 var bb = me.getBBox(sprite),
85011                     dx = bb.x - sprite.x,
85012                     dy = bb.y - sprite.y;
85013                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
85014                 if (fill) {
85015                     dom.removeChild(fill);
85016                     fill.position = dx + ' ' + dy;
85017                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
85018                     dom.appendChild(fill);
85019                 }
85020             }
85021         }
85022     },
85023
85024     createItem: function (config) {
85025         return Ext.create('Ext.draw.Sprite', config);
85026     },
85027
85028     getRegion: function() {
85029         return this.el.getRegion();
85030     },
85031
85032     addCls: function(sprite, className) {
85033         if (sprite && sprite.el) {
85034             sprite.el.addCls(className);
85035         }
85036     },
85037
85038     removeCls: function(sprite, className) {
85039         if (sprite && sprite.el) {
85040             sprite.el.removeCls(className);
85041         }
85042     },
85043
85044     /**
85045      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
85046      * to its corresponding VML attributes and store it for later use by individual sprites.
85047      * @param {Object} gradient
85048      */
85049     addGradient: function(gradient) {
85050         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
85051             colors = [],
85052             stops = Ext.create('Ext.util.MixedCollection');
85053
85054         // Build colors string
85055         stops.addAll(gradient.stops);
85056         stops.sortByKey("ASC", function(a, b) {
85057             a = parseInt(a, 10);
85058             b = parseInt(b, 10);
85059             return a > b ? 1 : (a < b ? -1 : 0);
85060         });
85061         stops.eachKey(function(k, v) {
85062             colors.push(k + "% " + v.color);
85063         });
85064
85065         gradients.add(gradient.id, {
85066             colors: colors.join(","),
85067             angle: gradient.angle
85068         });
85069     },
85070
85071     destroy: function() {
85072         var me = this;
85073         
85074         me.callParent(arguments);
85075         if (me.el) {
85076             me.el.remove();
85077         }
85078         delete me.el;
85079     }
85080 });
85081
85082 /**
85083  * @class Ext.fx.target.ElementCSS
85084  * @extends Ext.fx.target.Element
85085  * 
85086  * This class represents a animation target for an {@link Ext.Element} that supports CSS
85087  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
85088  * will be passed to the animation and the appropriate target will be created.
85089  */
85090 Ext.define('Ext.fx.target.ElementCSS', {
85091
85092     /* Begin Definitions */
85093
85094     extend: 'Ext.fx.target.Element',
85095
85096     /* End Definitions */
85097
85098     setAttr: function(targetData, isFirstFrame) {
85099         var cssArr = {
85100                 attrs: [],
85101                 duration: [],
85102                 easing: []
85103             },
85104             ln = targetData.length,
85105             attributes,
85106             attrs,
85107             attr,
85108             easing,
85109             duration,
85110             o,
85111             i,
85112             j,
85113             ln2;
85114         for (i = 0; i < ln; i++) {
85115             attrs = targetData[i];
85116             duration = attrs.duration;
85117             easing = attrs.easing;
85118             attrs = attrs.attrs;
85119             for (attr in attrs) {
85120                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
85121                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
85122                         return '-' + v.toLowerCase();
85123                     }));
85124                     cssArr.duration.push(duration + 'ms');
85125                     cssArr.easing.push(easing);
85126                 }
85127             }
85128         }
85129         attributes = cssArr.attrs.join(',');
85130         duration = cssArr.duration.join(',');
85131         easing = cssArr.easing.join(', ');
85132         for (i = 0; i < ln; i++) {
85133             attrs = targetData[i].attrs;
85134             for (attr in attrs) {
85135                 ln2 = attrs[attr].length;
85136                 for (j = 0; j < ln2; j++) {
85137                     o = attrs[attr][j];
85138                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
85139                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
85140                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
85141                     o[0].setStyle(attr, o[1]);
85142
85143                     // Must trigger reflow to make this get used as the start point for the transition that follows
85144                     if (isFirstFrame) {
85145                         o = o[0].dom.offsetWidth;
85146                     }
85147                     else {
85148                         // Remove transition properties when completed.
85149                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
85150                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
85151                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
85152                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
85153                         }, o[0], { single: true });
85154                     }
85155                 }
85156             }
85157         }
85158     }
85159 });
85160 /**
85161  * @class Ext.fx.target.CompositeElementCSS
85162  * @extends Ext.fx.target.CompositeElement
85163  * 
85164  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
85165  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
85166  * the group to be animated as a whole. In general this class will not be created directly, 
85167  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
85168  * will be created.
85169  */
85170 Ext.define('Ext.fx.target.CompositeElementCSS', {
85171
85172     /* Begin Definitions */
85173
85174     extend: 'Ext.fx.target.CompositeElement',
85175
85176     requires: ['Ext.fx.target.ElementCSS'],
85177
85178     /* End Definitions */
85179     setAttr: function() {
85180         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
85181     }
85182 });
85183 /**
85184  * @class Ext.layout.container.AbstractFit
85185  * @extends Ext.layout.container.Container
85186  * @private
85187  */
85188 Ext.define('Ext.layout.container.AbstractFit', {
85189
85190     /* Begin Definitions */
85191
85192     extend: 'Ext.layout.container.Container',
85193
85194     /* End Definitions */
85195
85196     itemCls: Ext.baseCSSPrefix + 'fit-item',
85197     targetCls: Ext.baseCSSPrefix + 'layout-fit',
85198     type: 'fit'
85199 });
85200 /**
85201  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
85202  * container. This class is intended to be extended or created via the `layout: 'fit'`
85203  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
85204  *
85205  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
85206  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
85207  * panels, only the first one will be displayed.
85208  *
85209  *     @example
85210  *     Ext.create('Ext.panel.Panel', {
85211  *         title: 'Fit Layout',
85212  *         width: 300,
85213  *         height: 150,
85214  *         layout:'fit',
85215  *         items: {
85216  *             title: 'Inner Panel',
85217  *             html: 'This is the inner panel content',
85218  *             bodyPadding: 20,
85219  *             border: false
85220  *         },
85221  *         renderTo: Ext.getBody()
85222  *     });
85223  */
85224 Ext.define('Ext.layout.container.Fit', {
85225
85226     /* Begin Definitions */
85227
85228     extend: 'Ext.layout.container.AbstractFit',
85229     alias: 'layout.fit',
85230     alternateClassName: 'Ext.layout.FitLayout',
85231     requires: ['Ext.layout.container.Box'],
85232
85233     /* End Definitions */
85234
85235     /**
85236      * @cfg {Object} defaultMargins
85237      * <p>If the individual contained items do not have a <tt>margins</tt>
85238      * property specified or margin specified via CSS, the default margins from this property will be
85239      * applied to each item.</p>
85240      * <br><p>This property may be specified as an object containing margins
85241      * to apply in the format:</p><pre><code>
85242 {
85243     top: (top margin),
85244     right: (right margin),
85245     bottom: (bottom margin),
85246     left: (left margin)
85247 }</code></pre>
85248      * <p>This property may also be specified as a string containing
85249      * space-separated, numeric margin values. The order of the sides associated
85250      * with each value matches the way CSS processes margin values:</p>
85251      * <div class="mdetail-params"><ul>
85252      * <li>If there is only one value, it applies to all sides.</li>
85253      * <li>If there are two values, the top and bottom borders are set to the
85254      * first value and the right and left are set to the second.</li>
85255      * <li>If there are three values, the top is set to the first value, the left
85256      * and right are set to the second, and the bottom is set to the third.</li>
85257      * <li>If there are four values, they apply to the top, right, bottom, and
85258      * left, respectively.</li>
85259      * </ul></div>
85260      * <p>Defaults to:</p><pre><code>
85261      * {top:0, right:0, bottom:0, left:0}
85262      * </code></pre>
85263      */
85264     defaultMargins: {
85265         top: 0,
85266         right: 0,
85267         bottom: 0,
85268         left: 0
85269     },
85270
85271     // @private
85272     onLayout : function() {
85273         var me = this,
85274             size,
85275             item,
85276             margins;
85277         me.callParent();
85278
85279         if (me.owner.items.length) {
85280             item = me.owner.items.get(0);
85281             margins = item.margins || me.defaultMargins;
85282             size = me.getLayoutTargetSize();
85283             size.width  -= margins.width;
85284             size.height -= margins.height;
85285             me.setItemBox(item, size);
85286
85287             // If any margins were configure either through the margins config, or in the CSS style,
85288             // Then positioning will be used.
85289             if (margins.left || margins.top) {
85290                 item.setPosition(margins.left, margins.top);
85291             }
85292         }
85293     },
85294
85295     getTargetBox : function() {
85296         return this.getLayoutTargetSize();
85297     },
85298
85299     setItemBox : function(item, box) {
85300         var me = this;
85301         if (item && box.height > 0) {
85302             if (!me.owner.isFixedWidth()) {
85303                box.width = undefined;
85304             }
85305             if (!me.owner.isFixedHeight()) {
85306                box.height = undefined;
85307             }
85308             me.setItemSize(item, box.width, box.height);
85309         }
85310     },
85311
85312     configureItem: function(item) {
85313
85314         // Card layout only controls dimensions which IT has controlled.
85315         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
85316         item.layoutManagedHeight = 0;
85317         item.layoutManagedWidth = 0;
85318
85319         this.callParent(arguments);
85320     }
85321 }, function() {
85322     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
85323     // (Defaulting to "0 0 0 0")
85324     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
85325 });
85326 /**
85327  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
85328  * @private
85329  */
85330 Ext.define('Ext.layout.container.AbstractCard', {
85331
85332     /* Begin Definitions */
85333
85334     extend: 'Ext.layout.container.Fit',
85335
85336     /* End Definitions */
85337
85338     type: 'card',
85339
85340     sizeAllCards: false,
85341
85342     hideInactive: true,
85343
85344     /**
85345      * @cfg {Boolean} deferredRender
85346      * True to render each contained item at the time it becomes active, false to render all contained items
85347      * as soon as the layout is rendered.  If there is a significant amount of content or
85348      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
85349      * true might improve performance.
85350      */
85351     deferredRender : false,
85352
85353     beforeLayout: function() {
85354         var me = this;
85355         me.getActiveItem();
85356         if (me.activeItem && me.deferredRender) {
85357             me.renderItems([me.activeItem], me.getRenderTarget());
85358             return true;
85359         }
85360         else {
85361             return this.callParent(arguments);
85362         }
85363     },
85364
85365     renderChildren: function () {
85366         if (!this.deferredRender) {
85367             this.getActiveItem();
85368             this.callParent();
85369         }
85370     },
85371
85372     onLayout: function() {
85373         var me = this,
85374             activeItem = me.activeItem,
85375             items = me.getVisibleItems(),
85376             ln = items.length,
85377             targetBox = me.getTargetBox(),
85378             i, item;
85379
85380         for (i = 0; i < ln; i++) {
85381             item = items[i];
85382             me.setItemBox(item, targetBox);
85383         }
85384
85385         if (!me.firstActivated && activeItem) {
85386             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
85387                 activeItem.fireEvent('activate', activeItem);
85388             }
85389             me.firstActivated = true;
85390         }
85391     },
85392
85393     isValidParent : function(item, target, position) {
85394         // Note: Card layout does not care about order within the target because only one is ever visible.
85395         // We only care whether the item is a direct child of the target.
85396         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
85397         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
85398     },
85399
85400     /**
85401      * Return the active (visible) component in the layout.
85402      * @returns {Ext.Component}
85403      */
85404     getActiveItem: function() {
85405         var me = this;
85406         if (!me.activeItem && me.owner) {
85407             me.activeItem = me.parseActiveItem(me.owner.activeItem);
85408         }
85409
85410         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
85411             return me.activeItem;
85412         }
85413
85414         return null;
85415     },
85416
85417     // @private
85418     parseActiveItem: function(item) {
85419         if (item && item.isComponent) {
85420             return item;
85421         }
85422         else if (typeof item == 'number' || item === undefined) {
85423             return this.getLayoutItems()[item || 0];
85424         }
85425         else {
85426             return this.owner.getComponent(item);
85427         }
85428     },
85429
85430     // @private
85431     configureItem: function(item, position) {
85432         this.callParent([item, position]);
85433         if (this.hideInactive && this.activeItem !== item) {
85434             item.hide();
85435         }
85436         else {
85437             item.show();
85438         }
85439     },
85440
85441     onRemove: function(component) {
85442         if (component === this.activeItem) {
85443             this.activeItem = null;
85444             if (this.owner.items.getCount() === 0) {
85445                 this.firstActivated = false;
85446             }
85447         }
85448     },
85449
85450     // @private
85451     getAnimation: function(newCard, owner) {
85452         var newAnim = (newCard || {}).cardSwitchAnimation;
85453         if (newAnim === false) {
85454             return false;
85455         }
85456         return newAnim || owner.cardSwitchAnimation;
85457     },
85458
85459     /**
85460      * Return the active (visible) component in the layout to the next card
85461      * @returns {Ext.Component} The next component or false.
85462      */
85463     getNext: function() {
85464         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85465         //should come back in 4.1
85466         var wrap = arguments[0];
85467         var items = this.getLayoutItems(),
85468             index = Ext.Array.indexOf(items, this.activeItem);
85469         return items[index + 1] || (wrap ? items[0] : false);
85470     },
85471
85472     /**
85473      * Sets the active (visible) component in the layout to the next card
85474      * @return {Ext.Component} the activated component or false when nothing activated.
85475      */
85476     next: function() {
85477         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85478         //should come back in 4.1
85479         var anim = arguments[0], wrap = arguments[1];
85480         return this.setActiveItem(this.getNext(wrap), anim);
85481     },
85482
85483     /**
85484      * Return the active (visible) component in the layout to the previous card
85485      * @returns {Ext.Component} The previous component or false.
85486      */
85487     getPrev: function() {
85488         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85489         //should come back in 4.1
85490         var wrap = arguments[0];
85491         var items = this.getLayoutItems(),
85492             index = Ext.Array.indexOf(items, this.activeItem);
85493         return items[index - 1] || (wrap ? items[items.length - 1] : false);
85494     },
85495
85496     /**
85497      * Sets the active (visible) component in the layout to the previous card
85498      * @return {Ext.Component} the activated component or false when nothing activated.
85499      */
85500     prev: function() {
85501         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85502         //should come back in 4.1
85503         var anim = arguments[0], wrap = arguments[1];
85504         return this.setActiveItem(this.getPrev(wrap), anim);
85505     }
85506 });
85507
85508 /**
85509  * Tracks what records are currently selected in a databound component.
85510  *
85511  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
85512  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
85513  * and provide a way to binding to the component.
85514  *
85515  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
85516  * subclasses to update the UI widget.
85517  */
85518 Ext.define('Ext.selection.Model', {
85519     extend: 'Ext.util.Observable',
85520     alternateClassName: 'Ext.AbstractSelectionModel',
85521     requires: ['Ext.data.StoreManager'],
85522     // lastSelected
85523
85524     /**
85525      * @cfg {String} mode
85526      * Mode of selection.  Valid values are:
85527      *
85528      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
85529      *   deselecting that item.  This is the default.
85530      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
85531      *   select or deselect an item.
85532      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
85533      */
85534
85535     /**
85536      * @cfg {Boolean} allowDeselect
85537      * Allow users to deselect a record in a DataView, List or Grid.
85538      * Only applicable when the {@link #mode} is 'SINGLE'.
85539      */
85540     allowDeselect: false,
85541
85542     /**
85543      * @property {Ext.util.MixedCollection} selected
85544      * A MixedCollection that maintains all of the currently selected records. Read-only.
85545      */
85546     selected: null,
85547
85548     /**
85549      * Prune records when they are removed from the store from the selection.
85550      * This is a private flag. For an example of its usage, take a look at
85551      * Ext.selection.TreeModel.
85552      * @private
85553      */
85554     pruneRemoved: true,
85555
85556     constructor: function(cfg) {
85557         var me = this;
85558
85559         cfg = cfg || {};
85560         Ext.apply(me, cfg);
85561
85562         me.addEvents(
85563             /**
85564              * @event
85565              * Fired after a selection change has occurred
85566              * @param {Ext.selection.Model} this
85567              * @param {Ext.data.Model[]} selected The selected records
85568              */
85569             'selectionchange'
85570         );
85571
85572         me.modes = {
85573             SINGLE: true,
85574             SIMPLE: true,
85575             MULTI: true
85576         };
85577
85578         // sets this.selectionMode
85579         me.setSelectionMode(cfg.mode || me.mode);
85580
85581         // maintains the currently selected records.
85582         me.selected = Ext.create('Ext.util.MixedCollection');
85583
85584         me.callParent(arguments);
85585     },
85586
85587     // binds the store to the selModel.
85588     bind : function(store, initial){
85589         var me = this;
85590
85591         if(!initial && me.store){
85592             if(store !== me.store && me.store.autoDestroy){
85593                 me.store.destroyStore();
85594             }else{
85595                 me.store.un("add", me.onStoreAdd, me);
85596                 me.store.un("clear", me.onStoreClear, me);
85597                 me.store.un("remove", me.onStoreRemove, me);
85598                 me.store.un("update", me.onStoreUpdate, me);
85599             }
85600         }
85601         if(store){
85602             store = Ext.data.StoreManager.lookup(store);
85603             store.on({
85604                 add: me.onStoreAdd,
85605                 clear: me.onStoreClear,
85606                 remove: me.onStoreRemove,
85607                 update: me.onStoreUpdate,
85608                 scope: me
85609             });
85610         }
85611         me.store = store;
85612         if(store && !initial) {
85613             me.refresh();
85614         }
85615     },
85616
85617     /**
85618      * Selects all records in the view.
85619      * @param {Boolean} suppressEvent True to suppress any select events
85620      */
85621     selectAll: function(suppressEvent) {
85622         var me = this,
85623             selections = me.store.getRange(),
85624             i = 0,
85625             len = selections.length,
85626             start = me.getSelection().length;
85627
85628         me.bulkChange = true;
85629         for (; i < len; i++) {
85630             me.doSelect(selections[i], true, suppressEvent);
85631         }
85632         delete me.bulkChange;
85633         // fire selection change only if the number of selections differs
85634         me.maybeFireSelectionChange(me.getSelection().length !== start);
85635     },
85636
85637     /**
85638      * Deselects all records in the view.
85639      * @param {Boolean} suppressEvent True to suppress any deselect events
85640      */
85641     deselectAll: function(suppressEvent) {
85642         var me = this,
85643             selections = me.getSelection(),
85644             i = 0,
85645             len = selections.length,
85646             start = me.getSelection().length;
85647
85648         me.bulkChange = true;
85649         for (; i < len; i++) {
85650             me.doDeselect(selections[i], suppressEvent);
85651         }
85652         delete me.bulkChange;
85653         // fire selection change only if the number of selections differs
85654         me.maybeFireSelectionChange(me.getSelection().length !== start);
85655     },
85656
85657     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
85658     // selection modes. Requires that an event be passed so that we can know
85659     // if user held ctrl or shift.
85660     selectWithEvent: function(record, e, keepExisting) {
85661         var me = this;
85662
85663         switch (me.selectionMode) {
85664             case 'MULTI':
85665                 if (e.ctrlKey && me.isSelected(record)) {
85666                     me.doDeselect(record, false);
85667                 } else if (e.shiftKey && me.lastFocused) {
85668                     me.selectRange(me.lastFocused, record, e.ctrlKey);
85669                 } else if (e.ctrlKey) {
85670                     me.doSelect(record, true, false);
85671                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
85672                     me.doSelect(record, keepExisting, false);
85673                 } else {
85674                     me.doSelect(record, false);
85675                 }
85676                 break;
85677             case 'SIMPLE':
85678                 if (me.isSelected(record)) {
85679                     me.doDeselect(record);
85680                 } else {
85681                     me.doSelect(record, true);
85682                 }
85683                 break;
85684             case 'SINGLE':
85685                 // if allowDeselect is on and this record isSelected, deselect it
85686                 if (me.allowDeselect && me.isSelected(record)) {
85687                     me.doDeselect(record);
85688                 // select the record and do NOT maintain existing selections
85689                 } else {
85690                     me.doSelect(record, false);
85691                 }
85692                 break;
85693         }
85694     },
85695
85696     /**
85697      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
85698      * All rows in between startRow and endRow are also selected.
85699      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
85700      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
85701      * @param {Boolean} [keepExisting] True to retain existing selections
85702      */
85703     selectRange : function(startRow, endRow, keepExisting, dir){
85704         var me = this,
85705             store = me.store,
85706             selectedCount = 0,
85707             i,
85708             tmp,
85709             dontDeselect,
85710             records = [];
85711
85712         if (me.isLocked()){
85713             return;
85714         }
85715
85716         if (!keepExisting) {
85717             me.deselectAll(true);
85718         }
85719
85720         if (!Ext.isNumber(startRow)) {
85721             startRow = store.indexOf(startRow);
85722         }
85723         if (!Ext.isNumber(endRow)) {
85724             endRow = store.indexOf(endRow);
85725         }
85726
85727         // swap values
85728         if (startRow > endRow){
85729             tmp = endRow;
85730             endRow = startRow;
85731             startRow = tmp;
85732         }
85733
85734         for (i = startRow; i <= endRow; i++) {
85735             if (me.isSelected(store.getAt(i))) {
85736                 selectedCount++;
85737             }
85738         }
85739
85740         if (!dir) {
85741             dontDeselect = -1;
85742         } else {
85743             dontDeselect = (dir == 'up') ? startRow : endRow;
85744         }
85745
85746         for (i = startRow; i <= endRow; i++){
85747             if (selectedCount == (endRow - startRow + 1)) {
85748                 if (i != dontDeselect) {
85749                     me.doDeselect(i, true);
85750                 }
85751             } else {
85752                 records.push(store.getAt(i));
85753             }
85754         }
85755         me.doMultiSelect(records, true);
85756     },
85757
85758     /**
85759      * Selects a record instance by record instance or index.
85760      * @param {Ext.data.Model[]/Number} records An array of records or an index
85761      * @param {Boolean} [keepExisting] True to retain existing selections
85762      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
85763      */
85764     select: function(records, keepExisting, suppressEvent) {
85765         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
85766         if (Ext.isDefined(records)) {
85767             this.doSelect(records, keepExisting, suppressEvent);
85768         }
85769     },
85770
85771     /**
85772      * Deselects a record instance by record instance or index.
85773      * @param {Ext.data.Model[]/Number} records An array of records or an index
85774      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
85775      */
85776     deselect: function(records, suppressEvent) {
85777         this.doDeselect(records, suppressEvent);
85778     },
85779
85780     doSelect: function(records, keepExisting, suppressEvent) {
85781         var me = this,
85782             record;
85783
85784         if (me.locked) {
85785             return;
85786         }
85787         if (typeof records === "number") {
85788             records = [me.store.getAt(records)];
85789         }
85790         if (me.selectionMode == "SINGLE" && records) {
85791             record = records.length ? records[0] : records;
85792             me.doSingleSelect(record, suppressEvent);
85793         } else {
85794             me.doMultiSelect(records, keepExisting, suppressEvent);
85795         }
85796     },
85797
85798     doMultiSelect: function(records, keepExisting, suppressEvent) {
85799         var me = this,
85800             selected = me.selected,
85801             change = false,
85802             i = 0,
85803             len, record;
85804
85805         if (me.locked) {
85806             return;
85807         }
85808
85809
85810         records = !Ext.isArray(records) ? [records] : records;
85811         len = records.length;
85812         if (!keepExisting && selected.getCount() > 0) {
85813             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
85814                 return;
85815             }
85816             // TODO - coalesce the selectionchange event in deselect w/the one below...
85817         }
85818
85819         function commit () {
85820             selected.add(record);
85821             change = true;
85822         }
85823
85824         for (; i < len; i++) {
85825             record = records[i];
85826             if (keepExisting && me.isSelected(record)) {
85827                 continue;
85828             }
85829             me.lastSelected = record;
85830
85831             me.onSelectChange(record, true, suppressEvent, commit);
85832         }
85833         me.setLastFocused(record, suppressEvent);
85834         // fire selchange if there was a change and there is no suppressEvent flag
85835         me.maybeFireSelectionChange(change && !suppressEvent);
85836     },
85837
85838     // records can be an index, a record or an array of records
85839     doDeselect: function(records, suppressEvent) {
85840         var me = this,
85841             selected = me.selected,
85842             i = 0,
85843             len, record,
85844             attempted = 0,
85845             accepted = 0;
85846
85847         if (me.locked) {
85848             return false;
85849         }
85850
85851         if (typeof records === "number") {
85852             records = [me.store.getAt(records)];
85853         } else if (!Ext.isArray(records)) {
85854             records = [records];
85855         }
85856
85857         function commit () {
85858             ++accepted;
85859             selected.remove(record);
85860         }
85861
85862         len = records.length;
85863
85864         for (; i < len; i++) {
85865             record = records[i];
85866             if (me.isSelected(record)) {
85867                 if (me.lastSelected == record) {
85868                     me.lastSelected = selected.last();
85869                 }
85870                 ++attempted;
85871                 me.onSelectChange(record, false, suppressEvent, commit);
85872             }
85873         }
85874
85875         // fire selchange if there was a change and there is no suppressEvent flag
85876         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
85877         return accepted === attempted;
85878     },
85879
85880     doSingleSelect: function(record, suppressEvent) {
85881         var me = this,
85882             changed = false,
85883             selected = me.selected;
85884
85885         if (me.locked) {
85886             return;
85887         }
85888         // already selected.
85889         // should we also check beforeselect?
85890         if (me.isSelected(record)) {
85891             return;
85892         }
85893
85894         function commit () {
85895             me.bulkChange = true;
85896             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
85897                 delete me.bulkChange;
85898                 return false;
85899             }
85900             delete me.bulkChange;
85901
85902             selected.add(record);
85903             me.lastSelected = record;
85904             changed = true;
85905         }
85906
85907         me.onSelectChange(record, true, suppressEvent, commit);
85908
85909         if (changed) {
85910             if (!suppressEvent) {
85911                 me.setLastFocused(record);
85912             }
85913             me.maybeFireSelectionChange(!suppressEvent);
85914         }
85915     },
85916
85917     /**
85918      * Sets a record as the last focused record. This does NOT mean
85919      * that the record has been selected.
85920      * @param {Ext.data.Model} record
85921      */
85922     setLastFocused: function(record, supressFocus) {
85923         var me = this,
85924             recordBeforeLast = me.lastFocused;
85925         me.lastFocused = record;
85926         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
85927     },
85928
85929     /**
85930      * Determines if this record is currently focused.
85931      * @param {Ext.data.Model} record
85932      */
85933     isFocused: function(record) {
85934         return record === this.getLastFocused();
85935     },
85936
85937
85938     // fire selection change as long as true is not passed
85939     // into maybeFireSelectionChange
85940     maybeFireSelectionChange: function(fireEvent) {
85941         var me = this;
85942         if (fireEvent && !me.bulkChange) {
85943             me.fireEvent('selectionchange', me, me.getSelection());
85944         }
85945     },
85946
85947     /**
85948      * Returns the last selected record.
85949      */
85950     getLastSelected: function() {
85951         return this.lastSelected;
85952     },
85953
85954     getLastFocused: function() {
85955         return this.lastFocused;
85956     },
85957
85958     /**
85959      * Returns an array of the currently selected records.
85960      * @return {Ext.data.Model[]} The selected records
85961      */
85962     getSelection: function() {
85963         return this.selected.getRange();
85964     },
85965
85966     /**
85967      * Returns the current selectionMode.
85968      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
85969      */
85970     getSelectionMode: function() {
85971         return this.selectionMode;
85972     },
85973
85974     /**
85975      * Sets the current selectionMode.
85976      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
85977      */
85978     setSelectionMode: function(selMode) {
85979         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
85980         // set to mode specified unless it doesnt exist, in that case
85981         // use single.
85982         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
85983     },
85984
85985     /**
85986      * Returns true if the selections are locked.
85987      * @return {Boolean}
85988      */
85989     isLocked: function() {
85990         return this.locked;
85991     },
85992
85993     /**
85994      * Locks the current selection and disables any changes from happening to the selection.
85995      * @param {Boolean} locked  True to lock, false to unlock.
85996      */
85997     setLocked: function(locked) {
85998         this.locked = !!locked;
85999     },
86000
86001     /**
86002      * Returns true if the specified row is selected.
86003      * @param {Ext.data.Model/Number} record The record or index of the record to check
86004      * @return {Boolean}
86005      */
86006     isSelected: function(record) {
86007         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
86008         return this.selected.indexOf(record) !== -1;
86009     },
86010
86011     /**
86012      * Returns true if there are any a selected records.
86013      * @return {Boolean}
86014      */
86015     hasSelection: function() {
86016         return this.selected.getCount() > 0;
86017     },
86018
86019     refresh: function() {
86020         var me = this,
86021             toBeSelected = [],
86022             oldSelections = me.getSelection(),
86023             len = oldSelections.length,
86024             selection,
86025             change,
86026             i = 0,
86027             lastFocused = this.getLastFocused();
86028
86029         // check to make sure that there are no records
86030         // missing after the refresh was triggered, prune
86031         // them from what is to be selected if so
86032         for (; i < len; i++) {
86033             selection = oldSelections[i];
86034             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
86035                 toBeSelected.push(selection);
86036             }
86037         }
86038
86039         // there was a change from the old selected and
86040         // the new selection
86041         if (me.selected.getCount() != toBeSelected.length) {
86042             change = true;
86043         }
86044
86045         me.clearSelections();
86046
86047         if (me.store.indexOf(lastFocused) !== -1) {
86048             // restore the last focus but supress restoring focus
86049             this.setLastFocused(lastFocused, true);
86050         }
86051
86052         if (toBeSelected.length) {
86053             // perform the selection again
86054             me.doSelect(toBeSelected, false, true);
86055         }
86056
86057         me.maybeFireSelectionChange(change);
86058     },
86059
86060     /**
86061      * A fast reset of the selections without firing events, updating the ui, etc.
86062      * For private usage only.
86063      * @private
86064      */
86065     clearSelections: function() {
86066         // reset the entire selection to nothing
86067         this.selected.clear();
86068         this.lastSelected = null;
86069         this.setLastFocused(null);
86070     },
86071
86072     // when a record is added to a store
86073     onStoreAdd: function() {
86074
86075     },
86076
86077     // when a store is cleared remove all selections
86078     // (if there were any)
86079     onStoreClear: function() {
86080         if (this.selected.getCount > 0) {
86081             this.clearSelections();
86082             this.maybeFireSelectionChange(true);
86083         }
86084     },
86085
86086     // prune records from the SelectionModel if
86087     // they were selected at the time they were
86088     // removed.
86089     onStoreRemove: function(store, record) {
86090         var me = this,
86091             selected = me.selected;
86092
86093         if (me.locked || !me.pruneRemoved) {
86094             return;
86095         }
86096
86097         if (selected.remove(record)) {
86098             if (me.lastSelected == record) {
86099                 me.lastSelected = null;
86100             }
86101             if (me.getLastFocused() == record) {
86102                 me.setLastFocused(null);
86103             }
86104             me.maybeFireSelectionChange(true);
86105         }
86106     },
86107
86108     /**
86109      * Returns the count of selected records.
86110      * @return {Number} The number of selected records
86111      */
86112     getCount: function() {
86113         return this.selected.getCount();
86114     },
86115
86116     // cleanup.
86117     destroy: function() {
86118
86119     },
86120
86121     // if records are updated
86122     onStoreUpdate: function() {
86123
86124     },
86125
86126     // @abstract
86127     onSelectChange: function(record, isSelected, suppressEvent) {
86128
86129     },
86130
86131     // @abstract
86132     onLastFocusChanged: function(oldFocused, newFocused) {
86133
86134     },
86135
86136     // @abstract
86137     onEditorKey: function(field, e) {
86138
86139     },
86140
86141     // @abstract
86142     bindComponent: function(cmp) {
86143
86144     }
86145 });
86146 /**
86147  * @class Ext.selection.DataViewModel
86148  * @ignore
86149  */
86150 Ext.define('Ext.selection.DataViewModel', {
86151     extend: 'Ext.selection.Model',
86152
86153     requires: ['Ext.util.KeyNav'],
86154
86155     deselectOnContainerClick: true,
86156
86157     /**
86158      * @cfg {Boolean} enableKeyNav
86159      *
86160      * Turns on/off keyboard navigation within the DataView.
86161      */
86162     enableKeyNav: true,
86163
86164     constructor: function(cfg){
86165         this.addEvents(
86166             /**
86167              * @event beforedeselect
86168              * Fired before a record is deselected. If any listener returns false, the
86169              * deselection is cancelled.
86170              * @param {Ext.selection.DataViewModel} this
86171              * @param {Ext.data.Model} record The deselected record
86172              */
86173             'beforedeselect',
86174
86175             /**
86176              * @event beforeselect
86177              * Fired before a record is selected. If any listener returns false, the
86178              * selection is cancelled.
86179              * @param {Ext.selection.DataViewModel} this
86180              * @param {Ext.data.Model} record The selected record
86181              */
86182             'beforeselect',
86183
86184             /**
86185              * @event deselect
86186              * Fired after a record is deselected
86187              * @param {Ext.selection.DataViewModel} this
86188              * @param  {Ext.data.Model} record The deselected record
86189              */
86190             'deselect',
86191
86192             /**
86193              * @event select
86194              * Fired after a record is selected
86195              * @param {Ext.selection.DataViewModel} this
86196              * @param  {Ext.data.Model} record The selected record
86197              */
86198             'select'
86199         );
86200         this.callParent(arguments);
86201     },
86202
86203     bindComponent: function(view) {
86204         var me = this,
86205             eventListeners = {
86206                 refresh: me.refresh,
86207                 scope: me
86208             };
86209
86210         me.view = view;
86211         me.bind(view.getStore());
86212
86213         view.on(view.triggerEvent, me.onItemClick, me);
86214         view.on(view.triggerCtEvent, me.onContainerClick, me);
86215
86216         view.on(eventListeners);
86217
86218         if (me.enableKeyNav) {
86219             me.initKeyNav(view);
86220         }
86221     },
86222
86223     onItemClick: function(view, record, item, index, e) {
86224         this.selectWithEvent(record, e);
86225     },
86226
86227     onContainerClick: function() {
86228         if (this.deselectOnContainerClick) {
86229             this.deselectAll();
86230         }
86231     },
86232
86233     initKeyNav: function(view) {
86234         var me = this;
86235
86236         if (!view.rendered) {
86237             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
86238             return;
86239         }
86240
86241         view.el.set({
86242             tabIndex: -1
86243         });
86244         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
86245             down: Ext.pass(me.onNavKey, [1], me),
86246             right: Ext.pass(me.onNavKey, [1], me),
86247             left: Ext.pass(me.onNavKey, [-1], me),
86248             up: Ext.pass(me.onNavKey, [-1], me),
86249             scope: me
86250         });
86251     },
86252
86253     onNavKey: function(step) {
86254         step = step || 1;
86255         var me = this,
86256             view = me.view,
86257             selected = me.getSelection()[0],
86258             numRecords = me.view.store.getCount(),
86259             idx;
86260
86261         if (selected) {
86262             idx = view.indexOf(view.getNode(selected)) + step;
86263         } else {
86264             idx = 0;
86265         }
86266
86267         if (idx < 0) {
86268             idx = numRecords - 1;
86269         } else if (idx >= numRecords) {
86270             idx = 0;
86271         }
86272
86273         me.select(idx);
86274     },
86275
86276     // Allow the DataView to update the ui
86277     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
86278         var me = this,
86279             view = me.view,
86280             eventName = isSelected ? 'select' : 'deselect';
86281
86282         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
86283                 commitFn() !== false) {
86284
86285             if (isSelected) {
86286                 view.onItemSelect(record);
86287             } else {
86288                 view.onItemDeselect(record);
86289             }
86290
86291             if (!suppressEvent) {
86292                 me.fireEvent(eventName, me, record);
86293             }
86294         }
86295     },
86296     
86297     destroy: function(){
86298         Ext.destroy(this.keyNav);
86299         this.callParent();
86300     }
86301 });
86302
86303 /**
86304  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
86305  * options, such as:
86306  *
86307  * - {@link #path}
86308  * - {@link #expires}
86309  * - {@link #domain}
86310  * - {@link #secure}
86311  *
86312  * Example:
86313  *
86314  *     Ext.create('Ext.state.CookieProvider', {
86315  *         path: "/cgi-bin/",
86316  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
86317  *         domain: "sencha.com"
86318  *     });
86319  *
86320  *     Ext.state.Manager.setProvider(cp);
86321  *
86322  * @constructor
86323  * Creates a new CookieProvider.
86324  * @param {Object} config (optional) Config object.
86325  * @return {Object}
86326  */
86327 Ext.define('Ext.state.CookieProvider', {
86328     extend: 'Ext.state.Provider',
86329
86330     /**
86331      * @cfg {String} path
86332      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
86333      */
86334
86335     /**
86336      * @cfg {Date} expires
86337      * The cookie expiration date. Defaults to 7 days from now.
86338      */
86339
86340     /**
86341      * @cfg {String} domain
86342      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
86343      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
86344      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
86345      * the 'www' like 'www.sencha.com'.
86346      */
86347
86348     /**
86349      * @cfg {Boolean} [secure=false]
86350      * True if the site is using SSL
86351      */
86352
86353     /**
86354      * Creates a new CookieProvider.
86355      * @param {Object} [config] Config object.
86356      */
86357     constructor : function(config){
86358         var me = this;
86359         me.path = "/";
86360         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
86361         me.domain = null;
86362         me.secure = false;
86363         me.callParent(arguments);
86364         me.state = me.readCookies();
86365     },
86366
86367     // private
86368     set : function(name, value){
86369         var me = this;
86370
86371         if(typeof value == "undefined" || value === null){
86372             me.clear(name);
86373             return;
86374         }
86375         me.setCookie(name, value);
86376         me.callParent(arguments);
86377     },
86378
86379     // private
86380     clear : function(name){
86381         this.clearCookie(name);
86382         this.callParent(arguments);
86383     },
86384
86385     // private
86386     readCookies : function(){
86387         var cookies = {},
86388             c = document.cookie + ";",
86389             re = /\s?(.*?)=(.*?);/g,
86390             prefix = this.prefix,
86391             len = prefix.length,
86392             matches,
86393             name,
86394             value;
86395
86396         while((matches = re.exec(c)) != null){
86397             name = matches[1];
86398             value = matches[2];
86399             if (name && name.substring(0, len) == prefix){
86400                 cookies[name.substr(len)] = this.decodeValue(value);
86401             }
86402         }
86403         return cookies;
86404     },
86405
86406     // private
86407     setCookie : function(name, value){
86408         var me = this;
86409
86410         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
86411            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
86412            ((me.path == null) ? "" : ("; path=" + me.path)) +
86413            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86414            ((me.secure == true) ? "; secure" : "");
86415     },
86416
86417     // private
86418     clearCookie : function(name){
86419         var me = this;
86420
86421         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
86422            ((me.path == null) ? "" : ("; path=" + me.path)) +
86423            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86424            ((me.secure == true) ? "; secure" : "");
86425     }
86426 });
86427
86428 /**
86429  * @class Ext.state.LocalStorageProvider
86430  * @extends Ext.state.Provider
86431  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
86432  * If the browser does not support local storage, an exception will be thrown upon instantiating
86433  * this class.
86434  */
86435
86436 Ext.define('Ext.state.LocalStorageProvider', {
86437     /* Begin Definitions */
86438     
86439     extend: 'Ext.state.Provider',
86440     
86441     alias: 'state.localstorage',
86442     
86443     /* End Definitions */
86444    
86445     constructor: function(){
86446         var me = this;
86447         me.callParent(arguments);
86448         me.store = me.getStorageObject();
86449         me.state = me.readLocalStorage();
86450     },
86451     
86452     readLocalStorage: function(){
86453         var store = this.store,
86454             i = 0,
86455             len = store.length,
86456             prefix = this.prefix,
86457             prefixLen = prefix.length,
86458             data = {},
86459             key;
86460             
86461         for (; i < len; ++i) {
86462             key = store.key(i);
86463             if (key.substring(0, prefixLen) == prefix) {
86464                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
86465             }            
86466         }
86467         return data;
86468     },
86469     
86470     set : function(name, value){
86471         var me = this;
86472         
86473         me.clear(name);
86474         if (typeof value == "undefined" || value === null) {
86475             return;
86476         }
86477         me.store.setItem(me.prefix + name, me.encodeValue(value));
86478         me.callParent(arguments);
86479     },
86480
86481     // private
86482     clear : function(name){
86483         this.store.removeItem(this.prefix + name);
86484         this.callParent(arguments);
86485     },
86486     
86487     getStorageObject: function(){
86488         try {
86489             var supports = 'localStorage' in window && window['localStorage'] !== null;
86490             if (supports) {
86491                 return window.localStorage;
86492             }
86493         } catch (e) {
86494             return false;
86495         }
86496     }    
86497 });
86498
86499 /**
86500  * Represents a 2D point with x and y properties, useful for comparison and instantiation
86501  * from an event:
86502  *
86503  *     var point = Ext.util.Point.fromEvent(e);
86504  *
86505  */
86506 Ext.define('Ext.util.Point', {
86507
86508     /* Begin Definitions */
86509     extend: 'Ext.util.Region',
86510
86511     statics: {
86512
86513         /**
86514          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
86515          * @static
86516          * @param {Event} e The event
86517          * @return {Ext.util.Point}
86518          */
86519         fromEvent: function(e) {
86520             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
86521             return new this(e.pageX, e.pageY);
86522         }
86523     },
86524
86525     /* End Definitions */
86526
86527     /**
86528      * Creates a point from two coordinates.
86529      * @param {Number} x X coordinate.
86530      * @param {Number} y Y coordinate.
86531      */
86532     constructor: function(x, y) {
86533         this.callParent([y, x, y, x]);
86534     },
86535
86536     /**
86537      * Returns a human-eye-friendly string that represents this point,
86538      * useful for debugging
86539      * @return {String}
86540      */
86541     toString: function() {
86542         return "Point[" + this.x + "," + this.y + "]";
86543     },
86544
86545     /**
86546      * Compare this point and another point
86547      * @param {Ext.util.Point/Object} The point to compare with, either an instance
86548      * of Ext.util.Point or an object with left and top properties
86549      * @return {Boolean} Returns whether they are equivalent
86550      */
86551     equals: function(p) {
86552         return (this.x == p.x && this.y == p.y);
86553     },
86554
86555     /**
86556      * Whether the given point is not away from this point within the given threshold amount.
86557      * @param {Ext.util.Point/Object} p The point to check with, either an instance
86558      * of Ext.util.Point or an object with left and top properties
86559      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
86560      * @return {Boolean}
86561      */
86562     isWithin: function(p, threshold) {
86563         if (!Ext.isObject(threshold)) {
86564             threshold = {
86565                 x: threshold,
86566                 y: threshold
86567             };
86568         }
86569
86570         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
86571                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
86572     },
86573
86574     /**
86575      * Compare this point with another point when the x and y values of both points are rounded. E.g:
86576      * [100.3,199.8] will equals to [100, 200]
86577      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
86578      * of Ext.util.Point or an object with x and y properties
86579      * @return {Boolean}
86580      */
86581     roundedEquals: function(p) {
86582         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
86583     }
86584 }, function() {
86585     /**
86586      * @method
86587      * Alias for {@link #translateBy}
86588      * @alias Ext.util.Region#translateBy
86589      */
86590     this.prototype.translate = Ext.util.Region.prototype.translateBy;
86591 });
86592
86593 /**
86594  * @class Ext.LoadMask
86595  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
86596  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
86597  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
86598  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
86599  * the Store's loading process.</p>
86600  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
86601  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
86602  * <p>Example usage:</p>
86603  * <pre><code>
86604 // Basic mask:
86605 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
86606 myMask.show();
86607 </code></pre>
86608
86609  */
86610
86611 Ext.define('Ext.LoadMask', {
86612
86613     extend: 'Ext.Component',
86614
86615     alias: 'widget.loadmask',
86616
86617     /* Begin Definitions */
86618
86619     mixins: {
86620         floating: 'Ext.util.Floating'
86621     },
86622
86623     uses: ['Ext.data.StoreManager'],
86624
86625     /* End Definitions */
86626
86627     /**
86628      * @cfg {Ext.data.Store} store
86629      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
86630      * hidden on either load success, or load fail.
86631      */
86632
86633     /**
86634      * @cfg {String} msg
86635      * The text to display in a centered loading message box.
86636      */
86637     msg : 'Loading...',
86638     /**
86639      * @cfg {String} [msgCls="x-mask-loading"]
86640      * The CSS class to apply to the loading message element.
86641      */
86642     msgCls : Ext.baseCSSPrefix + 'mask-loading',
86643     
86644     /**
86645      * @cfg {Boolean} useMsg
86646      * Whether or not to use a loading message class or simply mask the bound element.
86647      */
86648     useMsg: true,
86649
86650     /**
86651      * Read-only. True if the mask is currently disabled so that it will not be displayed
86652      * @type Boolean
86653      */
86654     disabled: false,
86655
86656     baseCls: Ext.baseCSSPrefix + 'mask-msg',
86657
86658     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
86659
86660     // Private. The whole point is that there's a mask.
86661     modal: true,
86662
86663     // Private. Obviously, it's floating.
86664     floating: {
86665         shadow: 'frame'
86666     },
86667
86668     // Private. Masks are not focusable
86669     focusOnToFront: false,
86670
86671     /**
86672      * Creates new LoadMask.
86673      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
86674      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
86675      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
86676      * and the mask only be visible when the Component is.</p>
86677      * @param {Object} [config] The config object
86678      */
86679     constructor : function(el, config) {
86680         var me = this;
86681
86682         // If a Component passed, bind to it.
86683         if (el.isComponent) {
86684             me.ownerCt = el;
86685             me.bindComponent(el);
86686         }
86687         // Create a dumy Component encapsulating the specified Element
86688         else {
86689             me.ownerCt = new Ext.Component({
86690                 el: Ext.get(el),
86691                 rendered: true,
86692                 componentLayoutCounter: 1
86693             });
86694             me.container = el;
86695         }
86696         me.callParent([config]);
86697
86698         if (me.store) {
86699             me.bindStore(me.store, true);
86700         }
86701         me.renderData = {
86702             msgCls: me.msgCls
86703         };
86704         me.renderSelectors = {
86705             msgEl: 'div'
86706         };
86707     },
86708
86709     bindComponent: function(comp) {
86710         this.mon(comp, {
86711             resize: this.onComponentResize,
86712             scope: this
86713         });
86714     },
86715
86716     afterRender: function() {
86717         this.callParent(arguments);
86718         this.container = this.floatParent.getContentTarget();
86719     },
86720
86721     /**
86722      * @private
86723      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
86724      */
86725     onComponentResize: function() {
86726         var me = this;
86727         if (me.rendered && me.isVisible()) {
86728             me.toFront();
86729             me.center();
86730         }
86731     },
86732
86733     /**
86734      * Changes the data store bound to this LoadMask.
86735      * @param {Ext.data.Store} store The store to bind to this LoadMask
86736      */
86737     bindStore : function(store, initial) {
86738         var me = this;
86739
86740         if (!initial && me.store) {
86741             me.mun(me.store, {
86742                 scope: me,
86743                 beforeload: me.onBeforeLoad,
86744                 load: me.onLoad,
86745                 exception: me.onLoad
86746             });
86747             if (!store) {
86748                 me.store = null;
86749             }
86750         }
86751         if (store) {
86752             store = Ext.data.StoreManager.lookup(store);
86753             me.mon(store, {
86754                 scope: me,
86755                 beforeload: me.onBeforeLoad,
86756                 load: me.onLoad,
86757                 exception: me.onLoad
86758             });
86759
86760         }
86761         me.store = store;
86762         if (store && store.isLoading()) {
86763             me.onBeforeLoad();
86764         }
86765     },
86766
86767     onDisable : function() {
86768         this.callParent(arguments);
86769         if (this.loading) {
86770             this.onLoad();
86771         }
86772     },
86773
86774     // private
86775     onBeforeLoad : function() {
86776         var me = this,
86777             owner = me.ownerCt || me.floatParent,
86778             origin;
86779         if (!this.disabled) {
86780             // If the owning Component has not been layed out, defer so that the ZIndexManager
86781             // gets to read its layed out size when sizing the modal mask
86782             if (owner.componentLayoutCounter) {
86783                 Ext.Component.prototype.show.call(me);
86784             } else {
86785                 // The code below is a 'run-once' interceptor.
86786                 origin = owner.afterComponentLayout;
86787                 owner.afterComponentLayout = function() {
86788                     owner.afterComponentLayout = origin;
86789                     origin.apply(owner, arguments);
86790                     if(me.loading) {
86791                         Ext.Component.prototype.show.call(me);
86792                     }
86793                 };
86794             }
86795         }
86796     },
86797
86798     onHide: function(){
86799         var me = this;
86800         me.callParent(arguments);
86801         me.showOnParentShow = true;
86802     },
86803
86804     onShow: function() {
86805         var me = this,
86806             msgEl = me.msgEl;
86807             
86808         me.callParent(arguments);
86809         me.loading = true;
86810         if (me.useMsg) {
86811             msgEl.show().update(me.msg);
86812         } else {
86813             msgEl.parent().hide();
86814         }
86815     },
86816
86817     afterShow: function() {
86818         this.callParent(arguments);
86819         this.center();
86820     },
86821
86822     // private
86823     onLoad : function() {
86824         this.loading = false;
86825         Ext.Component.prototype.hide.call(this);
86826     }
86827 });
86828 /**
86829  * @class Ext.view.AbstractView
86830  * @extends Ext.Component
86831  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
86832  * @private
86833  */
86834 Ext.define('Ext.view.AbstractView', {
86835     extend: 'Ext.Component',
86836     alternateClassName: 'Ext.view.AbstractView',
86837     requires: [
86838         'Ext.LoadMask',
86839         'Ext.data.StoreManager',
86840         'Ext.CompositeElementLite',
86841         'Ext.DomQuery',
86842         'Ext.selection.DataViewModel'
86843     ],
86844
86845     inheritableStatics: {
86846         getRecord: function(node) {
86847             return this.getBoundView(node).getRecord(node);
86848         },
86849
86850         getBoundView: function(node) {
86851             return Ext.getCmp(node.boundView);
86852         }
86853     },
86854
86855     /**
86856      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
86857      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
86858      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
86859      */
86860     /**
86861      * @cfg {Ext.data.Store} store (required)
86862      * The {@link Ext.data.Store} to bind this DataView to.
86863      */
86864
86865     /**
86866      * @cfg {Boolean} deferInitialRefresh
86867      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
86868      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
86869      * by the expensive update of the view structure.</p>
86870      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
86871      * <i>selection</i> may not take place at render time. To access a View's item elements as soon as possible, use the {@link #viewready} event.
86872      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
86873      */
86874     deferInitialRefresh: true,
86875
86876     /**
86877      * @cfg {String} itemSelector (required)
86878      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
86879      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
86880      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
86881      * only be one root level element that matches the selector for each record.
86882      */
86883
86884     /**
86885      * @cfg {String} itemCls
86886      * Specifies the class to be assigned to each element in the view when used in conjunction with the
86887      * {@link #itemTpl} configuration.
86888      */
86889     itemCls: Ext.baseCSSPrefix + 'dataview-item',
86890
86891     /**
86892      * @cfg {String/String[]/Ext.XTemplate} itemTpl
86893      * The inner portion of the item template to be rendered. Follows an XTemplate
86894      * structure and will be placed inside of a tpl.
86895      */
86896
86897     /**
86898      * @cfg {String} overItemCls
86899      * A CSS class to apply to each item in the view on mouseover.
86900      * Ensure {@link #trackOver} is set to `true` to make use of this.
86901      */
86902
86903     /**
86904      * @cfg {String} loadingText
86905      * A string to display during data load operations.  If specified, this text will be
86906      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
86907      * contents will continue to display normally until the new data is loaded and the contents are replaced.
86908      */
86909     loadingText: 'Loading...',
86910
86911     /**
86912      * @cfg {Boolean/Object} loadMask
86913      * False to disable a load mask from displaying will the view is loading. This can also be a
86914      * {@link Ext.LoadMask} configuration object.
86915      */
86916     loadMask: true,
86917
86918     /**
86919      * @cfg {String} loadingCls
86920      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
86921      */
86922
86923     /**
86924      * @cfg {Boolean} loadingUseMsg
86925      * Whether or not to use the loading message.
86926      * @private
86927      */
86928     loadingUseMsg: true,
86929
86930
86931     /**
86932      * @cfg {Number} loadingHeight
86933      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
86934      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
86935      * loading mask is applied and there are no other contents in the data view.
86936      */
86937
86938     /**
86939      * @cfg {String} [selectedItemCls='x-view-selected']
86940      * A CSS class to apply to each selected item in the view.
86941      */
86942     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
86943
86944     /**
86945      * @cfg {String} emptyText
86946      * The text to display in the view when there is no data to display.
86947      * Note that when using local data the emptyText will not be displayed unless you set
86948      * the {@link #deferEmptyText} option to false.
86949      */
86950     emptyText: "",
86951
86952     /**
86953      * @cfg {Boolean} deferEmptyText
86954      * True to defer emptyText being applied until the store's first load.
86955      */
86956     deferEmptyText: true,
86957
86958     /**
86959      * @cfg {Boolean} trackOver
86960      * True to enable mouseenter and mouseleave events
86961      */
86962     trackOver: false,
86963
86964     /**
86965      * @cfg {Boolean} blockRefresh
86966      * Set this to true to ignore datachanged events on the bound store. This is useful if
86967      * you wish to provide custom transition animations via a plugin
86968      */
86969     blockRefresh: false,
86970
86971     /**
86972      * @cfg {Boolean} disableSelection
86973      * True to disable selection within the DataView. This configuration will lock the selection model
86974      * that the DataView uses.
86975      */
86976
86977
86978     //private
86979     last: false,
86980
86981     triggerEvent: 'itemclick',
86982     triggerCtEvent: 'containerclick',
86983
86984     addCmpEvents: function() {
86985
86986     },
86987
86988     // private
86989     initComponent : function(){
86990         var me = this,
86991             isDef = Ext.isDefined,
86992             itemTpl = me.itemTpl,
86993             memberFn = {};
86994
86995         if (itemTpl) {
86996             if (Ext.isArray(itemTpl)) {
86997                 // string array
86998                 itemTpl = itemTpl.join('');
86999             } else if (Ext.isObject(itemTpl)) {
87000                 // tpl instance
87001                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
87002                 itemTpl = itemTpl.html;
87003             }
87004
87005             if (!me.itemSelector) {
87006                 me.itemSelector = '.' + me.itemCls;
87007             }
87008
87009             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
87010             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
87011         }
87012
87013
87014         me.callParent();
87015         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
87016             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
87017         }
87018
87019
87020         me.addEvents(
87021             /**
87022              * @event beforerefresh
87023              * Fires before the view is refreshed
87024              * @param {Ext.view.View} this The DataView object
87025              */
87026             'beforerefresh',
87027             /**
87028              * @event refresh
87029              * Fires when the view is refreshed
87030              * @param {Ext.view.View} this The DataView object
87031              */
87032             'refresh',
87033             /**
87034              * @event viewready
87035              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
87036              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
87037              * for selection until this event fires.
87038              * @param {Ext.view.View} this
87039              */
87040             'viewready',
87041             /**
87042              * @event itemupdate
87043              * Fires when the node associated with an individual record is updated
87044              * @param {Ext.data.Model} record The model instance
87045              * @param {Number} index The index of the record/node
87046              * @param {HTMLElement} node The node that has just been updated
87047              */
87048             'itemupdate',
87049             /**
87050              * @event itemadd
87051              * Fires when the nodes associated with an recordset have been added to the underlying store
87052              * @param {Ext.data.Model[]} records The model instance
87053              * @param {Number} index The index at which the set of record/nodes starts
87054              * @param {HTMLElement[]} node The node that has just been updated
87055              */
87056             'itemadd',
87057             /**
87058              * @event itemremove
87059              * Fires when the node associated with an individual record is removed
87060              * @param {Ext.data.Model} record The model instance
87061              * @param {Number} index The index of the record/node
87062              */
87063             'itemremove'
87064         );
87065
87066         me.addCmpEvents();
87067
87068         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
87069         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
87070         me.all = new Ext.CompositeElementLite();
87071     },
87072
87073     onRender: function() {
87074         var me = this,
87075             mask = me.loadMask,
87076             cfg = {
87077                 msg: me.loadingText,
87078                 msgCls: me.loadingCls,
87079                 useMsg: me.loadingUseMsg
87080             };
87081
87082         me.callParent(arguments);
87083
87084         if (mask) {
87085             // either a config object
87086             if (Ext.isObject(mask)) {
87087                 cfg = Ext.apply(cfg, mask);
87088             }
87089             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
87090             // If this DataView is floating, then mask this DataView.
87091             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
87092             // LoadMask captures the element upon render.
87093             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
87094             me.loadMask.on({
87095                 scope: me,
87096                 beforeshow: me.onMaskBeforeShow,
87097                 hide: me.onMaskHide
87098             });
87099         }
87100     },
87101
87102     onMaskBeforeShow: function(){
87103         var loadingHeight = this.loadingHeight;
87104         
87105         this.getSelectionModel().deselectAll();
87106         if (loadingHeight) {
87107             this.setCalculatedSize(undefined, loadingHeight);
87108         }
87109     },
87110
87111     onMaskHide: function(){
87112         var me = this;
87113         
87114         if (!me.destroying && me.loadingHeight) {
87115             me.setHeight(me.height);
87116         }
87117     },
87118
87119     afterRender: function() {
87120         this.callParent(arguments);
87121
87122         // Init the SelectionModel after any on('render') listeners have been added.
87123         // Drag plugins create a DragDrop instance in a render listener, and that needs
87124         // to see an itemmousedown event first.
87125         this.getSelectionModel().bindComponent(this);
87126     },
87127
87128     /**
87129      * Gets the selection model for this view.
87130      * @return {Ext.selection.Model} The selection model
87131      */
87132     getSelectionModel: function(){
87133         var me = this,
87134             mode = 'SINGLE';
87135
87136         if (!me.selModel) {
87137             me.selModel = {};
87138         }
87139
87140         if (me.simpleSelect) {
87141             mode = 'SIMPLE';
87142         } else if (me.multiSelect) {
87143             mode = 'MULTI';
87144         }
87145
87146         Ext.applyIf(me.selModel, {
87147             allowDeselect: me.allowDeselect,
87148             mode: mode
87149         });
87150
87151         if (!me.selModel.events) {
87152             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
87153         }
87154
87155         if (!me.selModel.hasRelaySetup) {
87156             me.relayEvents(me.selModel, [
87157                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
87158             ]);
87159             me.selModel.hasRelaySetup = true;
87160         }
87161
87162         // lock the selection model if user
87163         // has disabled selection
87164         if (me.disableSelection) {
87165             me.selModel.locked = true;
87166         }
87167
87168         return me.selModel;
87169     },
87170
87171     /**
87172      * Refreshes the view by reloading the data from the store and re-rendering the template.
87173      */
87174     refresh: function() {
87175         var me = this,
87176             el,
87177             records;
87178
87179         if (!me.rendered || me.isDestroyed) {
87180             return;
87181         }
87182
87183         me.fireEvent('beforerefresh', me);
87184         el = me.getTargetEl();
87185         records = me.store.getRange();
87186
87187         el.update('');
87188         if (records.length < 1) {
87189             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
87190                 el.update(me.emptyText);
87191             }
87192             me.all.clear();
87193         } else {
87194             me.tpl.overwrite(el, me.collectData(records, 0));
87195             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
87196             me.updateIndexes(0);
87197         }
87198
87199         me.selModel.refresh();
87200         me.hasSkippedEmptyText = true;
87201         me.fireEvent('refresh', me);
87202
87203         // Upon first refresh, fire the viewready event.
87204         // Reconfiguring the grid "renews" this event.
87205         if (!me.viewReady) {
87206             // Fire an event when deferred content becomes available.
87207             // This supports grid Panel's deferRowRender capability
87208             me.viewReady = true;
87209             me.fireEvent('viewready', me);
87210         }
87211     },
87212
87213     /**
87214      * Function which can be overridden to provide custom formatting for each Record that is used by this
87215      * DataView's {@link #tpl template} to render each node.
87216      * @param {Object/Object[]} data The raw data object that was used to create the Record.
87217      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
87218      * @param {Ext.data.Model} record The Record being prepared for rendering.
87219      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
87220      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
87221      */
87222     prepareData: function(data, index, record) {
87223         if (record) {
87224             Ext.apply(data, record.getAssociatedData());
87225         }
87226         return data;
87227     },
87228
87229     /**
87230      * <p>Function which can be overridden which returns the data object passed to this
87231      * DataView's {@link #tpl template} to render the whole DataView.</p>
87232      * <p>This is usually an Array of data objects, each element of which is processed by an
87233      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
87234      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
87235      * provide non-repeating data such as headings, totals etc.</p>
87236      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
87237      * @param {Number} startIndex the index number of the Record being prepared for rendering.
87238      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
87239      * contain <i>named</i> properties.
87240      */
87241     collectData : function(records, startIndex){
87242         var r = [],
87243             i = 0,
87244             len = records.length,
87245             record;
87246
87247         for(; i < len; i++){
87248             record = records[i];
87249             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
87250         }
87251         return r;
87252     },
87253
87254     // private
87255     bufferRender : function(records, index){
87256         var div = document.createElement('div');
87257         this.tpl.overwrite(div, this.collectData(records, index));
87258         return Ext.query(this.getItemSelector(), div);
87259     },
87260
87261     // private
87262     onUpdate : function(ds, record){
87263         var me = this,
87264             index = me.store.indexOf(record),
87265             node;
87266
87267         if (index > -1){
87268             node = me.bufferRender([record], index)[0];
87269             // ensure the node actually exists in the DOM
87270             if (me.getNode(record)) {
87271                 me.all.replaceElement(index, node, true);
87272                 me.updateIndexes(index, index);
87273                 // Maintain selection after update
87274                 // TODO: Move to approriate event handler.
87275                 me.selModel.refresh();
87276                 me.fireEvent('itemupdate', record, index, node);
87277             }
87278         }
87279
87280     },
87281
87282     // private
87283     onAdd : function(ds, records, index) {
87284         var me = this,
87285             nodes;
87286
87287         if (me.all.getCount() === 0) {
87288             me.refresh();
87289             return;
87290         }
87291
87292         nodes = me.bufferRender(records, index);
87293         me.doAdd(nodes, records, index);
87294
87295         me.selModel.refresh();
87296         me.updateIndexes(index);
87297         me.fireEvent('itemadd', records, index, nodes);
87298     },
87299
87300     doAdd: function(nodes, records, index) {
87301         var all = this.all;
87302
87303         if (index < all.getCount()) {
87304             all.item(index).insertSibling(nodes, 'before', true);
87305         } else {
87306             all.last().insertSibling(nodes, 'after', true);
87307         }
87308
87309         Ext.Array.insert(all.elements, index, nodes);
87310     },
87311
87312     // private
87313     onRemove : function(ds, record, index) {
87314         var me = this;
87315
87316         me.doRemove(record, index);
87317         me.updateIndexes(index);
87318         if (me.store.getCount() === 0){
87319             me.refresh();
87320         }
87321         me.fireEvent('itemremove', record, index);
87322     },
87323
87324     doRemove: function(record, index) {
87325         this.all.removeElement(index, true);
87326     },
87327
87328     /**
87329      * Refreshes an individual node's data from the store.
87330      * @param {Number} index The item's data index in the store
87331      */
87332     refreshNode : function(index){
87333         this.onUpdate(this.store, this.store.getAt(index));
87334     },
87335
87336     // private
87337     updateIndexes : function(startIndex, endIndex) {
87338         var ns = this.all.elements,
87339             records = this.store.getRange(),
87340             i;
87341             
87342         startIndex = startIndex || 0;
87343         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
87344         for(i = startIndex; i <= endIndex; i++){
87345             ns[i].viewIndex = i;
87346             ns[i].viewRecordId = records[i].internalId;
87347             if (!ns[i].boundView) {
87348                 ns[i].boundView = this.id;
87349             }
87350         }
87351     },
87352
87353     /**
87354      * Returns the store associated with this DataView.
87355      * @return {Ext.data.Store} The store
87356      */
87357     getStore : function(){
87358         return this.store;
87359     },
87360
87361     /**
87362      * Changes the data store bound to this view and refreshes it.
87363      * @param {Ext.data.Store} store The store to bind to this view
87364      */
87365     bindStore : function(store, initial) {
87366         var me = this,
87367             maskStore;
87368
87369         if (!initial && me.store) {
87370             if (store !== me.store && me.store.autoDestroy) {
87371                 me.store.destroyStore();
87372             }
87373             else {
87374                 me.mun(me.store, {
87375                     scope: me,
87376                     datachanged: me.onDataChanged,
87377                     add: me.onAdd,
87378                     remove: me.onRemove,
87379                     update: me.onUpdate,
87380                     clear: me.refresh
87381                 });
87382             }
87383             if (!store) {
87384                 // Ensure we have an instantiated LoadMask before we unbind it.
87385                 if (me.loadMask && me.loadMask.bindStore) {
87386                     me.loadMask.bindStore(null);
87387                 }
87388                 me.store = null;
87389             }
87390         }
87391         if (store) {
87392             store = Ext.data.StoreManager.lookup(store);
87393             me.mon(store, {
87394                 scope: me,
87395                 datachanged: me.onDataChanged,
87396                 add: me.onAdd,
87397                 remove: me.onRemove,
87398                 update: me.onUpdate,
87399                 clear: me.refresh
87400             });
87401             // Ensure we have an instantiated LoadMask before we bind it.
87402             if (me.loadMask && me.loadMask.bindStore) {
87403                 // View's store is a NodeStore, use owning TreePanel's Store
87404                 if (Ext.Array.contains(store.alias, 'store.node')) {
87405                     maskStore = this.ownerCt.store;
87406                 } else {
87407                     maskStore = store;
87408                 }
87409                 me.loadMask.bindStore(maskStore);
87410             }
87411         }
87412
87413         // Flag to say that initial refresh has not been performed.
87414         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
87415         me.viewReady = false;
87416
87417         me.store = store;
87418         // Bind the store to our selection model
87419         me.getSelectionModel().bind(store);
87420
87421         /*
87422          * This code used to have checks for:
87423          * if (store && (!initial || store.getCount() || me.emptyText)) {
87424          * Instead, just trigger a refresh and let the view itself figure out
87425          * what needs to happen. It can cause incorrect display if our store
87426          * has no data.
87427          */
87428         if (store) {
87429             if (initial && me.deferInitialRefresh) {
87430                 Ext.Function.defer(function () {
87431                     if (!me.isDestroyed) {
87432                         me.refresh(true);
87433                     }
87434                 }, 1);
87435             } else {
87436                 me.refresh(true);
87437             }
87438         }
87439     },
87440
87441     /**
87442      * @private
87443      * Calls this.refresh if this.blockRefresh is not true
87444      */
87445     onDataChanged: function() {
87446         if (this.blockRefresh !== true) {
87447             this.refresh.apply(this, arguments);
87448         }
87449     },
87450
87451     /**
87452      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
87453      * @param {HTMLElement} node
87454      * @return {HTMLElement} The template node
87455      */
87456     findItemByChild: function(node){
87457         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
87458     },
87459
87460     /**
87461      * Returns the template node by the Ext.EventObject or null if it is not found.
87462      * @param {Ext.EventObject} e
87463      */
87464     findTargetByEvent: function(e) {
87465         return e.getTarget(this.getItemSelector(), this.getTargetEl());
87466     },
87467
87468
87469     /**
87470      * Gets the currently selected nodes.
87471      * @return {HTMLElement[]} An array of HTMLElements
87472      */
87473     getSelectedNodes: function(){
87474         var nodes   = [],
87475             records = this.selModel.getSelection(),
87476             ln = records.length,
87477             i  = 0;
87478
87479         for (; i < ln; i++) {
87480             nodes.push(this.getNode(records[i]));
87481         }
87482
87483         return nodes;
87484     },
87485
87486     /**
87487      * Gets an array of the records from an array of nodes
87488      * @param {HTMLElement[]} nodes The nodes to evaluate
87489      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
87490      */
87491     getRecords: function(nodes) {
87492         var records = [],
87493             i = 0,
87494             len = nodes.length,
87495             data = this.store.data;
87496
87497         for (; i < len; i++) {
87498             records[records.length] = data.getByKey(nodes[i].viewRecordId);
87499         }
87500
87501         return records;
87502     },
87503
87504     /**
87505      * Gets a record from a node
87506      * @param {Ext.Element/HTMLElement} node The node to evaluate
87507      *
87508      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
87509      */
87510     getRecord: function(node){
87511         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
87512     },
87513
87514
87515     /**
87516      * Returns true if the passed node is selected, else false.
87517      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
87518      * @return {Boolean} True if selected, else false
87519      */
87520     isSelected : function(node) {
87521         // TODO: El/Idx/Record
87522         var r = this.getRecord(node);
87523         return this.selModel.isSelected(r);
87524     },
87525
87526     /**
87527      * Selects a record instance by record instance or index.
87528      * @param {Ext.data.Model[]/Number} records An array of records or an index
87529      * @param {Boolean} [keepExisting] True to keep existing selections
87530      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
87531      */
87532     select: function(records, keepExisting, suppressEvent) {
87533         this.selModel.select(records, keepExisting, suppressEvent);
87534     },
87535
87536     /**
87537      * Deselects a record instance by record instance or index.
87538      * @param {Ext.data.Model[]/Number} records An array of records or an index
87539      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
87540      */
87541     deselect: function(records, suppressEvent) {
87542         this.selModel.deselect(records, suppressEvent);
87543     },
87544
87545     /**
87546      * Gets a template node.
87547      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
87548      * the id of a template node or the record associated with the node.
87549      * @return {HTMLElement} The node or null if it wasn't found
87550      */
87551     getNode : function(nodeInfo) {
87552         if (!this.rendered) {
87553             return null;
87554         }
87555         if (Ext.isString(nodeInfo)) {
87556             return document.getElementById(nodeInfo);
87557         }
87558         if (Ext.isNumber(nodeInfo)) {
87559             return this.all.elements[nodeInfo];
87560         }
87561         if (nodeInfo instanceof Ext.data.Model) {
87562             return this.getNodeByRecord(nodeInfo);
87563         }
87564         return nodeInfo; // already an HTMLElement
87565     },
87566
87567     /**
87568      * @private
87569      */
87570     getNodeByRecord: function(record) {
87571         var ns = this.all.elements,
87572             ln = ns.length,
87573             i = 0;
87574
87575         for (; i < ln; i++) {
87576             if (ns[i].viewRecordId === record.internalId) {
87577                 return ns[i];
87578             }
87579         }
87580
87581         return null;
87582     },
87583
87584     /**
87585      * Gets a range nodes.
87586      * @param {Number} start (optional) The index of the first node in the range
87587      * @param {Number} end (optional) The index of the last node in the range
87588      * @return {HTMLElement[]} An array of nodes
87589      */
87590     getNodes: function(start, end) {
87591         var ns = this.all.elements,
87592             nodes = [],
87593             i;
87594
87595         start = start || 0;
87596         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
87597         if (start <= end) {
87598             for (i = start; i <= end && ns[i]; i++) {
87599                 nodes.push(ns[i]);
87600             }
87601         } else {
87602             for (i = start; i >= end && ns[i]; i--) {
87603                 nodes.push(ns[i]);
87604             }
87605         }
87606         return nodes;
87607     },
87608
87609     /**
87610      * Finds the index of the passed node.
87611      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
87612      * or a record associated with a node.
87613      * @return {Number} The index of the node or -1
87614      */
87615     indexOf: function(node) {
87616         node = this.getNode(node);
87617         if (Ext.isNumber(node.viewIndex)) {
87618             return node.viewIndex;
87619         }
87620         return this.all.indexOf(node);
87621     },
87622
87623     onDestroy : function() {
87624         var me = this;
87625
87626         me.all.clear();
87627         me.callParent();
87628         me.bindStore(null);
87629         me.selModel.destroy();
87630     },
87631
87632     // invoked by the selection model to maintain visual UI cues
87633     onItemSelect: function(record) {
87634         var node = this.getNode(record);
87635         
87636         if (node) {
87637             Ext.fly(node).addCls(this.selectedItemCls);
87638         }
87639     },
87640
87641     // invoked by the selection model to maintain visual UI cues
87642     onItemDeselect: function(record) {
87643         var node = this.getNode(record);
87644         
87645         if (node) {
87646             Ext.fly(node).removeCls(this.selectedItemCls);
87647         }
87648     },
87649
87650     getItemSelector: function() {
87651         return this.itemSelector;
87652     }
87653 }, function() {
87654     // all of this information is available directly
87655     // from the SelectionModel itself, the only added methods
87656     // to DataView regarding selection will perform some transformation/lookup
87657     // between HTMLElement/Nodes to records and vice versa.
87658     Ext.deprecate('extjs', '4.0', function() {
87659         Ext.view.AbstractView.override({
87660             /**
87661              * @cfg {Boolean} [multiSelect=false]
87662              * True to allow selection of more than one item at a time, false to allow selection of only a single item
87663              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
87664              */
87665             /**
87666              * @cfg {Boolean} [singleSelect=false]
87667              * True to allow selection of exactly one item at a time, false to allow no selection at all.
87668              * Note that if {@link #multiSelect} = true, this value will be ignored.
87669              */
87670             /**
87671              * @cfg {Boolean} [simpleSelect=false]
87672              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
87673              * false to force the user to hold Ctrl or Shift to select more than on item.
87674              */
87675
87676             /**
87677              * Gets the number of selected nodes.
87678              * @return {Number} The node count
87679              */
87680             getSelectionCount : function(){
87681                 if (Ext.global.console) {
87682                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
87683                 }
87684                 return this.selModel.getSelection().length;
87685             },
87686
87687             /**
87688              * Gets an array of the selected records
87689              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
87690              */
87691             getSelectedRecords : function(){
87692                 if (Ext.global.console) {
87693                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
87694                 }
87695                 return this.selModel.getSelection();
87696             },
87697
87698             select: function(records, keepExisting, supressEvents) {
87699                 if (Ext.global.console) {
87700                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
87701                 }
87702                 var sm = this.getSelectionModel();
87703                 return sm.select.apply(sm, arguments);
87704             },
87705
87706             clearSelections: function() {
87707                 if (Ext.global.console) {
87708                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
87709                 }
87710                 var sm = this.getSelectionModel();
87711                 return sm.deselectAll();
87712             }
87713         });
87714     });
87715 });
87716
87717 /**
87718  * @class Ext.Action
87719  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
87720  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
87721  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
87722  * and {@link Ext.menu.Menu} components).</p>
87723  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
87724  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
87725  * called at once through a single call to the Action.</p>
87726  * <p>Any Component that is to be configured with an Action must also support
87727  * the following methods:<ul>
87728  * <li><code>setText(string)</code></li>
87729  * <li><code>setIconCls(string)</code></li>
87730  * <li><code>setDisabled(boolean)</code></li>
87731  * <li><code>setVisible(boolean)</code></li>
87732  * <li><code>setHandler(function)</code></li></ul></p>
87733  * <p>This allows the Action to control its associated Components.</p>
87734  * Example usage:<br>
87735  * <pre><code>
87736 // Define the shared Action.  Each Component below will have the same
87737 // display text and icon, and will display the same message on click.
87738 var action = new Ext.Action({
87739     {@link #text}: 'Do something',
87740     {@link #handler}: function(){
87741         Ext.Msg.alert('Click', 'You did something.');
87742     },
87743     {@link #iconCls}: 'do-something',
87744     {@link #itemId}: 'myAction'
87745 });
87746
87747 var panel = new Ext.panel.Panel({
87748     title: 'Actions',
87749     width: 500,
87750     height: 300,
87751     tbar: [
87752         // Add the Action directly to a toolbar as a menu button
87753         action,
87754         {
87755             text: 'Action Menu',
87756             // Add the Action to a menu as a text item
87757             menu: [action]
87758         }
87759     ],
87760     items: [
87761         // Add the Action to the panel body as a standard button
87762         new Ext.button.Button(action)
87763     ],
87764     renderTo: Ext.getBody()
87765 });
87766
87767 // Change the text for all components using the Action
87768 action.setText('Something else');
87769
87770 // Reference an Action through a container using the itemId
87771 var btn = panel.getComponent('myAction');
87772 var aRef = btn.baseAction;
87773 aRef.setText('New text');
87774 </code></pre>
87775  */
87776 Ext.define('Ext.Action', {
87777
87778     /* Begin Definitions */
87779
87780     /* End Definitions */
87781
87782     /**
87783      * @cfg {String} [text='']
87784      * The text to set for all components configured by this Action.
87785      */
87786     /**
87787      * @cfg {String} [iconCls='']
87788      * The CSS class selector that specifies a background image to be used as the header icon for
87789      * all components configured by this Action.
87790      * <p>An example of specifying a custom icon class would be something like:
87791      * </p><pre><code>
87792 // specify the property in the config for the class:
87793      ...
87794      iconCls: 'do-something'
87795
87796 // css class that specifies background image to be used as the icon image:
87797 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
87798 </code></pre>
87799      */
87800     /**
87801      * @cfg {Boolean} [disabled=false]
87802      * True to disable all components configured by this Action, false to enable them.
87803      */
87804     /**
87805      * @cfg {Boolean} [hidden=false]
87806      * True to hide all components configured by this Action, false to show them.
87807      */
87808     /**
87809      * @cfg {Function} handler
87810      * The function that will be invoked by each component tied to this Action
87811      * when the component's primary event is triggered.
87812      */
87813     /**
87814      * @cfg {String} itemId
87815      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
87816      */
87817     /**
87818      * @cfg {Object} scope
87819      * The scope (this reference) in which the {@link #handler} is executed.
87820      * Defaults to the browser window.
87821      */
87822
87823     /**
87824      * Creates new Action.
87825      * @param {Object} config Config object.
87826      */
87827     constructor : function(config){
87828         this.initialConfig = config;
87829         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
87830         this.items = [];
87831     },
87832
87833     // private
87834     isAction : true,
87835
87836     /**
87837      * Sets the text to be displayed by all components configured by this Action.
87838      * @param {String} text The text to display
87839      */
87840     setText : function(text){
87841         this.initialConfig.text = text;
87842         this.callEach('setText', [text]);
87843     },
87844
87845     /**
87846      * Gets the text currently displayed by all components configured by this Action.
87847      */
87848     getText : function(){
87849         return this.initialConfig.text;
87850     },
87851
87852     /**
87853      * Sets the icon CSS class for all components configured by this Action.  The class should supply
87854      * a background image that will be used as the icon image.
87855      * @param {String} cls The CSS class supplying the icon image
87856      */
87857     setIconCls : function(cls){
87858         this.initialConfig.iconCls = cls;
87859         this.callEach('setIconCls', [cls]);
87860     },
87861
87862     /**
87863      * Gets the icon CSS class currently used by all components configured by this Action.
87864      */
87865     getIconCls : function(){
87866         return this.initialConfig.iconCls;
87867     },
87868
87869     /**
87870      * Sets the disabled state of all components configured by this Action.  Shortcut method
87871      * for {@link #enable} and {@link #disable}.
87872      * @param {Boolean} disabled True to disable the component, false to enable it
87873      */
87874     setDisabled : function(v){
87875         this.initialConfig.disabled = v;
87876         this.callEach('setDisabled', [v]);
87877     },
87878
87879     /**
87880      * Enables all components configured by this Action.
87881      */
87882     enable : function(){
87883         this.setDisabled(false);
87884     },
87885
87886     /**
87887      * Disables all components configured by this Action.
87888      */
87889     disable : function(){
87890         this.setDisabled(true);
87891     },
87892
87893     /**
87894      * Returns true if the components using this Action are currently disabled, else returns false.
87895      */
87896     isDisabled : function(){
87897         return this.initialConfig.disabled;
87898     },
87899
87900     /**
87901      * Sets the hidden state of all components configured by this Action.  Shortcut method
87902      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
87903      * @param {Boolean} hidden True to hide the component, false to show it
87904      */
87905     setHidden : function(v){
87906         this.initialConfig.hidden = v;
87907         this.callEach('setVisible', [!v]);
87908     },
87909
87910     /**
87911      * Shows all components configured by this Action.
87912      */
87913     show : function(){
87914         this.setHidden(false);
87915     },
87916
87917     /**
87918      * Hides all components configured by this Action.
87919      */
87920     hide : function(){
87921         this.setHidden(true);
87922     },
87923
87924     /**
87925      * Returns true if the components configured by this Action are currently hidden, else returns false.
87926      */
87927     isHidden : function(){
87928         return this.initialConfig.hidden;
87929     },
87930
87931     /**
87932      * Sets the function that will be called by each Component using this action when its primary event is triggered.
87933      * @param {Function} fn The function that will be invoked by the action's components.  The function
87934      * will be called with no arguments.
87935      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
87936      */
87937     setHandler : function(fn, scope){
87938         this.initialConfig.handler = fn;
87939         this.initialConfig.scope = scope;
87940         this.callEach('setHandler', [fn, scope]);
87941     },
87942
87943     /**
87944      * Executes the specified function once for each Component currently tied to this Action.  The function passed
87945      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
87946      * @param {Function} fn The function to execute for each component
87947      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
87948      */
87949     each : function(fn, scope){
87950         Ext.each(this.items, fn, scope);
87951     },
87952
87953     // private
87954     callEach : function(fnName, args){
87955         var items = this.items,
87956             i = 0,
87957             len = items.length;
87958
87959         for(; i < len; i++){
87960             items[i][fnName].apply(items[i], args);
87961         }
87962     },
87963
87964     // private
87965     addComponent : function(comp){
87966         this.items.push(comp);
87967         comp.on('destroy', this.removeComponent, this);
87968     },
87969
87970     // private
87971     removeComponent : function(comp){
87972         Ext.Array.remove(this.items, comp);
87973     },
87974
87975     /**
87976      * Executes this Action manually using the handler function specified in the original config object
87977      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
87978      * function will be passed on to the handler function.
87979      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
87980      */
87981     execute : function(){
87982         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
87983     }
87984 });
87985
87986 /**
87987  * Component layout for editors
87988  * @class Ext.layout.component.Editor
87989  * @extends Ext.layout.component.Component
87990  * @private
87991  */
87992 Ext.define('Ext.layout.component.Editor', {
87993
87994     /* Begin Definitions */
87995
87996     alias: ['layout.editor'],
87997
87998     extend: 'Ext.layout.component.Component',
87999
88000     /* End Definitions */
88001
88002     onLayout: function(width, height) {
88003         var me = this,
88004             owner = me.owner,
88005             autoSize = owner.autoSize;
88006             
88007         if (autoSize === true) {
88008             autoSize = {
88009                 width: 'field',
88010                 height: 'field'    
88011             };
88012         }
88013         
88014         if (autoSize) {
88015             width = me.getDimension(owner, autoSize.width, 'Width', width);
88016             height = me.getDimension(owner, autoSize.height, 'Height', height);
88017         }
88018         me.setTargetSize(width, height);
88019         owner.field.setSize(width, height);
88020     },
88021     
88022     getDimension: function(owner, type, dimension, actual){
88023         var method = 'get' + dimension;
88024         switch (type) {
88025             case 'boundEl':
88026                 return owner.boundEl[method]();
88027             case 'field':
88028                 return owner.field[method]();
88029             default:
88030                 return actual;
88031         }
88032     }
88033 });
88034 /**
88035  * @class Ext.Editor
88036  * @extends Ext.Component
88037  *
88038  * <p>
88039  * The Editor class is used to provide inline editing for elements on the page. The editor
88040  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
88041  * The editor is a floating Component, when the editor is shown it is automatically aligned to
88042  * display over the top of the bound element it is editing. The Editor contains several options
88043  * for how to handle key presses:
88044  * <ul>
88045  * <li>{@link #completeOnEnter}</li>
88046  * <li>{@link #cancelOnEsc}</li>
88047  * <li>{@link #swallowKeys}</li>
88048  * </ul>
88049  * It also has options for how to use the value once the editor has been activated:
88050  * <ul>
88051  * <li>{@link #revertInvalid}</li>
88052  * <li>{@link #ignoreNoChange}</li>
88053  * <li>{@link #updateEl}</li>
88054  * </ul>
88055  * Sample usage:
88056  * </p>
88057  * <pre><code>
88058 var editor = new Ext.Editor({
88059     updateEl: true, // update the innerHTML of the bound element when editing completes
88060     field: {
88061         xtype: 'textfield'
88062     }
88063 });
88064 var el = Ext.get('my-text'); // The element to 'edit'
88065 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
88066  * </code></pre>
88067  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
88068  *
88069  */
88070 Ext.define('Ext.Editor', {
88071
88072     /* Begin Definitions */
88073
88074     extend: 'Ext.Component',
88075
88076     alias: 'widget.editor',
88077
88078     requires: ['Ext.layout.component.Editor'],
88079
88080     /* End Definitions */
88081
88082    componentLayout: 'editor',
88083
88084     /**
88085     * @cfg {Ext.form.field.Field} field
88086     * The Field object (or descendant) or config object for field
88087     */
88088
88089     /**
88090      * @cfg {Boolean} allowBlur
88091      * True to {@link #completeEdit complete the editing process} if in edit mode when the
88092      * field is blurred.
88093      */
88094     allowBlur: true,
88095
88096     /**
88097      * @cfg {Boolean/Object} autoSize
88098      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
88099      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
88100      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
88101      * the editor object.
88102      * Examples:
88103      * <pre><code>
88104 autoSize: true // The editor will be sized to the height/width of the field
88105
88106 height: 21,
88107 autoSize: {
88108     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
88109 }
88110
88111 autoSize: {
88112     width: 'field', // Width from the field
88113     height: 'boundEl' // Height from the boundEl
88114 }
88115      * </pre></code>
88116      */
88117
88118     /**
88119      * @cfg {Boolean} revertInvalid
88120      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
88121      * validation fails
88122      */
88123     revertInvalid: true,
88124
88125     /**
88126      * @cfg {Boolean} [ignoreNoChange=false]
88127      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
88128      * the value has not changed.  Applies only to string values - edits for other data types
88129      * will never be ignored.
88130      */
88131
88132     /**
88133      * @cfg {Boolean} [hideEl=true]
88134      * False to keep the bound element visible while the editor is displayed
88135      */
88136
88137     /**
88138      * @cfg {Object} value
88139      * The data value of the underlying field
88140      */
88141     value : '',
88142
88143     /**
88144      * @cfg {String} alignment
88145      * The position to align to (see {@link Ext.Element#alignTo} for more details).
88146      */
88147     alignment: 'c-c?',
88148
88149     /**
88150      * @cfg {Number[]} offsets
88151      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
88152      */
88153     offsets: [0, 0],
88154
88155     /**
88156      * @cfg {Boolean/String} shadow
88157      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
88158      */
88159     shadow : 'frame',
88160
88161     /**
88162      * @cfg {Boolean} constrain
88163      * True to constrain the editor to the viewport
88164      */
88165     constrain : false,
88166
88167     /**
88168      * @cfg {Boolean} swallowKeys
88169      * Handle the keydown/keypress events so they don't propagate
88170      */
88171     swallowKeys : true,
88172
88173     /**
88174      * @cfg {Boolean} completeOnEnter
88175      * True to complete the edit when the enter key is pressed.
88176      */
88177     completeOnEnter : true,
88178
88179     /**
88180      * @cfg {Boolean} cancelOnEsc
88181      * True to cancel the edit when the escape key is pressed.
88182      */
88183     cancelOnEsc : true,
88184
88185     /**
88186      * @cfg {Boolean} updateEl
88187      * True to update the innerHTML of the bound element when the update completes
88188      */
88189     updateEl : false,
88190
88191     /**
88192      * @cfg {String/HTMLElement/Ext.Element} parentEl
88193      * An element to render to. Defaults to the <tt>document.body</tt>.
88194      */
88195
88196     // private overrides
88197     hidden: true,
88198     baseCls: Ext.baseCSSPrefix + 'editor',
88199
88200     initComponent : function() {
88201         var me = this,
88202             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
88203
88204         Ext.apply(field, {
88205             inEditor: true,
88206             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
88207         });
88208         me.mon(field, {
88209             scope: me,
88210             blur: {
88211                 fn: me.onBlur,
88212                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
88213                 delay: 1
88214             },
88215             specialkey: me.onSpecialKey
88216         });
88217
88218         if (field.grow) {
88219             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
88220         }
88221         me.floating = {
88222             constrain: me.constrain
88223         };
88224
88225         me.callParent(arguments);
88226
88227         me.addEvents(
88228             /**
88229              * @event beforestartedit
88230              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
88231              * false from the handler of this event.
88232              * @param {Ext.Editor} this
88233              * @param {Ext.Element} boundEl The underlying element bound to this editor
88234              * @param {Object} value The field value being set
88235              */
88236             'beforestartedit',
88237
88238             /**
88239              * @event startedit
88240              * Fires when this editor is displayed
88241              * @param {Ext.Editor} this
88242              * @param {Ext.Element} boundEl The underlying element bound to this editor
88243              * @param {Object} value The starting field value
88244              */
88245             'startedit',
88246
88247             /**
88248              * @event beforecomplete
88249              * Fires after a change has been made to the field, but before the change is reflected in the underlying
88250              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
88251              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
88252              * event will not fire since no edit actually occurred.
88253              * @param {Ext.Editor} this
88254              * @param {Object} value The current field value
88255              * @param {Object} startValue The original field value
88256              */
88257             'beforecomplete',
88258             /**
88259              * @event complete
88260              * Fires after editing is complete and any changed value has been written to the underlying field.
88261              * @param {Ext.Editor} this
88262              * @param {Object} value The current field value
88263              * @param {Object} startValue The original field value
88264              */
88265             'complete',
88266             /**
88267              * @event canceledit
88268              * Fires after editing has been canceled and the editor's value has been reset.
88269              * @param {Ext.Editor} this
88270              * @param {Object} value The user-entered field value that was discarded
88271              * @param {Object} startValue The original field value that was set back into the editor after cancel
88272              */
88273             'canceledit',
88274             /**
88275              * @event specialkey
88276              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
88277              * {@link Ext.EventObject#getKey} to determine which key was pressed.
88278              * @param {Ext.Editor} this
88279              * @param {Ext.form.field.Field} The field attached to this editor
88280              * @param {Ext.EventObject} event The event object
88281              */
88282             'specialkey'
88283         );
88284     },
88285
88286     // private
88287     onAutoSize: function(){
88288         this.doComponentLayout();
88289     },
88290
88291     // private
88292     onRender : function(ct, position) {
88293         var me = this,
88294             field = me.field,
88295             inputEl = field.inputEl;
88296
88297         me.callParent(arguments);
88298
88299         field.render(me.el);
88300         //field.hide();
88301         // Ensure the field doesn't get submitted as part of any form
88302         if (inputEl) {
88303             inputEl.dom.name = '';
88304             if (me.swallowKeys) {
88305                 inputEl.swallowEvent([
88306                     'keypress', // *** Opera
88307                     'keydown'   // *** all other browsers
88308                 ]);
88309             }
88310         }
88311     },
88312
88313     // private
88314     onSpecialKey : function(field, event) {
88315         var me = this,
88316             key = event.getKey(),
88317             complete = me.completeOnEnter && key == event.ENTER,
88318             cancel = me.cancelOnEsc && key == event.ESC;
88319
88320         if (complete || cancel) {
88321             event.stopEvent();
88322             // Must defer this slightly to prevent exiting edit mode before the field's own
88323             // key nav can handle the enter key, e.g. selecting an item in a combobox list
88324             Ext.defer(function() {
88325                 if (complete) {
88326                     me.completeEdit();
88327                 } else {
88328                     me.cancelEdit();
88329                 }
88330                 if (field.triggerBlur) {
88331                     field.triggerBlur();
88332                 }
88333             }, 10);
88334         }
88335
88336         this.fireEvent('specialkey', this, field, event);
88337     },
88338
88339     /**
88340      * Starts the editing process and shows the editor.
88341      * @param {String/HTMLElement/Ext.Element} el The element to edit
88342      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
88343       * to the innerHTML of el.
88344      */
88345     startEdit : function(el, value) {
88346         var me = this,
88347             field = me.field;
88348
88349         me.completeEdit();
88350         me.boundEl = Ext.get(el);
88351         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
88352
88353         if (!me.rendered) {
88354             me.render(me.parentEl || document.body);
88355         }
88356
88357         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
88358             me.startValue = value;
88359             me.show();
88360             field.reset();
88361             field.setValue(value);
88362             me.realign(true);
88363             field.focus(false, 10);
88364             if (field.autoSize) {
88365                 field.autoSize();
88366             }
88367             me.editing = true;
88368         }
88369     },
88370
88371     /**
88372      * Realigns the editor to the bound field based on the current alignment config value.
88373      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
88374      */
88375     realign : function(autoSize) {
88376         var me = this;
88377         if (autoSize === true) {
88378             me.doComponentLayout();
88379         }
88380         me.alignTo(me.boundEl, me.alignment, me.offsets);
88381     },
88382
88383     /**
88384      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
88385      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
88386      */
88387     completeEdit : function(remainVisible) {
88388         var me = this,
88389             field = me.field,
88390             value;
88391
88392         if (!me.editing) {
88393             return;
88394         }
88395
88396         // Assert combo values first
88397         if (field.assertValue) {
88398             field.assertValue();
88399         }
88400
88401         value = me.getValue();
88402         if (!field.isValid()) {
88403             if (me.revertInvalid !== false) {
88404                 me.cancelEdit(remainVisible);
88405             }
88406             return;
88407         }
88408
88409         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
88410             me.hideEdit(remainVisible);
88411             return;
88412         }
88413
88414         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
88415             // Grab the value again, may have changed in beforecomplete
88416             value = me.getValue();
88417             if (me.updateEl && me.boundEl) {
88418                 me.boundEl.update(value);
88419             }
88420             me.hideEdit(remainVisible);
88421             me.fireEvent('complete', me, value, me.startValue);
88422         }
88423     },
88424
88425     // private
88426     onShow : function() {
88427         var me = this;
88428
88429         me.callParent(arguments);
88430         if (me.hideEl !== false) {
88431             me.boundEl.hide();
88432         }
88433         me.fireEvent("startedit", me.boundEl, me.startValue);
88434     },
88435
88436     /**
88437      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
88438      * reverted to the original starting value.
88439      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
88440      */
88441     cancelEdit : function(remainVisible) {
88442         var me = this,
88443             startValue = me.startValue,
88444             value;
88445
88446         if (me.editing) {
88447             value = me.getValue();
88448             me.setValue(startValue);
88449             me.hideEdit(remainVisible);
88450             me.fireEvent('canceledit', me, value, startValue);
88451         }
88452     },
88453
88454     // private
88455     hideEdit: function(remainVisible) {
88456         if (remainVisible !== true) {
88457             this.editing = false;
88458             this.hide();
88459         }
88460     },
88461
88462     // private
88463     onBlur : function() {
88464         var me = this;
88465
88466         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
88467         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
88468             me.completeEdit();
88469         }
88470     },
88471
88472     // private
88473     onHide : function() {
88474         var me = this,
88475             field = me.field;
88476
88477         if (me.editing) {
88478             me.completeEdit();
88479             return;
88480         }
88481         field.blur();
88482         if (field.collapse) {
88483             field.collapse();
88484         }
88485
88486         //field.hide();
88487         if (me.hideEl !== false) {
88488             me.boundEl.show();
88489         }
88490         me.callParent(arguments);
88491     },
88492
88493     /**
88494      * Sets the data value of the editor
88495      * @param {Object} value Any valid value supported by the underlying field
88496      */
88497     setValue : function(value) {
88498         this.field.setValue(value);
88499     },
88500
88501     /**
88502      * Gets the data value of the editor
88503      * @return {Object} The data value
88504      */
88505     getValue : function() {
88506         return this.field.getValue();
88507     },
88508
88509     beforeDestroy : function() {
88510         var me = this;
88511
88512         Ext.destroy(me.field);
88513         delete me.field;
88514         delete me.parentEl;
88515         delete me.boundEl;
88516
88517         me.callParent(arguments);
88518     }
88519 });
88520 /**
88521  * @class Ext.Img
88522  * @extends Ext.Component
88523  *
88524  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
88525  * with the configured src.
88526  *
88527  * {@img Ext.Img/Ext.Img.png Ext.Img component}
88528  *
88529  * ## Example usage: 
88530  *
88531  *     var changingImage = Ext.create('Ext.Img', {
88532  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
88533  *         renderTo: Ext.getBody()
88534  *     });
88535  *      
88536  *     // change the src of the image programmatically
88537  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
88538 */
88539 Ext.define('Ext.Img', {
88540     extend: 'Ext.Component',
88541     alias: ['widget.image', 'widget.imagecomponent'],
88542     /** @cfg {String} src The image src */
88543     src: '',
88544
88545     getElConfig: function() {
88546         return {
88547             tag: 'img',
88548             src: this.src
88549         };
88550     },
88551     
88552     // null out this function, we can't set any html inside the image
88553     initRenderTpl: Ext.emptyFn,
88554     
88555     /**
88556      * Updates the {@link #src} of the image
88557      */
88558     setSrc: function(src) {
88559         var me = this,
88560             img = me.el;
88561         me.src = src;
88562         if (img) {
88563             img.dom.src = src;
88564         }
88565     }
88566 });
88567
88568 /**
88569  * @class Ext.Layer
88570  * @extends Ext.Element
88571  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
88572  * automatic maintaining of shadow/shim positions.
88573  *
88574  * @cfg {Boolean} [shim=true]
88575  * False to disable the iframe shim in browsers which need one.
88576  *
88577  * @cfg {String/Boolean} [shadow=false]
88578  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
88579  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
88580  *
88581  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
88582  * DomHelper object config to create element with.
88583  *
88584  * @cfg {Boolean} [constrain=true]
88585  * False to disable constrain to viewport.
88586  *
88587  * @cfg {String} cls
88588  * CSS class to add to the element
88589  *
88590  * @cfg {Number} [zindex=11000]
88591  * Starting z-index.
88592  *
88593  * @cfg {Number} [shadowOffset=4]
88594  * Number of pixels to offset the shadow
88595  *
88596  * @cfg {Boolean} [useDisplay=false]
88597  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
88598  * to use css style <tt>'display:none;'</tt> to hide the Layer.
88599  *
88600  * @cfg {String} visibilityCls
88601  * The CSS class name to add in order to hide this Layer if this layer
88602  * is configured with <code>{@link #hideMode}: 'asclass'</code>
88603  *
88604  * @cfg {String} hideMode
88605  * A String which specifies how this Layer will be hidden.
88606  * Values may be<div class="mdetail-params"><ul>
88607  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
88608  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
88609  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
88610  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
88611  * in a Component having zero dimensions.</li></ul></div>
88612  */
88613 Ext.define('Ext.Layer', {
88614     uses: ['Ext.Shadow'],
88615
88616     // shims are shared among layer to keep from having 100 iframes
88617     statics: {
88618         shims: []
88619     },
88620
88621     extend: 'Ext.Element',
88622
88623     /**
88624      * Creates new Layer.
88625      * @param {Object} config (optional) An object with config options.
88626      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
88627      * If the element is not found it creates it.
88628      */
88629     constructor: function(config, existingEl) {
88630         config = config || {};
88631         var me = this,
88632             dh = Ext.DomHelper,
88633             cp = config.parentEl,
88634             pel = cp ? Ext.getDom(cp) : document.body,
88635         hm = config.hideMode;
88636
88637         if (existingEl) {
88638             me.dom = Ext.getDom(existingEl);
88639         }
88640         if (!me.dom) {
88641             me.dom = dh.append(pel, config.dh || {
88642                 tag: 'div',
88643                 cls: Ext.baseCSSPrefix + 'layer'
88644             });
88645         } else {
88646             me.addCls(Ext.baseCSSPrefix + 'layer');
88647             if (!me.dom.parentNode) {
88648                 pel.appendChild(me.dom);
88649             }
88650         }
88651
88652         if (config.cls) {
88653             me.addCls(config.cls);
88654         }
88655         me.constrain = config.constrain !== false;
88656
88657         // Allow Components to pass their hide mode down to the Layer if they are floating.
88658         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
88659         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
88660         if (hm) {
88661             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
88662             if (me.visibilityMode == Ext.Element.ASCLASS) {
88663                 me.visibilityCls = config.visibilityCls;
88664             }
88665         } else if (config.useDisplay) {
88666             me.setVisibilityMode(Ext.Element.DISPLAY);
88667         } else {
88668             me.setVisibilityMode(Ext.Element.VISIBILITY);
88669         }
88670
88671         if (config.id) {
88672             me.id = me.dom.id = config.id;
88673         } else {
88674             me.id = Ext.id(me.dom);
88675         }
88676         me.position('absolute');
88677         if (config.shadow) {
88678             me.shadowOffset = config.shadowOffset || 4;
88679             me.shadow = Ext.create('Ext.Shadow', {
88680                 offset: me.shadowOffset,
88681                 mode: config.shadow
88682             });
88683             me.disableShadow();
88684         } else {
88685             me.shadowOffset = 0;
88686         }
88687         me.useShim = config.shim !== false && Ext.useShims;
88688         if (config.hidden === true) {
88689             me.hide();
88690         } else {
88691             me.show();
88692         }
88693     },
88694
88695     getZIndex: function() {
88696         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
88697     },
88698
88699     getShim: function() {
88700         var me = this,
88701             shim, pn;
88702
88703         if (!me.useShim) {
88704             return null;
88705         }
88706         if (!me.shim) {
88707             shim = me.self.shims.shift();
88708             if (!shim) {
88709                 shim = me.createShim();
88710                 shim.enableDisplayMode('block');
88711                 shim.hide();
88712             }
88713             pn = me.dom.parentNode;
88714             if (shim.dom.parentNode != pn) {
88715                 pn.insertBefore(shim.dom, me.dom);
88716             }
88717             me.shim = shim;
88718         }
88719         return me.shim;
88720     },
88721
88722     hideShim: function() {
88723         var me = this;
88724         
88725         if (me.shim) {
88726             me.shim.setDisplayed(false);
88727             me.self.shims.push(me.shim);
88728             delete me.shim;
88729         }
88730     },
88731
88732     disableShadow: function() {
88733         var me = this;
88734         
88735         if (me.shadow && !me.shadowDisabled) {
88736             me.shadowDisabled = true;
88737             me.shadow.hide();
88738             me.lastShadowOffset = me.shadowOffset;
88739             me.shadowOffset = 0;
88740         }
88741     },
88742
88743     enableShadow: function(show) {
88744         var me = this;
88745         
88746         if (me.shadow && me.shadowDisabled) {
88747             me.shadowDisabled = false;
88748             me.shadowOffset = me.lastShadowOffset;
88749             delete me.lastShadowOffset;
88750             if (show) {
88751                 me.sync(true);
88752             }
88753         }
88754     },
88755
88756     /**
88757      * @private
88758      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
88759      * <p>This code can execute repeatedly in milliseconds,
88760      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
88761      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
88762      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
88763      */
88764     sync: function(doShow) {
88765         var me = this,
88766             shadow = me.shadow,
88767             shadowPos, shimStyle, shadowSize;
88768
88769         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
88770             var shim = me.getShim(),
88771                 l = me.getLeft(true),
88772                 t = me.getTop(true),
88773                 w = me.dom.offsetWidth,
88774                 h = me.dom.offsetHeight,
88775                 shimIndex;
88776
88777             if (shadow && !me.shadowDisabled) {
88778                 if (doShow && !shadow.isVisible()) {
88779                     shadow.show(me);
88780                 } else {
88781                     shadow.realign(l, t, w, h);
88782                 }
88783                 if (shim) {
88784                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88785                     shimIndex = shim.getStyle('z-index');
88786                     if (shimIndex > me.zindex) {
88787                         me.shim.setStyle('z-index', me.zindex - 2);
88788                     }
88789                     shim.show();
88790                     // fit the shim behind the shadow, so it is shimmed too
88791                     if (shadow.isVisible()) {
88792                         shadowPos = shadow.el.getXY();
88793                         shimStyle = shim.dom.style;
88794                         shadowSize = shadow.el.getSize();
88795                         if (Ext.supports.CSS3BoxShadow) {
88796                             shadowSize.height += 6;
88797                             shadowSize.width += 4;
88798                             shadowPos[0] -= 2;
88799                             shadowPos[1] -= 4;
88800                         }
88801                         shimStyle.left = (shadowPos[0]) + 'px';
88802                         shimStyle.top = (shadowPos[1]) + 'px';
88803                         shimStyle.width = (shadowSize.width) + 'px';
88804                         shimStyle.height = (shadowSize.height) + 'px';
88805                     } else {
88806                         shim.setSize(w, h);
88807                         shim.setLeftTop(l, t);
88808                     }
88809                 }
88810             } else if (shim) {
88811                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88812                 shimIndex = shim.getStyle('z-index');
88813                 if (shimIndex > me.zindex) {
88814                     me.shim.setStyle('z-index', me.zindex - 2);
88815                 }
88816                 shim.show();
88817                 shim.setSize(w, h);
88818                 shim.setLeftTop(l, t);
88819             }
88820         }
88821         return me;
88822     },
88823
88824     remove: function() {
88825         this.hideUnders();
88826         this.callParent();
88827     },
88828
88829     // private
88830     beginUpdate: function() {
88831         this.updating = true;
88832     },
88833
88834     // private
88835     endUpdate: function() {
88836         this.updating = false;
88837         this.sync(true);
88838     },
88839
88840     // private
88841     hideUnders: function() {
88842         if (this.shadow) {
88843             this.shadow.hide();
88844         }
88845         this.hideShim();
88846     },
88847
88848     // private
88849     constrainXY: function() {
88850         if (this.constrain) {
88851             var vw = Ext.Element.getViewWidth(),
88852                 vh = Ext.Element.getViewHeight(),
88853                 s = Ext.getDoc().getScroll(),
88854                 xy = this.getXY(),
88855                 x = xy[0],
88856                 y = xy[1],
88857                 so = this.shadowOffset,
88858                 w = this.dom.offsetWidth + so,
88859                 h = this.dom.offsetHeight + so,
88860                 moved = false; // only move it if it needs it
88861             // first validate right/bottom
88862             if ((x + w) > vw + s.left) {
88863                 x = vw - w - so;
88864                 moved = true;
88865             }
88866             if ((y + h) > vh + s.top) {
88867                 y = vh - h - so;
88868                 moved = true;
88869             }
88870             // then make sure top/left isn't negative
88871             if (x < s.left) {
88872                 x = s.left;
88873                 moved = true;
88874             }
88875             if (y < s.top) {
88876                 y = s.top;
88877                 moved = true;
88878             }
88879             if (moved) {
88880                 Ext.Layer.superclass.setXY.call(this, [x, y]);
88881                 this.sync();
88882             }
88883         }
88884         return this;
88885     },
88886
88887     getConstrainOffset: function() {
88888         return this.shadowOffset;
88889     },
88890
88891     // overridden Element method
88892     setVisible: function(visible, animate, duration, callback, easing) {
88893         var me = this,
88894             cb;
88895
88896         // post operation processing
88897         cb = function() {
88898             if (visible) {
88899                 me.sync(true);
88900             }
88901             if (callback) {
88902                 callback();
88903             }
88904         };
88905
88906         // Hide shadow and shim if hiding
88907         if (!visible) {
88908             me.hideUnders(true);
88909         }
88910         me.callParent([visible, animate, duration, callback, easing]);
88911         if (!animate) {
88912             cb();
88913         }
88914         return me;
88915     },
88916
88917     // private
88918     beforeFx: function() {
88919         this.beforeAction();
88920         return this.callParent(arguments);
88921     },
88922
88923     // private
88924     afterFx: function() {
88925         this.callParent(arguments);
88926         this.sync(this.isVisible());
88927     },
88928
88929     // private
88930     beforeAction: function() {
88931         if (!this.updating && this.shadow) {
88932             this.shadow.hide();
88933         }
88934     },
88935
88936     // overridden Element method
88937     setLeft: function(left) {
88938         this.callParent(arguments);
88939         return this.sync();
88940     },
88941
88942     setTop: function(top) {
88943         this.callParent(arguments);
88944         return this.sync();
88945     },
88946
88947     setLeftTop: function(left, top) {
88948         this.callParent(arguments);
88949         return this.sync();
88950     },
88951
88952     setXY: function(xy, animate, duration, callback, easing) {
88953         var me = this;
88954         
88955         // Callback will restore shadow state and call the passed callback
88956         callback = me.createCB(callback);
88957
88958         me.fixDisplay();
88959         me.beforeAction();
88960         me.callParent([xy, animate, duration, callback, easing]);
88961         if (!animate) {
88962             callback();
88963         }
88964         return me;
88965     },
88966
88967     // private
88968     createCB: function(callback) {
88969         var me = this,
88970             showShadow = me.shadow && me.shadow.isVisible();
88971
88972         return function() {
88973             me.constrainXY();
88974             me.sync(showShadow);
88975             if (callback) {
88976                 callback();
88977             }
88978         };
88979     },
88980
88981     // overridden Element method
88982     setX: function(x, animate, duration, callback, easing) {
88983         this.setXY([x, this.getY()], animate, duration, callback, easing);
88984         return this;
88985     },
88986
88987     // overridden Element method
88988     setY: function(y, animate, duration, callback, easing) {
88989         this.setXY([this.getX(), y], animate, duration, callback, easing);
88990         return this;
88991     },
88992
88993     // overridden Element method
88994     setSize: function(w, h, animate, duration, callback, easing) {
88995         var me = this;
88996         
88997         // Callback will restore shadow state and call the passed callback
88998         callback = me.createCB(callback);
88999
89000         me.beforeAction();
89001         me.callParent([w, h, animate, duration, callback, easing]);
89002         if (!animate) {
89003             callback();
89004         }
89005         return me;
89006     },
89007
89008     // overridden Element method
89009     setWidth: function(w, animate, duration, callback, easing) {
89010         var me = this;
89011         
89012         // Callback will restore shadow state and call the passed callback
89013         callback = me.createCB(callback);
89014
89015         me.beforeAction();
89016         me.callParent([w, animate, duration, callback, easing]);
89017         if (!animate) {
89018             callback();
89019         }
89020         return me;
89021     },
89022
89023     // overridden Element method
89024     setHeight: function(h, animate, duration, callback, easing) {
89025         var me = this;
89026         
89027         // Callback will restore shadow state and call the passed callback
89028         callback = me.createCB(callback);
89029
89030         me.beforeAction();
89031         me.callParent([h, animate, duration, callback, easing]);
89032         if (!animate) {
89033             callback();
89034         }
89035         return me;
89036     },
89037
89038     // overridden Element method
89039     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
89040         var me = this;
89041         
89042         // Callback will restore shadow state and call the passed callback
89043         callback = me.createCB(callback);
89044
89045         me.beforeAction();
89046         if (!animate) {
89047             Ext.Layer.superclass.setXY.call(me, [x, y]);
89048             Ext.Layer.superclass.setSize.call(me, width, height);
89049             callback();
89050         } else {
89051             me.callParent([x, y, width, height, animate, duration, callback, easing]);
89052         }
89053         return me;
89054     },
89055
89056     /**
89057      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
89058      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
89059      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
89060      * element will receive the highest  z-index.
89061      * @param {Number} zindex The new z-index to set
89062      * @return {Ext.Layer} The Layer
89063      */
89064     setZIndex: function(zindex) {
89065         var me = this;
89066         
89067         me.zindex = zindex;
89068         if (me.getShim()) {
89069             me.shim.setStyle('z-index', zindex++);
89070         }
89071         if (me.shadow) {
89072             me.shadow.setZIndex(zindex++);
89073         }
89074         return me.setStyle('z-index', zindex);
89075     },
89076     
89077     setOpacity: function(opacity){
89078         if (this.shadow) {
89079             this.shadow.setOpacity(opacity);
89080         }
89081         return this.callParent(arguments);
89082     }
89083 });
89084
89085 /**
89086  * @class Ext.layout.component.ProgressBar
89087  * @extends Ext.layout.component.Component
89088  * @private
89089  */
89090
89091 Ext.define('Ext.layout.component.ProgressBar', {
89092
89093     /* Begin Definitions */
89094
89095     alias: ['layout.progressbar'],
89096
89097     extend: 'Ext.layout.component.Component',
89098
89099     /* End Definitions */
89100
89101     type: 'progressbar',
89102
89103     onLayout: function(width, height) {
89104         var me = this,
89105             owner = me.owner,
89106             textEl = owner.textEl;
89107         
89108         me.setElementSize(owner.el, width, height);
89109         textEl.setWidth(owner.el.getWidth(true));
89110         
89111         me.callParent([width, height]);
89112         
89113         owner.updateProgress(owner.value);
89114     }
89115 });
89116 /**
89117  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
89118  *
89119  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
89120  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
89121  * that has predictable points of interest at which you can update the control.
89122  *
89123  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
89124  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
89125  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
89126  * for indicating intermediate progress.
89127  *
89128  *     @example
89129  *     var p = Ext.create('Ext.ProgressBar', {
89130  *        renderTo: Ext.getBody(),
89131  *        width: 300
89132  *     });
89133  *
89134  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89135  *     p.wait({
89136  *         interval: 500, //bar will move fast!
89137  *         duration: 50000,
89138  *         increment: 15,
89139  *         text: 'Updating...',
89140  *         scope: this,
89141  *         fn: function(){
89142  *             p.updateText('Done!');
89143  *         }
89144  *     });
89145  */
89146 Ext.define('Ext.ProgressBar', {
89147     extend: 'Ext.Component',
89148     alias: 'widget.progressbar',
89149
89150     requires: [
89151         'Ext.Template',
89152         'Ext.CompositeElement',
89153         'Ext.TaskManager',
89154         'Ext.layout.component.ProgressBar'
89155     ],
89156
89157     uses: ['Ext.fx.Anim'],
89158
89159    /**
89160     * @cfg {Number} [value=0]
89161     * A floating point value between 0 and 1 (e.g., .5)
89162     */
89163
89164    /**
89165     * @cfg {String} [text='']
89166     * The progress bar text (defaults to '')
89167     */
89168
89169    /**
89170     * @cfg {String/HTMLElement/Ext.Element} textEl
89171     * The element to render the progress text to (defaults to the progress bar's internal text element)
89172     */
89173
89174    /**
89175     * @cfg {String} id
89176     * The progress bar element's id (defaults to an auto-generated id)
89177     */
89178
89179    /**
89180     * @cfg {String} [baseCls='x-progress']
89181     * The base CSS class to apply to the progress bar's wrapper element.
89182     */
89183     baseCls: Ext.baseCSSPrefix + 'progress',
89184
89185     config: {
89186         /**
89187         * @cfg {Boolean} animate
89188         * True to animate the progress bar during transitions
89189         */
89190         animate: false,
89191
89192         /**
89193          * @cfg {String} text
89194          * The text shown in the progress bar
89195          */
89196         text: ''
89197     },
89198
89199     // private
89200     waitTimer: null,
89201
89202     renderTpl: [
89203         '<div class="{baseCls}-text {baseCls}-text-back">',
89204             '<div>&#160;</div>',
89205         '</div>',
89206         '<div id="{id}-bar" class="{baseCls}-bar">',
89207             '<div class="{baseCls}-text">',
89208                 '<div>&#160;</div>',
89209             '</div>',
89210         '</div>'
89211     ],
89212
89213     componentLayout: 'progressbar',
89214
89215     // private
89216     initComponent: function() {
89217         this.callParent();
89218
89219         this.addChildEls('bar');
89220
89221         this.addEvents(
89222             /**
89223              * @event update
89224              * Fires after each update interval
89225              * @param {Ext.ProgressBar} this
89226              * @param {Number} value The current progress value
89227              * @param {String} text The current progress text
89228              */
89229             "update"
89230         );
89231     },
89232
89233     afterRender : function() {
89234         var me = this;
89235
89236         // This produces a composite w/2 el's (which is why we cannot use childEls or
89237         // renderSelectors):
89238         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
89239
89240         me.callParent(arguments);
89241
89242         if (me.value) {
89243             me.updateProgress(me.value, me.text);
89244         }
89245         else {
89246             me.updateText(me.text);
89247         }
89248     },
89249
89250     /**
89251      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
89252      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
89253      * it will never automatically reset -- you are responsible for determining when the progress is complete and
89254      * calling {@link #reset} to clear and/or hide the control.
89255      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
89256      * @param {String} [text=''] The string to display in the progress text element
89257      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
89258      * specified, the default for the class is used
89259      * @return {Ext.ProgressBar} this
89260      */
89261     updateProgress: function(value, text, animate) {
89262         var me = this,
89263             newWidth;
89264             
89265         me.value = value || 0;
89266         if (text) {
89267             me.updateText(text);
89268         }
89269         if (me.rendered && !me.isDestroyed) {
89270             if (me.isVisible(true)) {
89271                 newWidth = Math.floor(me.value * me.el.getWidth(true));
89272                 if (Ext.isForcedBorderBox) {
89273                     newWidth += me.bar.getBorderWidth("lr");
89274                 }
89275                 if (animate === true || (animate !== false && me.animate)) {
89276                     me.bar.stopAnimation();
89277                     me.bar.animate(Ext.apply({
89278                         to: {
89279                             width: newWidth + 'px'
89280                         }
89281                     }, me.animate));
89282                 } else {
89283                     me.bar.setWidth(newWidth);
89284                 }
89285             } else {
89286                 // force a layout when we're visible again
89287                 me.doComponentLayout();
89288             }
89289         }
89290         me.fireEvent('update', me, me.value, text);
89291         return me;
89292     },
89293
89294     /**
89295      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
89296      * display the updated text.
89297      * @param {String} [text=''] The string to display in the progress text element
89298      * @return {Ext.ProgressBar} this
89299      */
89300     updateText: function(text) {
89301         var me = this;
89302         
89303         me.text = text;
89304         if (me.rendered) {
89305             me.textEl.update(me.text);
89306         }
89307         return me;
89308     },
89309
89310     applyText : function(text) {
89311         this.updateText(text);
89312     },
89313
89314     /**
89315      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
89316      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
89317      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
89318      * {@link #reset}.
89319      *
89320      * Example usage:
89321      *
89322      *     var p = new Ext.ProgressBar({
89323      *        renderTo: 'my-el'
89324      *     });
89325      *
89326      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89327      *     var p = Ext.create('Ext.ProgressBar', {
89328      *        renderTo: Ext.getBody(),
89329      *        width: 300
89330      *     });
89331      *
89332      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89333      *     p.wait({
89334      *        interval: 500, //bar will move fast!
89335      *        duration: 50000,
89336      *        increment: 15,
89337      *        text: 'Updating...',
89338      *        scope: this,
89339      *        fn: function(){
89340      *           p.updateText('Done!');
89341      *        }
89342      *     });
89343      *
89344      *     //Or update indefinitely until some async action completes, then reset manually
89345      *     p.wait();
89346      *     myAction.on('complete', function(){
89347      *         p.reset();
89348      *         p.updateText('Done!');
89349      *     });
89350      *
89351      * @param {Object} config (optional) Configuration options
89352      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
89353      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
89354      * until reset is called)
89355      * @param {Number} config.interval The length of time in milliseconds between each progress update
89356      * (defaults to 1000 ms)
89357      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
89358      * value is not specified, the default for the class is used.
89359      * @param {Number} config.increment The number of progress update segments to display within the
89360      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
89361      * automatically wrap back to the beginning.
89362      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
89363      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
89364      * updating.  The function will be called with no arguments.  This function will be ignored if
89365      * duration is not specified since in that case the progress bar can only be stopped programmatically,
89366      * so any required function should be called by the same code after it resets the progress bar.
89367      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
89368      * duration and fn are both passed).
89369      * @return {Ext.ProgressBar} this
89370      */
89371     wait: function(o) {
89372         var me = this;
89373             
89374         if (!me.waitTimer) {
89375             scope = me;
89376             o = o || {};
89377             me.updateText(o.text);
89378             me.waitTimer = Ext.TaskManager.start({
89379                 run: function(i){
89380                     var inc = o.increment || 10;
89381                     i -= 1;
89382                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
89383                 },
89384                 interval: o.interval || 1000,
89385                 duration: o.duration,
89386                 onStop: function(){
89387                     if (o.fn) {
89388                         o.fn.apply(o.scope || me);
89389                     }
89390                     me.reset();
89391                 },
89392                 scope: scope
89393             });
89394         }
89395         return me;
89396     },
89397
89398     /**
89399      * Returns true if the progress bar is currently in a {@link #wait} operation
89400      * @return {Boolean} True if waiting, else false
89401      */
89402     isWaiting: function(){
89403         return this.waitTimer !== null;
89404     },
89405
89406     /**
89407      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
89408      * (using the {@link #hideMode} property internally).
89409      * @param {Boolean} [hide=false] True to hide the progress bar.
89410      * @return {Ext.ProgressBar} this
89411      */
89412     reset: function(hide){
89413         var me = this;
89414         
89415         me.updateProgress(0);
89416         me.clearTimer();
89417         if (hide === true) {
89418             me.hide();
89419         }
89420         return me;
89421     },
89422
89423     // private
89424     clearTimer: function(){
89425         var me = this;
89426         
89427         if (me.waitTimer) {
89428             me.waitTimer.onStop = null; //prevent recursion
89429             Ext.TaskManager.stop(me.waitTimer);
89430             me.waitTimer = null;
89431         }
89432     },
89433
89434     onDestroy: function(){
89435         var me = this;
89436         
89437         me.clearTimer();
89438         if (me.rendered) {
89439             if (me.textEl.isComposite) {
89440                 me.textEl.clear();
89441             }
89442             Ext.destroyMembers(me, 'textEl', 'progressBar');
89443         }
89444         me.callParent();
89445     }
89446 });
89447
89448 /**
89449  * Private utility class that manages the internal Shadow cache
89450  * @private
89451  */
89452 Ext.define('Ext.ShadowPool', {
89453     singleton: true,
89454     requires: ['Ext.DomHelper'],
89455
89456     markup: function() {
89457         if (Ext.supports.CSS3BoxShadow) {
89458             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
89459         } else if (Ext.isIE) {
89460             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
89461         } else {
89462             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
89463                 '<div class="xst" role="presentation">' +
89464                     '<div class="xstl" role="presentation"></div>' +
89465                     '<div class="xstc" role="presentation"></div>' +
89466                     '<div class="xstr" role="presentation"></div>' +
89467                 '</div>' +
89468                 '<div class="xsc" role="presentation">' +
89469                     '<div class="xsml" role="presentation"></div>' +
89470                     '<div class="xsmc" role="presentation"></div>' +
89471                     '<div class="xsmr" role="presentation"></div>' +
89472                 '</div>' +
89473                 '<div class="xsb" role="presentation">' +
89474                     '<div class="xsbl" role="presentation"></div>' +
89475                     '<div class="xsbc" role="presentation"></div>' +
89476                     '<div class="xsbr" role="presentation"></div>' +
89477                 '</div>' +
89478             '</div>';
89479         }
89480     }(),
89481
89482     shadows: [],
89483
89484     pull: function() {
89485         var sh = this.shadows.shift();
89486         if (!sh) {
89487             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
89488             sh.autoBoxAdjust = false;
89489         }
89490         return sh;
89491     },
89492
89493     push: function(sh) {
89494         this.shadows.push(sh);
89495     },
89496     
89497     reset: function() {
89498         Ext.Array.each(this.shadows, function(shadow) {
89499             shadow.remove();
89500         });
89501         this.shadows = [];
89502     }
89503 });
89504 /**
89505  * @class Ext.Shadow
89506  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
89507  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
89508  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
89509  */
89510 Ext.define('Ext.Shadow', {
89511     requires: ['Ext.ShadowPool'],
89512
89513     /**
89514      * Creates new Shadow.
89515      * @param {Object} config (optional) Config object.
89516      */
89517     constructor: function(config) {
89518         var me = this,
89519             adjusts = {
89520                 h: 0
89521             },
89522             offset,
89523             rad;
89524         
89525         Ext.apply(me, config);
89526         if (!Ext.isString(me.mode)) {
89527             me.mode = me.defaultMode;
89528         }
89529         offset = me.offset;
89530         rad = Math.floor(offset / 2);
89531         me.opacity = 50;
89532         switch (me.mode.toLowerCase()) {
89533             // all this hideous nonsense calculates the various offsets for shadows
89534             case "drop":
89535                 if (Ext.supports.CSS3BoxShadow) {
89536                     adjusts.w = adjusts.h = -offset;
89537                     adjusts.l = adjusts.t = offset;
89538                 } else {
89539                     adjusts.w = 0;
89540                     adjusts.l = adjusts.t = offset;
89541                     adjusts.t -= 1;
89542                     if (Ext.isIE) {
89543                         adjusts.l -= offset + rad;
89544                         adjusts.t -= offset + rad;
89545                         adjusts.w -= rad;
89546                         adjusts.h -= rad;
89547                         adjusts.t += 1;
89548                     }
89549                 }
89550                 break;
89551             case "sides":
89552                 if (Ext.supports.CSS3BoxShadow) {
89553                     adjusts.h -= offset;
89554                     adjusts.t = offset;
89555                     adjusts.l = adjusts.w = 0;
89556                 } else {
89557                     adjusts.w = (offset * 2);
89558                     adjusts.l = -offset;
89559                     adjusts.t = offset - 1;
89560                     if (Ext.isIE) {
89561                         adjusts.l -= (offset - rad);
89562                         adjusts.t -= offset + rad;
89563                         adjusts.l += 1;
89564                         adjusts.w -= (offset - rad) * 2;
89565                         adjusts.w -= rad + 1;
89566                         adjusts.h -= 1;
89567                     }
89568                 }
89569                 break;
89570             case "frame":
89571                 if (Ext.supports.CSS3BoxShadow) {
89572                     adjusts.l = adjusts.w = adjusts.t = 0;
89573                 } else {
89574                     adjusts.w = adjusts.h = (offset * 2);
89575                     adjusts.l = adjusts.t = -offset;
89576                     adjusts.t += 1;
89577                     adjusts.h -= 2;
89578                     if (Ext.isIE) {
89579                         adjusts.l -= (offset - rad);
89580                         adjusts.t -= (offset - rad);
89581                         adjusts.l += 1;
89582                         adjusts.w -= (offset + rad + 1);
89583                         adjusts.h -= (offset + rad);
89584                         adjusts.h += 1;
89585                     }
89586                     break;
89587                 }
89588         }
89589         me.adjusts = adjusts;
89590     },
89591
89592     /**
89593      * @cfg {String} mode
89594      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
89595      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
89596      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
89597      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
89598      * </ul></div>
89599      */
89600     /**
89601      * @cfg {Number} offset
89602      * The number of pixels to offset the shadow from the element
89603      */
89604     offset: 4,
89605
89606     // private
89607     defaultMode: "drop",
89608
89609     /**
89610      * Displays the shadow under the target element
89611      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
89612      */
89613     show: function(target) {
89614         var me = this,
89615             index;
89616         
89617         target = Ext.get(target);
89618         if (!me.el) {
89619             me.el = Ext.ShadowPool.pull();
89620             if (me.el.dom.nextSibling != target.dom) {
89621                 me.el.insertBefore(target);
89622             }
89623         }
89624         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
89625         me.el.setStyle("z-index", me.zIndex || index);
89626         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89627             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
89628         }
89629         me.realign(
89630             target.getLeft(true),
89631             target.getTop(true),
89632             target.dom.offsetWidth,
89633             target.dom.offsetHeight
89634         );
89635         me.el.dom.style.display = "block";
89636     },
89637
89638     /**
89639      * Returns true if the shadow is visible, else false
89640      */
89641     isVisible: function() {
89642         return this.el ? true: false;
89643     },
89644
89645     /**
89646      * Direct alignment when values are already available. Show must be called at least once before
89647      * calling this method to ensure it is initialized.
89648      * @param {Number} left The target element left position
89649      * @param {Number} top The target element top position
89650      * @param {Number} width The target element width
89651      * @param {Number} height The target element height
89652      */
89653     realign: function(l, t, targetWidth, targetHeight) {
89654         if (!this.el) {
89655             return;
89656         }
89657         var adjusts = this.adjusts,
89658             d = this.el.dom,
89659             targetStyle = d.style,
89660             shadowWidth,
89661             shadowHeight,
89662             cn,
89663             sww, 
89664             sws, 
89665             shs;
89666
89667         targetStyle.left = (l + adjusts.l) + "px";
89668         targetStyle.top = (t + adjusts.t) + "px";
89669         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
89670         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
89671         sws = shadowWidth + "px";
89672         shs = shadowHeight + "px";
89673         if (targetStyle.width != sws || targetStyle.height != shs) {
89674             targetStyle.width = sws;
89675             targetStyle.height = shs;
89676             if (Ext.supports.CSS3BoxShadow) {
89677                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
89678             } else {
89679
89680                 // Adjust the 9 point framed element to poke out on the required sides
89681                 if (!Ext.isIE) {
89682                     cn = d.childNodes;
89683                     sww = Math.max(0, (shadowWidth - 12)) + "px";
89684                     cn[0].childNodes[1].style.width = sww;
89685                     cn[1].childNodes[1].style.width = sww;
89686                     cn[2].childNodes[1].style.width = sww;
89687                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
89688                 }
89689             }
89690         }
89691     },
89692
89693     /**
89694      * Hides this shadow
89695      */
89696     hide: function() {
89697         var me = this;
89698         
89699         if (me.el) {
89700             me.el.dom.style.display = "none";
89701             Ext.ShadowPool.push(me.el);
89702             delete me.el;
89703         }
89704     },
89705
89706     /**
89707      * Adjust the z-index of this shadow
89708      * @param {Number} zindex The new z-index
89709      */
89710     setZIndex: function(z) {
89711         this.zIndex = z;
89712         if (this.el) {
89713             this.el.setStyle("z-index", z);
89714         }
89715     },
89716     
89717     /**
89718      * Sets the opacity of the shadow
89719      * @param {Number} opacity The opacity
89720      */
89721     setOpacity: function(opacity){
89722         if (this.el) {
89723             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89724                 opacity = Math.floor(opacity * 100 / 2) / 100;
89725             }
89726             this.opacity = opacity;
89727             this.el.setOpacity(opacity);
89728         }
89729     }
89730 });
89731 /**
89732  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
89733  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
89734  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
89735  *
89736  *     @example
89737  *     // display a dropdown menu:
89738  *     Ext.create('Ext.button.Split', {
89739  *         renderTo: Ext.getBody(),
89740  *         text: 'Options',
89741  *         // handle a click on the button itself
89742  *         handler: function() {
89743  *             alert("The button was clicked");
89744  *         },
89745  *         menu: new Ext.menu.Menu({
89746  *             items: [
89747  *                 // these will render as dropdown menu items when the arrow is clicked:
89748  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
89749  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
89750  *             ]
89751  *         })
89752  *     });
89753  *
89754  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
89755  * arrow is clicked:
89756  *
89757  *     Ext.create('Ext.button.Split', {
89758  *         renderTo: 'button-ct',
89759  *         text: 'Options',
89760  *         handler: optionsHandler,
89761  *         arrowHandler: myCustomHandler
89762  *     });
89763  *
89764  */
89765 Ext.define('Ext.button.Split', {
89766
89767     /* Begin Definitions */
89768     alias: 'widget.splitbutton',
89769
89770     extend: 'Ext.button.Button',
89771     alternateClassName: 'Ext.SplitButton',
89772     /* End Definitions */
89773     
89774     /**
89775      * @cfg {Function} arrowHandler
89776      * A function called when the arrow button is clicked (can be used instead of click event)
89777      */
89778     /**
89779      * @cfg {String} arrowTooltip
89780      * The title attribute of the arrow
89781      */
89782
89783     // private
89784     arrowCls      : 'split',
89785     split         : true,
89786
89787     // private
89788     initComponent : function(){
89789         this.callParent();
89790         /**
89791          * @event arrowclick
89792          * Fires when this button's arrow is clicked.
89793          * @param {Ext.button.Split} this
89794          * @param {Event} e The click event
89795          */
89796         this.addEvents("arrowclick");
89797     },
89798
89799     /**
89800      * Sets this button's arrow click handler.
89801      * @param {Function} handler The function to call when the arrow is clicked
89802      * @param {Object} scope (optional) Scope for the function passed above
89803      */
89804     setArrowHandler : function(handler, scope){
89805         this.arrowHandler = handler;
89806         this.scope = scope;
89807     },
89808
89809     // private
89810     onClick : function(e, t) {
89811         var me = this;
89812         
89813         e.preventDefault();
89814         if (!me.disabled) {
89815             if (me.overMenuTrigger) {
89816                 me.maybeShowMenu();
89817                 me.fireEvent("arrowclick", me, e);
89818                 if (me.arrowHandler) {
89819                     me.arrowHandler.call(me.scope || me, me, e);
89820                 }
89821             } else {
89822                 me.doToggle();
89823                 me.fireHandler();
89824             }
89825         }
89826     }
89827 });
89828 /**
89829  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
89830  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
89831  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
89832  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
89833  *
89834  *     @example
89835  *     Ext.create('Ext.button.Cycle', {
89836  *         showText: true,
89837  *         prependText: 'View as ',
89838  *         renderTo: Ext.getBody(),
89839  *         menu: {
89840  *             id: 'view-type-menu',
89841  *             items: [{
89842  *                 text: 'text only',
89843  *                 iconCls: 'view-text',
89844  *                 checked: true
89845  *             },{
89846  *                 text: 'HTML',
89847  *                 iconCls: 'view-html'
89848  *             }]
89849  *         },
89850  *         changeHandler: function(cycleBtn, activeItem) {
89851  *             Ext.Msg.alert('Change View', activeItem.text);
89852  *         }
89853  *     });
89854  */
89855 Ext.define('Ext.button.Cycle', {
89856
89857     /* Begin Definitions */
89858
89859     alias: 'widget.cycle',
89860
89861     extend: 'Ext.button.Split',
89862     alternateClassName: 'Ext.CycleButton',
89863
89864     /* End Definitions */
89865
89866     /**
89867      * @cfg {Object[]} items
89868      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
89869      * `{text:'Foo', iconCls:'foo-icon'}`)
89870      * 
89871      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
89872      * {@link Ext.menu.CheckItem CheckItems}.
89873      */
89874     /**
89875      * @cfg {Boolean} [showText=false]
89876      * True to display the active item's text as the button text. The Button will show its
89877      * configured {@link #text} if this config is omitted.
89878      */
89879     /**
89880      * @cfg {String} [prependText='']
89881      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
89882      * showText = true).
89883      */
89884     /**
89885      * @cfg {Function} changeHandler
89886      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
89887      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
89888      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
89889      * item)
89890      */
89891     /**
89892      * @cfg {String} forceIcon
89893      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
89894      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
89895      * button's icon to match the selected item's icon on change.
89896      */
89897     /**
89898      * @property {Ext.menu.Menu} menu
89899      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
89900      * available choices.
89901      */
89902
89903     // private
89904     getButtonText: function(item) {
89905         var me = this,
89906             text = '';
89907
89908         if (item && me.showText === true) {
89909             if (me.prependText) {
89910                 text += me.prependText;
89911             }
89912             text += item.text;
89913             return text;
89914         }
89915         return me.text;
89916     },
89917
89918     /**
89919      * Sets the button's active menu item.
89920      * @param {Ext.menu.CheckItem} item The item to activate
89921      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
89922      */
89923     setActiveItem: function(item, suppressEvent) {
89924         var me = this;
89925
89926         if (!Ext.isObject(item)) {
89927             item = me.menu.getComponent(item);
89928         }
89929         if (item) {
89930             if (!me.rendered) {
89931                 me.text = me.getButtonText(item);
89932                 me.iconCls = item.iconCls;
89933             } else {
89934                 me.setText(me.getButtonText(item));
89935                 me.setIconCls(item.iconCls);
89936             }
89937             me.activeItem = item;
89938             if (!item.checked) {
89939                 item.setChecked(true, false);
89940             }
89941             if (me.forceIcon) {
89942                 me.setIconCls(me.forceIcon);
89943             }
89944             if (!suppressEvent) {
89945                 me.fireEvent('change', me, item);
89946             }
89947         }
89948     },
89949
89950     /**
89951      * Gets the currently active menu item.
89952      * @return {Ext.menu.CheckItem} The active item
89953      */
89954     getActiveItem: function() {
89955         return this.activeItem;
89956     },
89957
89958     // private
89959     initComponent: function() {
89960         var me = this,
89961             checked = 0,
89962             items;
89963
89964         me.addEvents(
89965             /**
89966              * @event change
89967              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
89968              * set on this CycleButton, it will be called instead on active item change and this change event will not
89969              * be fired.
89970              * @param {Ext.button.Cycle} this
89971              * @param {Ext.menu.CheckItem} item The menu item that was selected
89972              */
89973             "change"
89974         );
89975
89976         if (me.changeHandler) {
89977             me.on('change', me.changeHandler, me.scope || me);
89978             delete me.changeHandler;
89979         }
89980
89981         // Allow them to specify a menu config which is a standard Button config.
89982         // Remove direct use of "items" in 5.0.
89983         items = (me.menu.items||[]).concat(me.items||[]);
89984         me.menu = Ext.applyIf({
89985             cls: Ext.baseCSSPrefix + 'cycle-menu',
89986             items: []
89987         }, me.menu);
89988
89989         // Convert all items to CheckItems
89990         Ext.each(items, function(item, i) {
89991             item = Ext.applyIf({
89992                 group: me.id,
89993                 itemIndex: i,
89994                 checkHandler: me.checkHandler,
89995                 scope: me,
89996                 checked: item.checked || false
89997             }, item);
89998             me.menu.items.push(item);
89999             if (item.checked) {
90000                 checked = i;
90001             }
90002         });
90003         me.itemCount = me.menu.items.length;
90004         me.callParent(arguments);
90005         me.on('click', me.toggleSelected, me);
90006         me.setActiveItem(checked, me);
90007
90008         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
90009         if (me.width && me.showText) {
90010             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
90011         }
90012     },
90013
90014     // private
90015     checkHandler: function(item, pressed) {
90016         if (pressed) {
90017             this.setActiveItem(item);
90018         }
90019     },
90020
90021     /**
90022      * This is normally called internally on button click, but can be called externally to advance the button's active
90023      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
90024      * item will be set to the first item in the menu.
90025      */
90026     toggleSelected: function() {
90027         var me = this,
90028             m = me.menu,
90029             checkItem;
90030
90031         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
90032         checkItem.setChecked(true);
90033     }
90034 });
90035 /**
90036  * Provides a container for arranging a group of related Buttons in a tabular manner.
90037  *
90038  *     @example
90039  *     Ext.create('Ext.panel.Panel', {
90040  *         title: 'Panel with ButtonGroup',
90041  *         width: 300,
90042  *         height:200,
90043  *         renderTo: document.body,
90044  *         bodyPadding: 10,
90045  *         html: 'HTML Panel Content',
90046  *         tbar: [{
90047  *             xtype: 'buttongroup',
90048  *             columns: 3,
90049  *             title: 'Clipboard',
90050  *             items: [{
90051  *                 text: 'Paste',
90052  *                 scale: 'large',
90053  *                 rowspan: 3,
90054  *                 iconCls: 'add',
90055  *                 iconAlign: 'top',
90056  *                 cls: 'btn-as-arrow'
90057  *             },{
90058  *                 xtype:'splitbutton',
90059  *                 text: 'Menu Button',
90060  *                 scale: 'large',
90061  *                 rowspan: 3,
90062  *                 iconCls: 'add',
90063  *                 iconAlign: 'top',
90064  *                 arrowAlign:'bottom',
90065  *                 menu: [{ text: 'Menu Item 1' }]
90066  *             },{
90067  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
90068  *             },{
90069  *                 text: 'Copy', iconCls: 'add16'
90070  *             },{
90071  *                 text: 'Format', iconCls: 'add16'
90072  *             }]
90073  *         }]
90074  *     });
90075  *
90076  */
90077 Ext.define('Ext.container.ButtonGroup', {
90078     extend: 'Ext.panel.Panel',
90079     alias: 'widget.buttongroup',
90080     alternateClassName: 'Ext.ButtonGroup',
90081
90082     /**
90083      * @cfg {Number} columns The `columns` configuration property passed to the
90084      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
90085      */
90086
90087     /**
90088      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
90089      */
90090     baseCls: Ext.baseCSSPrefix + 'btn-group',
90091
90092     /**
90093      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
90094      */
90095     layout: {
90096         type: 'table'
90097     },
90098
90099     defaultType: 'button',
90100
90101     /**
90102      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
90103      */
90104     frame: true,
90105
90106     frameHeader: false,
90107
90108     internalDefaults: {removeMode: 'container', hideParent: true},
90109
90110     initComponent : function(){
90111         // Copy the component's columns config to the layout if specified
90112         var me = this,
90113             cols = me.columns;
90114
90115         me.noTitleCls = me.baseCls + '-notitle';
90116         if (cols) {
90117             me.layout = Ext.apply({}, {columns: cols}, me.layout);
90118         }
90119
90120         if (!me.title) {
90121             me.addCls(me.noTitleCls);
90122         }
90123         me.callParent(arguments);
90124     },
90125
90126     afterLayout: function() {
90127         var me = this;
90128
90129         me.callParent(arguments);
90130
90131         // Pugly hack for a pugly browser:
90132         // If not an explicitly set width, then size the width to match the inner table
90133         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
90134             var t = me.getTargetEl();
90135             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
90136         }
90137
90138         // IE7 needs a forced repaint to make the top framing div expand to full width
90139         if (Ext.isIE7) {
90140             me.el.repaint();
90141         }
90142     },
90143
90144     afterRender: function() {
90145         var me = this;
90146
90147         //we need to add an addition item in here so the ButtonGroup title is centered
90148         if (me.header) {
90149             // Header text cannot flex, but must be natural size if it's being centered
90150             delete me.header.items.items[0].flex;
90151
90152             // For Centering, surround the text with two flex:1 spacers.
90153             me.suspendLayout = true;
90154             me.header.insert(1, {
90155                 xtype: 'component',
90156                 ui   : me.ui,
90157                 flex : 1
90158             });
90159             me.header.insert(0, {
90160                 xtype: 'component',
90161                 ui   : me.ui,
90162                 flex : 1
90163             });
90164             me.suspendLayout = false;
90165         }
90166
90167         me.callParent(arguments);
90168     },
90169
90170     // private
90171     onBeforeAdd: function(component) {
90172         if (component.is('button')) {
90173             component.ui = component.ui + '-toolbar';
90174         }
90175         this.callParent(arguments);
90176     },
90177
90178     //private
90179     applyDefaults: function(c) {
90180         if (!Ext.isString(c)) {
90181             c = this.callParent(arguments);
90182             var d = this.internalDefaults;
90183             if (c.events) {
90184                 Ext.applyIf(c.initialConfig, d);
90185                 Ext.apply(c, d);
90186             } else {
90187                 Ext.applyIf(c, d);
90188             }
90189         }
90190         return c;
90191     }
90192
90193     /**
90194      * @cfg {Array} tools  @hide
90195      */
90196     /**
90197      * @cfg {Boolean} collapsible  @hide
90198      */
90199     /**
90200      * @cfg {Boolean} collapseMode  @hide
90201      */
90202     /**
90203      * @cfg {Boolean} animCollapse  @hide
90204      */
90205     /**
90206      * @cfg {Boolean} closable  @hide
90207      */
90208 });
90209
90210 /**
90211  * A specialized container representing the viewable application area (the browser viewport).
90212  *
90213  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
90214  * the browser viewport and manages window resizing. There may only be one Viewport created
90215  * in a page.
90216  *
90217  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
90218  * on its child Components if you configure it with a {@link #layout}.
90219  *
90220  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
90221  * required layout is simpler, a different layout should be chosen.
90222  *
90223  * For example, to simply make a single child item occupy all available space, use
90224  * {@link Ext.layout.container.Fit fit layout}.
90225  *
90226  * To display one "active" item at full size from a choice of several child items, use
90227  * {@link Ext.layout.container.Card card layout}.
90228  *
90229  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
90230  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
90231  * method of any of its child Panels may themselves have a layout.
90232  *
90233  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
90234  * for scrolling if needed using the {@link #autoScroll} config.
90235  *
90236  * An example showing a classic application border layout:
90237  *
90238  *     @example
90239  *     Ext.create('Ext.container.Viewport', {
90240  *         layout: 'border',
90241  *         items: [{
90242  *             region: 'north',
90243  *             html: '<h1 class="x-panel-header">Page Title</h1>',
90244  *             autoHeight: true,
90245  *             border: false,
90246  *             margins: '0 0 5 0'
90247  *         }, {
90248  *             region: 'west',
90249  *             collapsible: true,
90250  *             title: 'Navigation',
90251  *             width: 150
90252  *             // could use a TreePanel or AccordionLayout for navigational items
90253  *         }, {
90254  *             region: 'south',
90255  *             title: 'South Panel',
90256  *             collapsible: true,
90257  *             html: 'Information goes here',
90258  *             split: true,
90259  *             height: 100,
90260  *             minHeight: 100
90261  *         }, {
90262  *             region: 'east',
90263  *             title: 'East Panel',
90264  *             collapsible: true,
90265  *             split: true,
90266  *             width: 150
90267  *         }, {
90268  *             region: 'center',
90269  *             xtype: 'tabpanel', // TabPanel itself has no title
90270  *             activeTab: 0,      // First tab active by default
90271  *             items: {
90272  *                 title: 'Default Tab',
90273  *                 html: 'The first tab\'s content. Others may be added dynamically'
90274  *             }
90275  *         }]
90276  *     });
90277  */
90278 Ext.define('Ext.container.Viewport', {
90279     extend: 'Ext.container.Container',
90280     alias: 'widget.viewport',
90281     requires: ['Ext.EventManager'],
90282     alternateClassName: 'Ext.Viewport',
90283
90284     // Privatize config options which, if used, would interfere with the
90285     // correct operation of the Viewport as the sole manager of the
90286     // layout of the document body.
90287
90288     /**
90289      * @cfg {String/HTMLElement/Ext.Element} applyTo
90290      * Not applicable.
90291      */
90292
90293     /**
90294      * @cfg {Boolean} allowDomMove
90295      * Not applicable.
90296      */
90297
90298     /**
90299      * @cfg {Boolean} hideParent
90300      * Not applicable.
90301      */
90302
90303     /**
90304      * @cfg {String/HTMLElement/Ext.Element} renderTo
90305      * Not applicable. Always renders to document body.
90306      */
90307
90308     /**
90309      * @cfg {Boolean} hideParent
90310      * Not applicable.
90311      */
90312
90313     /**
90314      * @cfg {Number} height
90315      * Not applicable. Sets itself to viewport width.
90316      */
90317
90318     /**
90319      * @cfg {Number} width
90320      * Not applicable. Sets itself to viewport height.
90321      */
90322
90323     /**
90324      * @cfg {Boolean} autoHeight
90325      * Not applicable.
90326      */
90327
90328     /**
90329      * @cfg {Boolean} autoWidth
90330      * Not applicable.
90331      */
90332
90333     /**
90334      * @cfg {Boolean} deferHeight
90335      * Not applicable.
90336      */
90337
90338     /**
90339      * @cfg {Boolean} monitorResize
90340      * Not applicable.
90341      */
90342
90343     isViewport: true,
90344
90345     ariaRole: 'application',
90346
90347     initComponent : function() {
90348         var me = this,
90349             html = Ext.fly(document.body.parentNode),
90350             el;
90351         me.callParent(arguments);
90352         html.addCls(Ext.baseCSSPrefix + 'viewport');
90353         if (me.autoScroll) {
90354             html.setStyle('overflow', 'auto');
90355         }
90356         me.el = el = Ext.getBody();
90357         el.setHeight = Ext.emptyFn;
90358         el.setWidth = Ext.emptyFn;
90359         el.setSize = Ext.emptyFn;
90360         el.dom.scroll = 'no';
90361         me.allowDomMove = false;
90362         Ext.EventManager.onWindowResize(me.fireResize, me);
90363         me.renderTo = me.el;
90364         me.width = Ext.Element.getViewportWidth();
90365         me.height = Ext.Element.getViewportHeight();
90366     },
90367
90368     fireResize : function(w, h){
90369         // setSize is the single entry point to layouts
90370         this.setSize(w, h);
90371     }
90372 });
90373
90374 /*
90375  * This is a derivative of the similarly named class in the YUI Library.
90376  * The original license:
90377  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
90378  * Code licensed under the BSD License:
90379  * http://developer.yahoo.net/yui/license.txt
90380  */
90381
90382
90383 /**
90384  * @class Ext.dd.DDTarget
90385  * @extends Ext.dd.DragDrop
90386  * A DragDrop implementation that does not move, but can be a drop
90387  * target.  You would get the same result by simply omitting implementation
90388  * for the event callbacks, but this way we reduce the processing cost of the
90389  * event listener and the callbacks.
90390  */
90391 Ext.define('Ext.dd.DDTarget', {
90392     extend: 'Ext.dd.DragDrop',
90393
90394     /**
90395      * Creates new DDTarget.
90396      * @param {String} id the id of the element that is a drop target
90397      * @param {String} sGroup the group of related DragDrop objects
90398      * @param {Object} config an object containing configurable attributes.
90399      * Valid properties for DDTarget in addition to those in DragDrop: none.
90400      */
90401     constructor: function(id, sGroup, config) {
90402         if (id) {
90403             this.initTarget(id, sGroup, config);
90404         }
90405     },
90406
90407     /**
90408      * @hide
90409      * Overridden and disabled. A DDTarget does not support being dragged.
90410      * @method
90411      */
90412     getDragEl: Ext.emptyFn,
90413     /**
90414      * @hide
90415      * Overridden and disabled. A DDTarget does not support being dragged.
90416      * @method
90417      */
90418     isValidHandleChild: Ext.emptyFn,
90419     /**
90420      * @hide
90421      * Overridden and disabled. A DDTarget does not support being dragged.
90422      * @method
90423      */
90424     startDrag: Ext.emptyFn,
90425     /**
90426      * @hide
90427      * Overridden and disabled. A DDTarget does not support being dragged.
90428      * @method
90429      */
90430     endDrag: Ext.emptyFn,
90431     /**
90432      * @hide
90433      * Overridden and disabled. A DDTarget does not support being dragged.
90434      * @method
90435      */
90436     onDrag: Ext.emptyFn,
90437     /**
90438      * @hide
90439      * Overridden and disabled. A DDTarget does not support being dragged.
90440      * @method
90441      */
90442     onDragDrop: Ext.emptyFn,
90443     /**
90444      * @hide
90445      * Overridden and disabled. A DDTarget does not support being dragged.
90446      * @method
90447      */
90448     onDragEnter: Ext.emptyFn,
90449     /**
90450      * @hide
90451      * Overridden and disabled. A DDTarget does not support being dragged.
90452      * @method
90453      */
90454     onDragOut: Ext.emptyFn,
90455     /**
90456      * @hide
90457      * Overridden and disabled. A DDTarget does not support being dragged.
90458      * @method
90459      */
90460     onDragOver: Ext.emptyFn,
90461     /**
90462      * @hide
90463      * Overridden and disabled. A DDTarget does not support being dragged.
90464      * @method
90465      */
90466     onInvalidDrop: Ext.emptyFn,
90467     /**
90468      * @hide
90469      * Overridden and disabled. A DDTarget does not support being dragged.
90470      * @method
90471      */
90472     onMouseDown: Ext.emptyFn,
90473     /**
90474      * @hide
90475      * Overridden and disabled. A DDTarget does not support being dragged.
90476      * @method
90477      */
90478     onMouseUp: Ext.emptyFn,
90479     /**
90480      * @hide
90481      * Overridden and disabled. A DDTarget does not support being dragged.
90482      * @method
90483      */
90484     setXConstraint: Ext.emptyFn,
90485     /**
90486      * @hide
90487      * Overridden and disabled. A DDTarget does not support being dragged.
90488      * @method
90489      */
90490     setYConstraint: Ext.emptyFn,
90491     /**
90492      * @hide
90493      * Overridden and disabled. A DDTarget does not support being dragged.
90494      * @method
90495      */
90496     resetConstraints: Ext.emptyFn,
90497     /**
90498      * @hide
90499      * Overridden and disabled. A DDTarget does not support being dragged.
90500      * @method
90501      */
90502     clearConstraints: Ext.emptyFn,
90503     /**
90504      * @hide
90505      * Overridden and disabled. A DDTarget does not support being dragged.
90506      * @method
90507      */
90508     clearTicks: Ext.emptyFn,
90509     /**
90510      * @hide
90511      * Overridden and disabled. A DDTarget does not support being dragged.
90512      * @method
90513      */
90514     setInitPosition: Ext.emptyFn,
90515     /**
90516      * @hide
90517      * Overridden and disabled. A DDTarget does not support being dragged.
90518      * @method
90519      */
90520     setDragElId: Ext.emptyFn,
90521     /**
90522      * @hide
90523      * Overridden and disabled. A DDTarget does not support being dragged.
90524      * @method
90525      */
90526     setHandleElId: Ext.emptyFn,
90527     /**
90528      * @hide
90529      * Overridden and disabled. A DDTarget does not support being dragged.
90530      * @method
90531      */
90532     setOuterHandleElId: Ext.emptyFn,
90533     /**
90534      * @hide
90535      * Overridden and disabled. A DDTarget does not support being dragged.
90536      * @method
90537      */
90538     addInvalidHandleClass: Ext.emptyFn,
90539     /**
90540      * @hide
90541      * Overridden and disabled. A DDTarget does not support being dragged.
90542      * @method
90543      */
90544     addInvalidHandleId: Ext.emptyFn,
90545     /**
90546      * @hide
90547      * Overridden and disabled. A DDTarget does not support being dragged.
90548      * @method
90549      */
90550     addInvalidHandleType: Ext.emptyFn,
90551     /**
90552      * @hide
90553      * Overridden and disabled. A DDTarget does not support being dragged.
90554      * @method
90555      */
90556     removeInvalidHandleClass: Ext.emptyFn,
90557     /**
90558      * @hide
90559      * Overridden and disabled. A DDTarget does not support being dragged.
90560      * @method
90561      */
90562     removeInvalidHandleId: Ext.emptyFn,
90563     /**
90564      * @hide
90565      * Overridden and disabled. A DDTarget does not support being dragged.
90566      * @method
90567      */
90568     removeInvalidHandleType: Ext.emptyFn,
90569
90570     toString: function() {
90571         return ("DDTarget " + this.id);
90572     }
90573 });
90574 /**
90575  * @class Ext.dd.DragTracker
90576  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
90577  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
90578  * an element that can be dragged around to change the Slider's value.
90579  * DragTracker provides a series of template methods that should be overridden to provide functionality
90580  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
90581  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
90582  */
90583 Ext.define('Ext.dd.DragTracker', {
90584
90585     uses: ['Ext.util.Region'],
90586
90587     mixins: {
90588         observable: 'Ext.util.Observable'
90589     },
90590
90591     /**
90592      * @property {Boolean} active
90593      * Read-only property indicated whether the user is currently dragging this
90594      * tracker.
90595      */
90596     active: false,
90597
90598     /**
90599      * @property {HTMLElement} dragTarget
90600      * <p><b>Only valid during drag operations. Read-only.</b></p>
90601      * <p>The element being dragged.</p>
90602      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
90603      */
90604
90605     /**
90606      * @cfg {Boolean} trackOver
90607      * <p>Defaults to <code>false</code>. Set to true to fire mouseover and mouseout events when the mouse enters or leaves the target element.</p>
90608      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
90609      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90610      */
90611     trackOver: false,
90612
90613     /**
90614      * @cfg {String} overCls
90615      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
90616      * when a delegate element) is mouseovered.</p>
90617      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90618      */
90619
90620     /**
90621      * @cfg {Ext.util.Region/Ext.Element} constrainTo
90622      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
90623      * the result of the {@link #getOffset} call.</p>
90624      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
90625      */
90626
90627     /**
90628      * @cfg {Number} tolerance
90629      * Number of pixels the drag target must be moved before dragging is
90630      * considered to have started. Defaults to <code>5</code>.
90631      */
90632     tolerance: 5,
90633
90634     /**
90635      * @cfg {Boolean/Number} autoStart
90636      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
90637      * Specify a Number for the number of milliseconds to defer trigger start.
90638      */
90639     autoStart: false,
90640
90641     /**
90642      * @cfg {String} delegate
90643      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
90644      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
90645      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
90646      */
90647
90648     /**
90649      * @cfg {Boolean} preventDefault
90650      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
90651      */
90652
90653     /**
90654      * @cfg {Boolean} stopEvent
90655      * Specify <code>true</code> to stop the <code>mousedown</code> event from bubbling to outer listeners from the target element (or its delegates). Defaults to <code>false</code>.
90656      */
90657
90658     constructor : function(config){
90659         Ext.apply(this, config);
90660         this.addEvents(
90661             /**
90662              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90663              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
90664              * used, when the mouse enters a delegate element).</p>
90665              * @param {Object} this
90666              * @param {Object} e event object
90667              * @param {HTMLElement} target The element mouseovered.
90668              */
90669             'mouseover',
90670
90671             /**
90672              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90673              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
90674              * used, when the mouse exits a delegate element).</p>
90675              * @param {Object} this
90676              * @param {Object} e event object
90677              */
90678             'mouseout',
90679
90680             /**
90681              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
90682              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
90683              * the {@link #autoStart} timer fires.</p>
90684              * <p>Return false to veto the drag operation.</p>
90685              * @param {Object} this
90686              * @param {Object} e event object
90687              */
90688             'mousedown',
90689
90690             /**
90691              * @event mouseup
90692              * @param {Object} this
90693              * @param {Object} e event object
90694              */
90695             'mouseup',
90696
90697             /**
90698              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
90699              * @param {Object} this
90700              * @param {Object} e event object
90701              */
90702             'mousemove',
90703
90704             /**
90705              * @event beforestart
90706              * @param {Object} this
90707              * @param {Object} e event object
90708              */
90709             'beforedragstart',
90710
90711             /**
90712              * @event dragstart
90713              * @param {Object} this
90714              * @param {Object} e event object
90715              */
90716             'dragstart',
90717
90718             /**
90719              * @event dragend
90720              * @param {Object} this
90721              * @param {Object} e event object
90722              */
90723             'dragend',
90724
90725             /**
90726              * @event drag
90727              * @param {Object} this
90728              * @param {Object} e event object
90729              */
90730             'drag'
90731         );
90732
90733         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
90734
90735         if (this.el) {
90736             this.initEl(this.el);
90737         }
90738
90739         // Dont pass the config so that it is not applied to 'this' again
90740         this.mixins.observable.constructor.call(this);
90741         if (this.disabled) {
90742             this.disable();
90743         }
90744
90745     },
90746
90747     /**
90748      * Initializes the DragTracker on a given element.
90749      * @param {Ext.Element/HTMLElement} el The element
90750      */
90751     initEl: function(el) {
90752         this.el = Ext.get(el);
90753
90754         // The delegate option may also be an element on which to listen
90755         this.handle = Ext.get(this.delegate);
90756
90757         // If delegate specified an actual element to listen on, we do not use the delegate listener option
90758         this.delegate = this.handle ? undefined : this.delegate;
90759
90760         if (!this.handle) {
90761             this.handle = this.el;
90762         }
90763
90764         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
90765         // We process mousedown to begin tracking.
90766         this.mon(this.handle, {
90767             mousedown: this.onMouseDown,
90768             delegate: this.delegate,
90769             scope: this
90770         });
90771
90772         // If configured to do so, track mouse entry and exit into the target (or delegate).
90773         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
90774         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
90775         if (this.trackOver || this.overCls) {
90776             this.mon(this.handle, {
90777                 mouseover: this.onMouseOver,
90778                 mouseout: this.onMouseOut,
90779                 delegate: this.delegate,
90780                 scope: this
90781             });
90782         }
90783     },
90784
90785     disable: function() {
90786         this.disabled = true;
90787     },
90788
90789     enable: function() {
90790         this.disabled = false;
90791     },
90792
90793     destroy : function() {
90794         this.clearListeners();
90795         delete this.el;
90796     },
90797
90798     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
90799     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
90800     onMouseOver: function(e, target) {
90801         var me = this;
90802         if (!me.disabled) {
90803             if (Ext.EventManager.contains(e) || me.delegate) {
90804                 me.mouseIsOut = false;
90805                 if (me.overCls) {
90806                     me.el.addCls(me.overCls);
90807                 }
90808                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
90809             }
90810         }
90811     },
90812
90813     // When the pointer exits a tracking element, fire a mouseout.
90814     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
90815     onMouseOut: function(e) {
90816         if (this.mouseIsDown) {
90817             this.mouseIsOut = true;
90818         } else {
90819             if (this.overCls) {
90820                 this.el.removeCls(this.overCls);
90821             }
90822             this.fireEvent('mouseout', this, e);
90823         }
90824     },
90825
90826     onMouseDown: function(e, target){
90827         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
90828         if (this.disabled ||e.dragTracked) {
90829             return;
90830         }
90831
90832         // This information should be available in mousedown listener and onBeforeStart implementations
90833         this.dragTarget = this.delegate ? target : this.handle.dom;
90834         this.startXY = this.lastXY = e.getXY();
90835         this.startRegion = Ext.fly(this.dragTarget).getRegion();
90836
90837         if (this.fireEvent('mousedown', this, e) === false ||
90838             this.fireEvent('beforedragstart', this, e) === false ||
90839             this.onBeforeStart(e) === false) {
90840             return;
90841         }
90842
90843         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
90844         // The onMouseOut method will only ever be called after mouseup.
90845         this.mouseIsDown = true;
90846
90847         // Flag for downstream DragTracker instances that the mouse is being tracked.
90848         e.dragTracked = true;
90849
90850         if (this.preventDefault !== false) {
90851             e.preventDefault();
90852         }
90853         Ext.getDoc().on({
90854             scope: this,
90855             mouseup: this.onMouseUp,
90856             mousemove: this.onMouseMove,
90857             selectstart: this.stopSelect
90858         });
90859         if (this.autoStart) {
90860             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
90861         }
90862     },
90863
90864     onMouseMove: function(e, target){
90865         // BrowserBug: IE hack to see if button was released outside of window.
90866         // Needed in IE6-9 in quirks and strictmode
90867         if (this.active && Ext.isIE && !e.browserEvent.button) {
90868             e.preventDefault();
90869             this.onMouseUp(e);
90870             return;
90871         }
90872
90873         e.preventDefault();
90874         var xy = e.getXY(),
90875             s = this.startXY;
90876
90877         this.lastXY = xy;
90878         if (!this.active) {
90879             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
90880                 this.triggerStart(e);
90881             } else {
90882                 return;
90883             }
90884         }
90885
90886         // Returning false from a mousemove listener deactivates
90887         if (this.fireEvent('mousemove', this, e) === false) {
90888             this.onMouseUp(e);
90889         } else {
90890             this.onDrag(e);
90891             this.fireEvent('drag', this, e);
90892         }
90893     },
90894
90895     onMouseUp: function(e) {
90896         // Clear the flag which ensures onMouseOut fires only after the mouse button
90897         // is lifted if the mouseout happens *during* a drag.
90898         this.mouseIsDown = false;
90899
90900         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
90901         if (this.mouseIsOut) {
90902             this.mouseIsOut = false;
90903             this.onMouseOut(e);
90904         }
90905         e.preventDefault();
90906         this.fireEvent('mouseup', this, e);
90907         this.endDrag(e);
90908     },
90909
90910     /**
90911      * @private
90912      * Stop the drag operation, and remove active mouse listeners.
90913      */
90914     endDrag: function(e) {
90915         var doc = Ext.getDoc(),
90916         wasActive = this.active;
90917
90918         doc.un('mousemove', this.onMouseMove, this);
90919         doc.un('mouseup', this.onMouseUp, this);
90920         doc.un('selectstart', this.stopSelect, this);
90921         this.clearStart();
90922         this.active = false;
90923         if (wasActive) {
90924             this.onEnd(e);
90925             this.fireEvent('dragend', this, e);
90926         }
90927         // Private property calculated when first required and only cached during a drag
90928         delete this._constrainRegion;
90929
90930         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
90931         delete Ext.EventObject.dragTracked;
90932     },
90933
90934     triggerStart: function(e) {
90935         this.clearStart();
90936         this.active = true;
90937         this.onStart(e);
90938         this.fireEvent('dragstart', this, e);
90939     },
90940
90941     clearStart : function() {
90942         if (this.timer) {
90943             clearTimeout(this.timer);
90944             delete this.timer;
90945         }
90946     },
90947
90948     stopSelect : function(e) {
90949         e.stopEvent();
90950         return false;
90951     },
90952
90953     /**
90954      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
90955      * holds the mouse button down. Return false to disallow the drag
90956      * @param {Ext.EventObject} e The event object
90957      * @template
90958      */
90959     onBeforeStart : function(e) {
90960
90961     },
90962
90963     /**
90964      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
90965      * (e.g. the user has moved the tracked element beyond the specified tolerance)
90966      * @param {Ext.EventObject} e The event object
90967      * @template
90968      */
90969     onStart : function(xy) {
90970
90971     },
90972
90973     /**
90974      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
90975      * @param {Ext.EventObject} e The event object
90976      * @template
90977      */
90978     onDrag : function(e) {
90979
90980     },
90981
90982     /**
90983      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
90984      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
90985      * @param {Ext.EventObject} e The event object
90986      * @template
90987      */
90988     onEnd : function(e) {
90989
90990     },
90991
90992     /**
90993      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
90994      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
90995      * {@link #delegate} selector.</p>
90996      * @return {Ext.Element} The element currently being tracked.
90997      */
90998     getDragTarget : function(){
90999         return this.dragTarget;
91000     },
91001
91002     /**
91003      * @private
91004      * @returns {Ext.Element} The DragTracker's encapsulating element.
91005      */
91006     getDragCt : function(){
91007         return this.el;
91008     },
91009
91010     /**
91011      * @private
91012      * Return the Region into which the drag operation is constrained.
91013      * Either the XY pointer itself can be constrained, or the dragTarget element
91014      * The private property _constrainRegion is cached until onMouseUp
91015      */
91016     getConstrainRegion: function() {
91017         if (this.constrainTo) {
91018             if (this.constrainTo instanceof Ext.util.Region) {
91019                 return this.constrainTo;
91020             }
91021             if (!this._constrainRegion) {
91022                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
91023             }
91024         } else {
91025             if (!this._constrainRegion) {
91026                 this._constrainRegion = this.getDragCt().getViewRegion();
91027             }
91028         }
91029         return this._constrainRegion;
91030     },
91031
91032     getXY : function(constrain){
91033         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
91034     },
91035
91036     /**
91037      * Returns the X, Y offset of the current mouse position from the mousedown point.
91038      *
91039      * This method may optionally constrain the real offset values, and returns a point coerced in one
91040      * of two modes:
91041      *
91042      *  - `point`
91043      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
91044      *  - `dragTarget`
91045      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
91046      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
91047      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
91048      *
91049      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
91050      * as `point` or `dragTarget`. See above.
91051      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
91052      */
91053     getOffset : function(constrain){
91054         var xy = this.getXY(constrain),
91055             s = this.startXY;
91056
91057         return [xy[0]-s[0], xy[1]-s[1]];
91058     },
91059
91060     constrainModes: {
91061         // Constrain the passed point to within the constrain region
91062         point: function(me, xy) {
91063             var dr = me.dragRegion,
91064                 constrainTo = me.getConstrainRegion();
91065
91066             // No constraint
91067             if (!constrainTo) {
91068                 return xy;
91069             }
91070
91071             dr.x = dr.left = dr[0] = dr.right = xy[0];
91072             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
91073             dr.constrainTo(constrainTo);
91074
91075             return [dr.left, dr.top];
91076         },
91077
91078         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
91079         dragTarget: function(me, xy) {
91080             var s = me.startXY,
91081                 dr = me.startRegion.copy(),
91082                 constrainTo = me.getConstrainRegion(),
91083                 adjust;
91084
91085             // No constraint
91086             if (!constrainTo) {
91087                 return xy;
91088             }
91089
91090             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
91091             // If it overflows, we constrain the passed XY to bring the potential
91092             // region back within the boundary.
91093             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
91094
91095             // Constrain the X coordinate by however much the dragTarget overflows
91096             if (dr.right > constrainTo.right) {
91097                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
91098                 dr.left += adjust;
91099             }
91100             if (dr.left < constrainTo.left) {
91101                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
91102             }
91103
91104             // Constrain the Y coordinate by however much the dragTarget overflows
91105             if (dr.bottom > constrainTo.bottom) {
91106                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
91107                 dr.top += adjust;
91108             }
91109             if (dr.top < constrainTo.top) {
91110                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
91111             }
91112             return xy;
91113         }
91114     }
91115 });
91116 /**
91117  * @class Ext.dd.DragZone
91118  * @extends Ext.dd.DragSource
91119  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
91120  * <p>This class does not move the drag target nodes, but a proxy element which may contain
91121  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
91122  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
91123  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
91124  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
91125  * is the most efficient way to "activate" those nodes.</p>
91126  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
91127  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
91128  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
91129  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
91130  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91131  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
91132  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
91133 myDataView.on('render', function(v) {
91134     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
91135
91136 //      On receipt of a mousedown event, see if it is within a DataView node.
91137 //      Return a drag data object if so.
91138         getDragData: function(e) {
91139
91140 //          Use the DataView's own itemSelector (a mandatory property) to
91141 //          test if the mousedown is within one of the DataView's nodes.
91142             var sourceEl = e.getTarget(v.itemSelector, 10);
91143
91144 //          If the mousedown is within a DataView node, clone the node to produce
91145 //          a ddel element for use by the drag proxy. Also add application data
91146 //          to the returned data object.
91147             if (sourceEl) {
91148                 d = sourceEl.cloneNode(true);
91149                 d.id = Ext.id();
91150                 return {
91151                     ddel: d,
91152                     sourceEl: sourceEl,
91153                     repairXY: Ext.fly(sourceEl).getXY(),
91154                     sourceStore: v.store,
91155                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
91156                 }
91157             }
91158         },
91159
91160 //      Provide coordinates for the proxy to slide back to on failed drag.
91161 //      This is the original XY coordinates of the draggable element captured
91162 //      in the getDragData method.
91163         getRepairXY: function() {
91164             return this.dragData.repairXY;
91165         }
91166     });
91167 });</code></pre>
91168  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
91169  * cooperates with this DragZone.
91170  */
91171 Ext.define('Ext.dd.DragZone', {
91172
91173     extend: 'Ext.dd.DragSource',
91174
91175     /**
91176      * Creates new DragZone.
91177      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91178      * @param {Object} config
91179      */
91180     constructor : function(el, config){
91181         this.callParent([el, config]);
91182         if (this.containerScroll) {
91183             Ext.dd.ScrollManager.register(this.el);
91184         }
91185     },
91186
91187     /**
91188      * This property contains the data representing the dragged object. This data is set up by the implementation
91189      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
91190      * any other data according to the application's needs.
91191      * @type Object
91192      * @property dragData
91193      */
91194
91195     /**
91196      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
91197      * for auto scrolling during drag operations.
91198      */
91199
91200     /**
91201      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
91202      * for a valid target to drag based on the mouse down. Override this method
91203      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
91204      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
91205      * @param {Event} e The mouse down event
91206      * @return {Object} The dragData
91207      */
91208     getDragData : function(e){
91209         return Ext.dd.Registry.getHandleFromEvent(e);
91210     },
91211
91212     /**
91213      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
91214      * this.dragData.ddel
91215      * @param {Number} x The x position of the click on the dragged object
91216      * @param {Number} y The y position of the click on the dragged object
91217      * @return {Boolean} true to continue the drag, false to cancel
91218      */
91219     onInitDrag : function(x, y){
91220         this.proxy.update(this.dragData.ddel.cloneNode(true));
91221         this.onStartDrag(x, y);
91222         return true;
91223     },
91224
91225     /**
91226      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
91227      */
91228     afterRepair : function(){
91229         var me = this;
91230         if (Ext.enableFx) {
91231             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
91232         }
91233         me.dragging = false;
91234     },
91235
91236     /**
91237      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
91238      * the XY of this.dragData.ddel
91239      * @param {Event} e The mouse up event
91240      * @return {Number[]} The xy location (e.g. [100, 200])
91241      */
91242     getRepairXY : function(e){
91243         return Ext.Element.fly(this.dragData.ddel).getXY();
91244     },
91245
91246     destroy : function(){
91247         this.callParent();
91248         if (this.containerScroll) {
91249             Ext.dd.ScrollManager.unregister(this.el);
91250         }
91251     }
91252 });
91253
91254 /**
91255  * @class Ext.dd.ScrollManager
91256  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
91257  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
91258  * but you can also override most of the configs per scroll container by adding a
91259  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
91260  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
91261  * <pre><code>
91262 var el = Ext.get('scroll-ct');
91263 el.ddScrollConfig = {
91264     vthresh: 50,
91265     hthresh: -1,
91266     frequency: 100,
91267     increment: 200
91268 };
91269 Ext.dd.ScrollManager.register(el);
91270 </code></pre>
91271  * Note: This class is designed to be used in "Point Mode
91272  * @singleton
91273  */
91274 Ext.define('Ext.dd.ScrollManager', {
91275     singleton: true,
91276     requires: [
91277         'Ext.dd.DragDropManager'
91278     ],
91279
91280     constructor: function() {
91281         var ddm = Ext.dd.DragDropManager;
91282         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
91283         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
91284         this.doScroll = Ext.Function.bind(this.doScroll, this);
91285         this.ddmInstance = ddm;
91286         this.els = {};
91287         this.dragEl = null;
91288         this.proc = {};
91289     },
91290
91291     onStop: function(e){
91292         var sm = Ext.dd.ScrollManager;
91293         sm.dragEl = null;
91294         sm.clearProc();
91295     },
91296
91297     triggerRefresh: function() {
91298         if (this.ddmInstance.dragCurrent) {
91299             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
91300         }
91301     },
91302
91303     doScroll: function() {
91304         if (this.ddmInstance.dragCurrent) {
91305             var proc   = this.proc,
91306                 procEl = proc.el,
91307                 ddScrollConfig = proc.el.ddScrollConfig,
91308                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
91309
91310             if (!this.animate) {
91311                 if (procEl.scroll(proc.dir, inc)) {
91312                     this.triggerRefresh();
91313                 }
91314             } else {
91315                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
91316             }
91317         }
91318     },
91319
91320     clearProc: function() {
91321         var proc = this.proc;
91322         if (proc.id) {
91323             clearInterval(proc.id);
91324         }
91325         proc.id = 0;
91326         proc.el = null;
91327         proc.dir = "";
91328     },
91329
91330     startProc: function(el, dir) {
91331         this.clearProc();
91332         this.proc.el = el;
91333         this.proc.dir = dir;
91334         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
91335             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
91336                   ? el.ddScrollConfig.frequency
91337                   : this.frequency;
91338
91339         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
91340             this.proc.id = setInterval(this.doScroll, freq);
91341         }
91342     },
91343
91344     onFire: function(e, isDrop) {
91345         if (isDrop || !this.ddmInstance.dragCurrent) {
91346             return;
91347         }
91348         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
91349             this.dragEl = this.ddmInstance.dragCurrent;
91350             // refresh regions on drag start
91351             this.refreshCache();
91352         }
91353
91354         var xy = e.getXY(),
91355             pt = e.getPoint(),
91356             proc = this.proc,
91357             els = this.els;
91358
91359         for (var id in els) {
91360             var el = els[id], r = el._region;
91361             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
91362             if (r && r.contains(pt) && el.isScrollable()) {
91363                 if (r.bottom - pt.y <= c.vthresh) {
91364                     if(proc.el != el){
91365                         this.startProc(el, "down");
91366                     }
91367                     return;
91368                 }else if (r.right - pt.x <= c.hthresh) {
91369                     if (proc.el != el) {
91370                         this.startProc(el, "left");
91371                     }
91372                     return;
91373                 } else if(pt.y - r.top <= c.vthresh) {
91374                     if (proc.el != el) {
91375                         this.startProc(el, "up");
91376                     }
91377                     return;
91378                 } else if(pt.x - r.left <= c.hthresh) {
91379                     if (proc.el != el) {
91380                         this.startProc(el, "right");
91381                     }
91382                     return;
91383                 }
91384             }
91385         }
91386         this.clearProc();
91387     },
91388
91389     /**
91390      * Registers new overflow element(s) to auto scroll
91391      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91392      * The id of or the element to be scrolled or an array of either
91393      */
91394     register : function(el){
91395         if (Ext.isArray(el)) {
91396             for(var i = 0, len = el.length; i < len; i++) {
91397                     this.register(el[i]);
91398             }
91399         } else {
91400             el = Ext.get(el);
91401             this.els[el.id] = el;
91402         }
91403     },
91404
91405     /**
91406      * Unregisters overflow element(s) so they are no longer scrolled
91407      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91408      * The id of or the element to be removed or an array of either
91409      */
91410     unregister : function(el){
91411         if(Ext.isArray(el)){
91412             for (var i = 0, len = el.length; i < len; i++) {
91413                 this.unregister(el[i]);
91414             }
91415         }else{
91416             el = Ext.get(el);
91417             delete this.els[el.id];
91418         }
91419     },
91420
91421     /**
91422      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
91423      * trigger scrolling
91424      * @type Number
91425      */
91426     vthresh : 25,
91427     /**
91428      * The number of pixels from the right or left edge of a container the pointer needs to be to
91429      * trigger scrolling
91430      * @type Number
91431      */
91432     hthresh : 25,
91433
91434     /**
91435      * The number of pixels to scroll in each scroll increment
91436      * @type Number
91437      */
91438     increment : 100,
91439
91440     /**
91441      * The frequency of scrolls in milliseconds
91442      * @type Number
91443      */
91444     frequency : 500,
91445
91446     /**
91447      * True to animate the scroll
91448      * @type Boolean
91449      */
91450     animate: true,
91451
91452     /**
91453      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
91454      * @type Number
91455      */
91456     animDuration: 0.4,
91457
91458     /**
91459      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
91460      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
91461      * @type String
91462      */
91463     ddGroup: undefined,
91464
91465     /**
91466      * Manually trigger a cache refresh.
91467      */
91468     refreshCache : function(){
91469         var els = this.els,
91470             id;
91471         for (id in els) {
91472             if(typeof els[id] == 'object'){ // for people extending the object prototype
91473                 els[id]._region = els[id].getRegion();
91474             }
91475         }
91476     }
91477 });
91478
91479 /**
91480  * @class Ext.dd.DropTarget
91481  * @extends Ext.dd.DDTarget
91482  * A simple class that provides the basic implementation needed to make any element a drop target that can have
91483  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
91484  */
91485 Ext.define('Ext.dd.DropTarget', {
91486     extend: 'Ext.dd.DDTarget',
91487     requires: ['Ext.dd.ScrollManager'],
91488
91489     /**
91490      * Creates new DropTarget.
91491      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91492      * @param {Object} config
91493      */
91494     constructor : function(el, config){
91495         this.el = Ext.get(el);
91496
91497         Ext.apply(this, config);
91498
91499         if(this.containerScroll){
91500             Ext.dd.ScrollManager.register(this.el);
91501         }
91502
91503         this.callParent([this.el.dom, this.ddGroup || this.group,
91504               {isTarget: true}]);
91505     },
91506
91507     /**
91508      * @cfg {String} ddGroup
91509      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
91510      * interact with other drag drop objects in the same group.
91511      */
91512     /**
91513      * @cfg {String} [overClass=""]
91514      * The CSS class applied to the drop target element while the drag source is over it.
91515      */
91516     /**
91517      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
91518      * The CSS class returned to the drag source when drop is allowed.
91519      */
91520     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
91521     /**
91522      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
91523      * The CSS class returned to the drag source when drop is not allowed.
91524      */
91525     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
91526
91527     // private
91528     isTarget : true,
91529
91530     // private
91531     isNotifyTarget : true,
91532
91533     /**
91534      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
91535      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
91536      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
91537      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91538      * @param {Event} e The event
91539      * @param {Object} data An object containing arbitrary data supplied by the drag source
91540      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91541      * underlying {@link Ext.dd.StatusProxy} can be updated
91542      */
91543     notifyEnter : function(dd, e, data){
91544         if(this.overClass){
91545             this.el.addCls(this.overClass);
91546         }
91547         return this.dropAllowed;
91548     },
91549
91550     /**
91551      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
91552      * This method will be called on every mouse movement while the drag source is over the drop target.
91553      * This default implementation simply returns the dropAllowed config value.
91554      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91555      * @param {Event} e The event
91556      * @param {Object} data An object containing arbitrary data supplied by the drag source
91557      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91558      * underlying {@link Ext.dd.StatusProxy} can be updated
91559      */
91560     notifyOver : function(dd, e, data){
91561         return this.dropAllowed;
91562     },
91563
91564     /**
91565      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
91566      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
91567      * overClass (if any) from the drop element.
91568      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91569      * @param {Event} e The event
91570      * @param {Object} data An object containing arbitrary data supplied by the drag source
91571      */
91572     notifyOut : function(dd, e, data){
91573         if(this.overClass){
91574             this.el.removeCls(this.overClass);
91575         }
91576     },
91577
91578     /**
91579      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
91580      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
91581      * implementation that does something to process the drop event and returns true so that the drag source's
91582      * repair action does not run.
91583      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91584      * @param {Event} e The event
91585      * @param {Object} data An object containing arbitrary data supplied by the drag source
91586      * @return {Boolean} False if the drop was invalid.
91587      */
91588     notifyDrop : function(dd, e, data){
91589         return false;
91590     },
91591
91592     destroy : function(){
91593         this.callParent();
91594         if(this.containerScroll){
91595             Ext.dd.ScrollManager.unregister(this.el);
91596         }
91597     }
91598 });
91599
91600 /**
91601  * @class Ext.dd.Registry
91602  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
91603  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
91604  * @singleton
91605  */
91606 Ext.define('Ext.dd.Registry', {
91607     singleton: true,
91608     constructor: function() {
91609         this.elements = {}; 
91610         this.handles = {}; 
91611         this.autoIdSeed = 0;
91612     },
91613     
91614     getId: function(el, autogen){
91615         if(typeof el == "string"){
91616             return el;
91617         }
91618         var id = el.id;
91619         if(!id && autogen !== false){
91620             id = "extdd-" + (++this.autoIdSeed);
91621             el.id = id;
91622         }
91623         return id;
91624     },
91625     
91626     /**
91627      * Resgister a drag drop element
91628      * @param {String/HTMLElement} element The id or DOM node to register
91629      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
91630      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
91631      * knows how to interpret, plus there are some specific properties known to the Registry that should be
91632      * populated in the data object (if applicable):
91633      * <pre>
91634 Value      Description<br />
91635 ---------  ------------------------------------------<br />
91636 handles    Array of DOM nodes that trigger dragging<br />
91637            for the element being registered<br />
91638 isHandle   True if the element passed in triggers<br />
91639            dragging itself, else false
91640 </pre>
91641      */
91642     register : function(el, data){
91643         data = data || {};
91644         if (typeof el == "string") {
91645             el = document.getElementById(el);
91646         }
91647         data.ddel = el;
91648         this.elements[this.getId(el)] = data;
91649         if (data.isHandle !== false) {
91650             this.handles[data.ddel.id] = data;
91651         }
91652         if (data.handles) {
91653             var hs = data.handles;
91654             for (var i = 0, len = hs.length; i < len; i++) {
91655                 this.handles[this.getId(hs[i])] = data;
91656             }
91657         }
91658     },
91659
91660     /**
91661      * Unregister a drag drop element
91662      * @param {String/HTMLElement} element The id or DOM node to unregister
91663      */
91664     unregister : function(el){
91665         var id = this.getId(el, false);
91666         var data = this.elements[id];
91667         if(data){
91668             delete this.elements[id];
91669             if(data.handles){
91670                 var hs = data.handles;
91671                 for (var i = 0, len = hs.length; i < len; i++) {
91672                     delete this.handles[this.getId(hs[i], false)];
91673                 }
91674             }
91675         }
91676     },
91677
91678     /**
91679      * Returns the handle registered for a DOM Node by id
91680      * @param {String/HTMLElement} id The DOM node or id to look up
91681      * @return {Object} handle The custom handle data
91682      */
91683     getHandle : function(id){
91684         if(typeof id != "string"){ // must be element?
91685             id = id.id;
91686         }
91687         return this.handles[id];
91688     },
91689
91690     /**
91691      * Returns the handle that is registered for the DOM node that is the target of the event
91692      * @param {Event} e The event
91693      * @return {Object} handle The custom handle data
91694      */
91695     getHandleFromEvent : function(e){
91696         var t = e.getTarget();
91697         return t ? this.handles[t.id] : null;
91698     },
91699
91700     /**
91701      * Returns a custom data object that is registered for a DOM node by id
91702      * @param {String/HTMLElement} id The DOM node or id to look up
91703      * @return {Object} data The custom data
91704      */
91705     getTarget : function(id){
91706         if(typeof id != "string"){ // must be element?
91707             id = id.id;
91708         }
91709         return this.elements[id];
91710     },
91711
91712     /**
91713      * Returns a custom data object that is registered for the DOM node that is the target of the event
91714      * @param {Event} e The event
91715      * @return {Object} data The custom data
91716      */
91717     getTargetFromEvent : function(e){
91718         var t = e.getTarget();
91719         return t ? this.elements[t.id] || this.handles[t.id] : null;
91720     }
91721 });
91722 /**
91723  * @class Ext.dd.DropZone
91724  * @extends Ext.dd.DropTarget
91725
91726 This class provides a container DD instance that allows dropping on multiple child target nodes.
91727
91728 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
91729 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
91730 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
91731 mouse event to see if it has taken place within an element, or class of elements. This is easily done
91732 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91733 {@link Ext.DomQuery} selector.
91734
91735 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
91736 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
91737 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
91738 of these methods to provide application-specific behaviour for these events to update both
91739 application state, and UI state.
91740
91741 For example to make a GridPanel a cooperating target with the example illustrated in
91742 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
91743
91744     myGridPanel.on('render', function() {
91745         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
91746
91747             // If the mouse is over a grid row, return that node. This is
91748             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
91749             getTargetFromEvent: function(e) {
91750                 return e.getTarget(myGridPanel.getView().rowSelector);
91751             },
91752
91753             // On entry into a target node, highlight that node.
91754             onNodeEnter : function(target, dd, e, data){ 
91755                 Ext.fly(target).addCls('my-row-highlight-class');
91756             },
91757
91758             // On exit from a target node, unhighlight that node.
91759             onNodeOut : function(target, dd, e, data){ 
91760                 Ext.fly(target).removeCls('my-row-highlight-class');
91761             },
91762
91763             // While over a target node, return the default drop allowed class which
91764             // places a "tick" icon into the drag proxy.
91765             onNodeOver : function(target, dd, e, data){ 
91766                 return Ext.dd.DropZone.prototype.dropAllowed;
91767             },
91768
91769             // On node drop we can interrogate the target to find the underlying
91770             // application object that is the real target of the dragged data.
91771             // In this case, it is a Record in the GridPanel's Store.
91772             // We can use the data set up by the DragZone's getDragData method to read
91773             // any data we decided to attach in the DragZone's getDragData method.
91774             onNodeDrop : function(target, dd, e, data){
91775                 var rowIndex = myGridPanel.getView().findRowIndex(target);
91776                 var r = myGridPanel.getStore().getAt(rowIndex);
91777                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
91778                     ' on Record id ' + r.id);
91779                 return true;
91780             }
91781         });
91782     }
91783
91784 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
91785 cooperates with this DropZone.
91786
91787  * @markdown
91788  */
91789 Ext.define('Ext.dd.DropZone', {
91790     extend: 'Ext.dd.DropTarget',
91791     requires: ['Ext.dd.Registry'],
91792
91793     /**
91794      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
91795      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
91796      * provide your own custom lookup.
91797      * @param {Event} e The event
91798      * @return {Object} data The custom data
91799      */
91800     getTargetFromEvent : function(e){
91801         return Ext.dd.Registry.getTargetFromEvent(e);
91802     },
91803
91804     /**
91805      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
91806      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91807      * This method has no default implementation and should be overridden to provide
91808      * node-specific processing if necessary.
91809      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
91810      * {@link #getTargetFromEvent} for this node)
91811      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91812      * @param {Event} e The event
91813      * @param {Object} data An object containing arbitrary data supplied by the drag source
91814      */
91815     onNodeEnter : function(n, dd, e, data){
91816         
91817     },
91818
91819     /**
91820      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
91821      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91822      * The default implementation returns this.dropNotAllowed, so it should be
91823      * overridden to provide the proper feedback.
91824      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91825      * {@link #getTargetFromEvent} for this node)
91826      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91827      * @param {Event} e The event
91828      * @param {Object} data An object containing arbitrary data supplied by the drag source
91829      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91830      * underlying {@link Ext.dd.StatusProxy} can be updated
91831      */
91832     onNodeOver : function(n, dd, e, data){
91833         return this.dropAllowed;
91834     },
91835
91836     /**
91837      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
91838      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
91839      * node-specific processing if necessary.
91840      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91841      * {@link #getTargetFromEvent} for this node)
91842      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91843      * @param {Event} e The event
91844      * @param {Object} data An object containing arbitrary data supplied by the drag source
91845      */
91846     onNodeOut : function(n, dd, e, data){
91847         
91848     },
91849
91850     /**
91851      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
91852      * the drop node.  The default implementation returns false, so it should be overridden to provide the
91853      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
91854      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91855      * {@link #getTargetFromEvent} for this node)
91856      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91857      * @param {Event} e The event
91858      * @param {Object} data An object containing arbitrary data supplied by the drag source
91859      * @return {Boolean} True if the drop was valid, else false
91860      */
91861     onNodeDrop : function(n, dd, e, data){
91862         return false;
91863     },
91864
91865     /**
91866      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
91867      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
91868      * it should be overridden to provide the proper feedback if necessary.
91869      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91870      * @param {Event} e The event
91871      * @param {Object} data An object containing arbitrary data supplied by the drag source
91872      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91873      * underlying {@link Ext.dd.StatusProxy} can be updated
91874      */
91875     onContainerOver : function(dd, e, data){
91876         return this.dropNotAllowed;
91877     },
91878
91879     /**
91880      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
91881      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
91882      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
91883      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
91884      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91885      * @param {Event} e The event
91886      * @param {Object} data An object containing arbitrary data supplied by the drag source
91887      * @return {Boolean} True if the drop was valid, else false
91888      */
91889     onContainerDrop : function(dd, e, data){
91890         return false;
91891     },
91892
91893     /**
91894      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
91895      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
91896      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
91897      * you should override this method and provide a custom implementation.
91898      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91899      * @param {Event} e The event
91900      * @param {Object} data An object containing arbitrary data supplied by the drag source
91901      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91902      * underlying {@link Ext.dd.StatusProxy} can be updated
91903      */
91904     notifyEnter : function(dd, e, data){
91905         return this.dropNotAllowed;
91906     },
91907
91908     /**
91909      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
91910      * This method will be called on every mouse movement while the drag source is over the drop zone.
91911      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
91912      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
91913      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
91914      * registered node, it will call {@link #onContainerOver}.
91915      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91916      * @param {Event} e The event
91917      * @param {Object} data An object containing arbitrary data supplied by the drag source
91918      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91919      * underlying {@link Ext.dd.StatusProxy} can be updated
91920      */
91921     notifyOver : function(dd, e, data){
91922         var n = this.getTargetFromEvent(e);
91923         if(!n) { // not over valid drop target
91924             if(this.lastOverNode){
91925                 this.onNodeOut(this.lastOverNode, dd, e, data);
91926                 this.lastOverNode = null;
91927             }
91928             return this.onContainerOver(dd, e, data);
91929         }
91930         if(this.lastOverNode != n){
91931             if(this.lastOverNode){
91932                 this.onNodeOut(this.lastOverNode, dd, e, data);
91933             }
91934             this.onNodeEnter(n, dd, e, data);
91935             this.lastOverNode = n;
91936         }
91937         return this.onNodeOver(n, dd, e, data);
91938     },
91939
91940     /**
91941      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
91942      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
91943      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
91944      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91945      * @param {Event} e The event
91946      * @param {Object} data An object containing arbitrary data supplied by the drag zone
91947      */
91948     notifyOut : function(dd, e, data){
91949         if(this.lastOverNode){
91950             this.onNodeOut(this.lastOverNode, dd, e, data);
91951             this.lastOverNode = null;
91952         }
91953     },
91954
91955     /**
91956      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
91957      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
91958      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
91959      * otherwise it will call {@link #onContainerDrop}.
91960      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91961      * @param {Event} e The event
91962      * @param {Object} data An object containing arbitrary data supplied by the drag source
91963      * @return {Boolean} False if the drop was invalid.
91964      */
91965     notifyDrop : function(dd, e, data){
91966         if(this.lastOverNode){
91967             this.onNodeOut(this.lastOverNode, dd, e, data);
91968             this.lastOverNode = null;
91969         }
91970         var n = this.getTargetFromEvent(e);
91971         return n ?
91972             this.onNodeDrop(n, dd, e, data) :
91973             this.onContainerDrop(dd, e, data);
91974     },
91975
91976     // private
91977     triggerCacheRefresh : function() {
91978         Ext.dd.DDM.refreshCache(this.groups);
91979     }
91980 });
91981 /**
91982  * @class Ext.flash.Component
91983  * @extends Ext.Component
91984  *
91985  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
91986  * in layout like any other Component.
91987  *
91988  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
91989  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
91990  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
91991  * and then simply import it into the head of your HTML document:
91992  *
91993  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
91994  *
91995  * ## Configuration
91996  *
91997  * This component allows several options for configuring how the target Flash movie is embedded. The most
91998  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
91999  * configurations include:
92000  *
92001  * - {@link #backgroundColor}
92002  * - {@link #wmode}
92003  * - {@link #flashVars}
92004  * - {@link #flashParams}
92005  * - {@link #flashAttributes}
92006  *
92007  * ## Example usage:
92008  *
92009  *     var win = Ext.widget('window', {
92010  *         title: "It's a tiger!",
92011  *         layout: 'fit',
92012  *         width: 300,
92013  *         height: 300,
92014  *         x: 20,
92015  *         y: 20,
92016  *         resizable: true,
92017  *         items: {
92018  *             xtype: 'flash',
92019  *             url: 'tiger.swf'
92020  *         }
92021  *     });
92022  *     win.show();
92023  *
92024  * ## Express Install
92025  *
92026  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
92027  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
92028  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
92029  *
92030  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
92031  *
92032  * @docauthor Jason Johnston <jason@sencha.com>
92033  */
92034 Ext.define('Ext.flash.Component', {
92035     extend: 'Ext.Component',
92036     alternateClassName: 'Ext.FlashComponent',
92037     alias: 'widget.flash',
92038
92039     /**
92040      * @cfg {String} flashVersion
92041      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
92042      */
92043     flashVersion : '9.0.115',
92044
92045     /**
92046      * @cfg {String} backgroundColor
92047      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
92048      */
92049     backgroundColor: '#ffffff',
92050
92051     /**
92052      * @cfg {String} wmode
92053      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
92054      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
92055      * movie transparent.
92056      */
92057     wmode: 'opaque',
92058
92059     /**
92060      * @cfg {Object} flashVars
92061      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
92062      */
92063
92064     /**
92065      * @cfg {Object} flashParams
92066      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
92067      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
92068      */
92069
92070     /**
92071      * @cfg {Object} flashAttributes
92072      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
92073      */
92074
92075     /**
92076      * @cfg {String} url
92077      * The URL of the SWF file to include. Required.
92078      */
92079
92080     /**
92081      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
92082      * so that the movie matches the width of the component.
92083      */
92084     swfWidth: '100%',
92085
92086     /**
92087      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
92088      * so that the movie matches the height of the component.
92089      */
92090     swfHeight: '100%',
92091
92092     /**
92093      * @cfg {Boolean} expressInstall
92094      * True to prompt the user to install flash if not installed. Note that this uses
92095      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
92096      */
92097     expressInstall: false,
92098
92099     /**
92100      * @property swf
92101      * @type {Ext.Element}
92102      * A reference to the object or embed element into which the SWF file is loaded. Only
92103      * populated after the component is rendered and the SWF has been successfully embedded.
92104      */
92105
92106     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
92107     renderTpl: ['<div id="{swfId}"></div>'],
92108
92109     initComponent: function() {
92110
92111         this.callParent();
92112         this.addEvents(
92113             /**
92114              * @event success
92115              * Fired when the Flash movie has been successfully embedded
92116              * @param {Ext.flash.Component} this
92117              */
92118             'success',
92119
92120             /**
92121              * @event failure
92122              * Fired when the Flash movie embedding fails
92123              * @param {Ext.flash.Component} this
92124              */
92125             'failure'
92126         );
92127     },
92128
92129     onRender: function() {
92130         var me = this,
92131             params, vars, undef,
92132             swfId = me.getSwfId();
92133
92134         me.renderData.swfId = swfId;
92135
92136         me.callParent(arguments);
92137
92138         params = Ext.apply({
92139             allowScriptAccess: 'always',
92140             bgcolor: me.backgroundColor,
92141             wmode: me.wmode
92142         }, me.flashParams);
92143
92144         vars = Ext.apply({
92145             allowedDomain: document.location.hostname
92146         }, me.flashVars);
92147
92148         new swfobject.embedSWF(
92149             me.url,
92150             swfId,
92151             me.swfWidth,
92152             me.swfHeight,
92153             me.flashVersion,
92154             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
92155             vars,
92156             params,
92157             me.flashAttributes,
92158             Ext.bind(me.swfCallback, me)
92159         );
92160     },
92161
92162     /**
92163      * @private
92164      * The callback method for handling an embedding success or failure by SWFObject
92165      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
92166      */
92167     swfCallback: function(e) {
92168         var me = this;
92169         if (e.success) {
92170             me.swf = Ext.get(e.ref);
92171             me.onSuccess();
92172             me.fireEvent('success', me);
92173         } else {
92174             me.onFailure();
92175             me.fireEvent('failure', me);
92176         }
92177     },
92178
92179     /**
92180      * Retrieve the id of the SWF object/embed element
92181      */
92182     getSwfId: function() {
92183         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
92184     },
92185
92186     onSuccess: function() {
92187         // swfobject forces visiblity:visible on the swf element, which prevents it 
92188         // from getting hidden when an ancestor is given visibility:hidden.
92189         this.swf.setStyle('visibility', 'inherit');
92190     },
92191
92192     onFailure: Ext.emptyFn,
92193
92194     beforeDestroy: function() {
92195         var me = this,
92196             swf = me.swf;
92197         if (swf) {
92198             swfobject.removeSWF(me.getSwfId());
92199             Ext.destroy(swf);
92200             delete me.swf;
92201         }
92202         me.callParent();
92203     },
92204
92205     statics: {
92206         /**
92207          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
92208          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
92209          * @static
92210          * @type String
92211          */
92212         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
92213     }
92214 });
92215
92216 /**
92217  * @class Ext.form.action.Action
92218  * @extends Ext.Base
92219  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
92220  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92221  * the Form needs to perform an action such as submit or load. The Configuration options
92222  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
92223  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
92224  * <p>The instance of Action which performed the action is passed to the success
92225  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
92226  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
92227  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
92228  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
92229  */
92230 Ext.define('Ext.form.action.Action', {
92231     alternateClassName: 'Ext.form.Action',
92232
92233     /**
92234      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
92235      * is invoking this Action. Required.
92236      */
92237
92238     /**
92239      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
92240      * configured on the {@link #form}.
92241      */
92242
92243     /**
92244      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
92245      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
92246      * before the {@link #success} callback is called and before the Form's
92247      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
92248      */
92249
92250     /**
92251      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
92252      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
92253      */
92254
92255     /**
92256      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
92257      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
92258      * input fields.</p>
92259      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92260      */
92261
92262     /**
92263      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
92264      * {@link Ext.data.proxy.Ajax#headers}.</p>
92265      */
92266
92267     /**
92268      * @cfg {Number} timeout The number of seconds to wait for a server response before
92269      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
92270      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
92271      * {@link #form}.
92272      */
92273
92274     /**
92275      * @cfg {Function} success The function to call when a valid success return packet is received.
92276      * The function is passed the following parameters:<ul class="mdetail-params">
92277      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92278      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
92279      * property of this object may be examined to perform custom postprocessing.</div></li>
92280      * </ul>
92281      */
92282
92283     /**
92284      * @cfg {Function} failure The function to call when a failure packet was received, or when an
92285      * error ocurred in the Ajax communication.
92286      * The function is passed the following parameters:<ul class="mdetail-params">
92287      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92288      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
92289      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
92290      * property of this object may be examined to perform custom postprocessing.</div></li>
92291      * </ul>
92292      */
92293
92294     /**
92295      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
92296      * callback functions (the <tt>this</tt> reference for the callback functions).
92297      */
92298
92299     /**
92300      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
92301      * during the time the action is being processed.
92302      */
92303
92304     /**
92305      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
92306      * during the time the action is being processed.
92307      */
92308
92309     /**
92310      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
92311      * when it is submitted. Defaults to <tt>true</tt>.
92312      */
92313     submitEmptyText : true,
92314     /**
92315      * @property type
92316      * The type of action this Action instance performs.
92317      * Currently only "submit" and "load" are supported.
92318      * @type {String}
92319      */
92320
92321     /**
92322      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
92323      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
92324      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
92325      * <pre><code>
92326 var fp = new Ext.form.Panel({
92327 ...
92328 buttons: [{
92329     text: 'Save',
92330     formBind: true,
92331     handler: function(){
92332         if(fp.getForm().isValid()){
92333             fp.getForm().submit({
92334                 url: 'form-submit.php',
92335                 waitMsg: 'Submitting your data...',
92336                 success: function(form, action){
92337                     // server responded with success = true
92338                     var result = action.{@link #result};
92339                 },
92340                 failure: function(form, action){
92341                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
92342                         Ext.Msg.alert('Error',
92343                             'Status:'+action.{@link #response}.status+': '+
92344                             action.{@link #response}.statusText);
92345                     }
92346                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
92347                         // server responded with success = false
92348                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
92349                     }
92350                 }
92351             });
92352         }
92353     }
92354 },{
92355     text: 'Reset',
92356     handler: function(){
92357         fp.getForm().reset();
92358     }
92359 }]
92360      * </code></pre>
92361      * @property failureType
92362      * @type {String}
92363      */
92364
92365     /**
92366      * The raw XMLHttpRequest object used to perform the action.
92367      * @property response
92368      * @type {Object}
92369      */
92370
92371     /**
92372      * The decoded response object containing a boolean <tt>success</tt> property and
92373      * other, action-specific properties.
92374      * @property result
92375      * @type {Object}
92376      */
92377
92378     /**
92379      * Creates new Action.
92380      * @param {Object} config (optional) Config object.
92381      */
92382     constructor: function(config) {
92383         if (config) {
92384             Ext.apply(this, config);
92385         }
92386
92387         // Normalize the params option to an Object
92388         var params = config.params;
92389         if (Ext.isString(params)) {
92390             this.params = Ext.Object.fromQueryString(params);
92391         }
92392     },
92393
92394     /**
92395      * Invokes this action using the current configuration.
92396      */
92397     run: Ext.emptyFn,
92398
92399     /**
92400      * @private
92401      * @method onSuccess
92402      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
92403      * @param {Object} response
92404      */
92405
92406     /**
92407      * @private
92408      * @method handleResponse
92409      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
92410      * @param {Object} response
92411      */
92412
92413     /**
92414      * @private
92415      * Handles a failure response.
92416      * @param {Object} response
92417      */
92418     onFailure : function(response){
92419         this.response = response;
92420         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
92421         this.form.afterAction(this, false);
92422     },
92423
92424     /**
92425      * @private
92426      * Validates that a response contains either responseText or responseXML and invokes
92427      * {@link #handleResponse} to build the result object.
92428      * @param {Object} response The raw response object.
92429      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
92430      *                         the response had empty responseText and responseXML.
92431      */
92432     processResponse : function(response){
92433         this.response = response;
92434         if (!response.responseText && !response.responseXML) {
92435             return true;
92436         }
92437         return (this.result = this.handleResponse(response));
92438     },
92439
92440     /**
92441      * @private
92442      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
92443      * @return {String} The URL.
92444      */
92445     getUrl: function() {
92446         return this.url || this.form.url;
92447     },
92448
92449     /**
92450      * @private
92451      * Determine the HTTP method to be used for the request.
92452      * @return {String} The HTTP method
92453      */
92454     getMethod: function() {
92455         return (this.method || this.form.method || 'POST').toUpperCase();
92456     },
92457
92458     /**
92459      * @private
92460      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
92461      * Items in params override items of the same name in baseParams.
92462      * @return {Object} the full set of parameters
92463      */
92464     getParams: function() {
92465         return Ext.apply({}, this.params, this.form.baseParams);
92466     },
92467
92468     /**
92469      * @private
92470      * Creates a callback object.
92471      */
92472     createCallback: function() {
92473         var me = this,
92474             undef,
92475             form = me.form;
92476         return {
92477             success: me.onSuccess,
92478             failure: me.onFailure,
92479             scope: me,
92480             timeout: (this.timeout * 1000) || (form.timeout * 1000),
92481             upload: form.fileUpload ? me.onSuccess : undef
92482         };
92483     },
92484
92485     statics: {
92486         /**
92487          * @property CLIENT_INVALID
92488          * Failure type returned when client side validation of the Form fails
92489          * thus aborting a submit action. Client side validation is performed unless
92490          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
92491          * @type {String}
92492          * @static
92493          */
92494         CLIENT_INVALID: 'client',
92495
92496         /**
92497          * @property SERVER_INVALID
92498          * <p>Failure type returned when server side processing fails and the {@link #result}'s
92499          * <tt>success</tt> property is set to <tt>false</tt>.</p>
92500          * <p>In the case of a form submission, field-specific error messages may be returned in the
92501          * {@link #result}'s <tt>errors</tt> property.</p>
92502          * @type {String}
92503          * @static
92504          */
92505         SERVER_INVALID: 'server',
92506
92507         /**
92508          * @property CONNECT_FAILURE
92509          * Failure type returned when a communication error happens when attempting
92510          * to send a request to the remote server. The {@link #response} may be examined to
92511          * provide further information.
92512          * @type {String}
92513          * @static
92514          */
92515         CONNECT_FAILURE: 'connect',
92516
92517         /**
92518          * @property LOAD_FAILURE
92519          * Failure type returned when the response's <tt>success</tt>
92520          * property is set to <tt>false</tt>, or no field values are returned in the response's
92521          * <tt>data</tt> property.
92522          * @type {String}
92523          * @static
92524          */
92525         LOAD_FAILURE: 'load'
92526
92527
92528     }
92529 });
92530
92531 /**
92532  * @class Ext.form.action.Submit
92533  * @extends Ext.form.action.Action
92534  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
92535  * and processes the returned response.</p>
92536  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92537  * {@link Ext.form.Basic#submit submit}ting.</p>
92538  * <p><u><b>Response Packet Criteria</b></u></p>
92539  * <p>A response packet may contain:
92540  * <div class="mdetail-params"><ul>
92541  * <li><b><code>success</code></b> property : Boolean
92542  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
92543  * <li><b><code>errors</code></b> property : Object
92544  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
92545  * which is optional, contains error messages for invalid fields.</div></li>
92546  * </ul></div>
92547  * <p><u><b>JSON Packets</b></u></p>
92548  * <p>By default, response packets are assumed to be JSON, so a typical response
92549  * packet may look like this:</p><pre><code>
92550 {
92551     success: false,
92552     errors: {
92553         clientCode: "Client not found",
92554         portOfLoading: "This field must not be null"
92555     }
92556 }</code></pre>
92557  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
92558  * or event handler methods. The object decoded from this JSON is available in the
92559  * {@link Ext.form.action.Action#result result} property.</p>
92560  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
92561     errorReader: new Ext.data.reader.Xml({
92562             record : 'field',
92563             success: '@success'
92564         }, [
92565             'id', 'msg'
92566         ]
92567     )
92568 </code></pre>
92569  * <p>then the results may be sent back in XML format:</p><pre><code>
92570 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
92571 &lt;message success="false"&gt;
92572 &lt;errors&gt;
92573     &lt;field&gt;
92574         &lt;id&gt;clientCode&lt;/id&gt;
92575         &lt;msg&gt;&lt;![CDATA[Code not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
92576     &lt;/field&gt;
92577     &lt;field&gt;
92578         &lt;id&gt;portOfLoading&lt;/id&gt;
92579         &lt;msg&gt;&lt;![CDATA[Port not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
92580     &lt;/field&gt;
92581 &lt;/errors&gt;
92582 &lt;/message&gt;
92583 </code></pre>
92584  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
92585  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
92586  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
92587  */
92588 Ext.define('Ext.form.action.Submit', {
92589     extend:'Ext.form.action.Action',
92590     alternateClassName: 'Ext.form.Action.Submit',
92591     alias: 'formaction.submit',
92592
92593     type: 'submit',
92594
92595     /**
92596      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
92597      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
92598      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
92599      */
92600
92601     // inherit docs
92602     run : function(){
92603         var form = this.form;
92604         if (this.clientValidation === false || form.isValid()) {
92605             this.doSubmit();
92606         } else {
92607             // client validation failed
92608             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
92609             form.afterAction(this, false);
92610         }
92611     },
92612
92613     /**
92614      * @private
92615      * Perform the submit of the form data.
92616      */
92617     doSubmit: function() {
92618         var formEl,
92619             ajaxOptions = Ext.apply(this.createCallback(), {
92620                 url: this.getUrl(),
92621                 method: this.getMethod(),
92622                 headers: this.headers
92623             });
92624
92625         // For uploads we need to create an actual form that contains the file upload fields,
92626         // and pass that to the ajax call so it can do its iframe-based submit method.
92627         if (this.form.hasUpload()) {
92628             formEl = ajaxOptions.form = this.buildForm();
92629             ajaxOptions.isUpload = true;
92630         } else {
92631             ajaxOptions.params = this.getParams();
92632         }
92633
92634         Ext.Ajax.request(ajaxOptions);
92635
92636         if (formEl) {
92637             Ext.removeNode(formEl);
92638         }
92639     },
92640
92641     /**
92642      * @private
92643      * Build the full set of parameters from the field values plus any additional configured params.
92644      */
92645     getParams: function() {
92646         var nope = false,
92647             configParams = this.callParent(),
92648             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
92649         return Ext.apply({}, fieldParams, configParams);
92650     },
92651
92652     /**
92653      * @private
92654      * Build a form element containing fields corresponding to all the parameters to be
92655      * submitted (everything returned by {@link #getParams}.
92656      * NOTE: the form element is automatically added to the DOM, so any code that uses
92657      * it must remove it from the DOM after finishing with it.
92658      * @return HTMLFormElement
92659      */
92660     buildForm: function() {
92661         var fieldsSpec = [],
92662             formSpec,
92663             formEl,
92664             basicForm = this.form,
92665             params = this.getParams(),
92666             uploadFields = [];
92667
92668         basicForm.getFields().each(function(field) {
92669             if (field.isFileUpload()) {
92670                 uploadFields.push(field);
92671             }
92672         });
92673
92674         function addField(name, val) {
92675             fieldsSpec.push({
92676                 tag: 'input',
92677                 type: 'hidden',
92678                 name: name,
92679                 value: Ext.String.htmlEncode(val)
92680             });
92681         }
92682
92683         // Add the form field values
92684         Ext.iterate(params, function(key, val) {
92685             if (Ext.isArray(val)) {
92686                 Ext.each(val, function(v) {
92687                     addField(key, v);
92688                 });
92689             } else {
92690                 addField(key, val);
92691             }
92692         });
92693
92694         formSpec = {
92695             tag: 'form',
92696             action: this.getUrl(),
92697             method: this.getMethod(),
92698             target: this.target || '_self',
92699             style: 'display:none',
92700             cn: fieldsSpec
92701         };
92702
92703         // Set the proper encoding for file uploads
92704         if (uploadFields.length) {
92705             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
92706         }
92707
92708         // Create the form
92709         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
92710
92711         // Special handling for file upload fields: since browser security measures prevent setting
92712         // their values programatically, and prevent carrying their selected values over when cloning,
92713         // we have to move the actual field instances out of their components and into the form.
92714         Ext.Array.each(uploadFields, function(field) {
92715             if (field.rendered) { // can only have a selected file value after being rendered
92716                 formEl.appendChild(field.extractFileInput());
92717             }
92718         });
92719
92720         return formEl;
92721     },
92722
92723
92724
92725     /**
92726      * @private
92727      */
92728     onSuccess: function(response) {
92729         var form = this.form,
92730             success = true,
92731             result = this.processResponse(response);
92732         if (result !== true && !result.success) {
92733             if (result.errors) {
92734                 form.markInvalid(result.errors);
92735             }
92736             this.failureType = Ext.form.action.Action.SERVER_INVALID;
92737             success = false;
92738         }
92739         form.afterAction(this, success);
92740     },
92741
92742     /**
92743      * @private
92744      */
92745     handleResponse: function(response) {
92746         var form = this.form,
92747             errorReader = form.errorReader,
92748             rs, errors, i, len, records;
92749         if (errorReader) {
92750             rs = errorReader.read(response);
92751             records = rs.records;
92752             errors = [];
92753             if (records) {
92754                 for(i = 0, len = records.length; i < len; i++) {
92755                     errors[i] = records[i].data;
92756                 }
92757             }
92758             if (errors.length < 1) {
92759                 errors = null;
92760             }
92761             return {
92762                 success : rs.success,
92763                 errors : errors
92764             };
92765         }
92766         return Ext.decode(response.responseText);
92767     }
92768 });
92769
92770 /**
92771  * @class Ext.util.ComponentDragger
92772  * @extends Ext.dd.DragTracker
92773  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
92774  * <p>This is configured with a Component to be made draggable, and a config object for the
92775  * {@link Ext.dd.DragTracker} class.</p>
92776  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
92777  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
92778  */
92779 Ext.define('Ext.util.ComponentDragger', {
92780
92781     /**
92782      * @cfg {Boolean} constrain
92783      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
92784      */
92785
92786     /**
92787      * @cfg {String/Ext.Element} delegate
92788      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
92789      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
92790      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
92791      */
92792
92793     /**
92794      * @cfg {Boolean} constrainDelegate
92795      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
92796      */
92797
92798     extend: 'Ext.dd.DragTracker',
92799
92800     autoStart: 500,
92801
92802     /**
92803      * Creates new ComponentDragger.
92804      * @param {Object} comp The Component to provide dragging for.
92805      * @param {Object} config (optional) Config object
92806      */
92807     constructor: function(comp, config) {
92808         this.comp = comp;
92809         this.initialConstrainTo = config.constrainTo;
92810         this.callParent([ config ]);
92811     },
92812
92813     onStart: function(e) {
92814         var me = this,
92815             comp = me.comp;
92816
92817         // Cache the start [X, Y] array
92818         this.startPosition = comp.getPosition();
92819
92820         // If client Component has a ghost method to show a lightweight version of itself
92821         // then use that as a drag proxy unless configured to liveDrag.
92822         if (comp.ghost && !comp.liveDrag) {
92823              me.proxy = comp.ghost();
92824              me.dragTarget = me.proxy.header.el;
92825         }
92826
92827         // Set the constrainTo Region before we start dragging.
92828         if (me.constrain || me.constrainDelegate) {
92829             me.constrainTo = me.calculateConstrainRegion();
92830         }
92831     },
92832
92833     calculateConstrainRegion: function() {
92834         var me = this,
92835             comp = me.comp,
92836             c = me.initialConstrainTo,
92837             delegateRegion,
92838             elRegion,
92839             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
92840
92841         // The configured constrainTo might be a Region or an element
92842         if (!(c instanceof Ext.util.Region)) {
92843             c =  Ext.fly(c).getViewRegion();
92844         }
92845
92846         // Reduce the constrain region to allow for shadow
92847         if (shadowSize) {
92848             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
92849         }
92850
92851         // If they only want to constrain the *delegate* to within the constrain region,
92852         // adjust the region to be larger based on the insets of the delegate from the outer
92853         // edges of the Component.
92854         if (!me.constrainDelegate) {
92855             delegateRegion = Ext.fly(me.dragTarget).getRegion();
92856             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
92857
92858             c.adjust(
92859                 delegateRegion.top - elRegion.top,
92860                 delegateRegion.right - elRegion.right,
92861                 delegateRegion.bottom - elRegion.bottom,
92862                 delegateRegion.left - elRegion.left
92863             );
92864         }
92865         return c;
92866     },
92867
92868     // Move either the ghost Component or the target Component to its new position on drag
92869     onDrag: function(e) {
92870         var me = this,
92871             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
92872             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
92873
92874         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
92875     },
92876
92877     onEnd: function(e) {
92878         if (this.proxy && !this.comp.liveDrag) {
92879             this.comp.unghost();
92880         }
92881     }
92882 });
92883 /**
92884  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
92885  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
92886  * to let them be managed by the Field layout.
92887  *
92888  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
92889  * is more likely you will want to use one of the component classes that import this mixin, such as
92890  * Ext.form.field.Base or Ext.form.FieldContainer.
92891  *
92892  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
92893  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
92894  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
92895  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
92896  * two.)
92897  *
92898  * Component classes which use this mixin should use the Field layout
92899  * or a derivation thereof to properly size and position the label and message according to the component config.
92900  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
92901  * set up correctly.
92902  *
92903  * @docauthor Jason Johnston <jason@sencha.com>
92904  */
92905 Ext.define("Ext.form.Labelable", {
92906     requires: ['Ext.XTemplate'],
92907
92908     /**
92909      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
92910      * The rendering template for the field decorations. Component classes using this mixin should include
92911      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
92912      * {@link #getSubTplMarkup} method to generate the field body content.
92913      */
92914     labelableRenderTpl: [
92915         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
92916             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
92917                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
92918                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
92919             '</label>',
92920         '</tpl>',
92921         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
92922         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
92923         '<div class="{clearCls}" role="presentation"><!-- --></div>',
92924         {
92925             compiled: true,
92926             disableFormats: true
92927         }
92928     ],
92929
92930     /**
92931      * @cfg {Ext.XTemplate} activeErrorsTpl
92932      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
92933      * into a single HTML string. By default this renders each message as an item in an unordered list.
92934      */
92935     activeErrorsTpl: [
92936         '<tpl if="errors && errors.length">',
92937             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
92938         '</tpl>'
92939     ],
92940
92941     /**
92942      * @property isFieldLabelable
92943      * @type Boolean
92944      * Flag denoting that this object is labelable as a field. Always true.
92945      */
92946     isFieldLabelable: true,
92947
92948     /**
92949      * @cfg {String} [formItemCls='x-form-item']
92950      * A CSS class to be applied to the outermost element to denote that it is participating in the form
92951      * field layout.
92952      */
92953     formItemCls: Ext.baseCSSPrefix + 'form-item',
92954
92955     /**
92956      * @cfg {String} [labelCls='x-form-item-label']
92957      * The CSS class to be applied to the label element.
92958      * This (single) CSS class is used to formulate the renderSelector and drives the field
92959      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
92960      * additional classes, use {@link #labelClsExtra}.
92961      */
92962     labelCls: Ext.baseCSSPrefix + 'form-item-label',
92963
92964     /**
92965      * @cfg {String} labelClsExtra
92966      * An optional string of one or more additional CSS classes to add to the label element.
92967      * Defaults to empty.
92968      */
92969
92970     /**
92971      * @cfg {String} [errorMsgCls='x-form-error-msg']
92972      * The CSS class to be applied to the error message element.
92973      */
92974     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
92975
92976     /**
92977      * @cfg {String} [baseBodyCls='x-form-item-body']
92978      * The CSS class to be applied to the body content element.
92979      */
92980     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
92981
92982     /**
92983      * @cfg {String} fieldBodyCls
92984      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
92985      */
92986     fieldBodyCls: '',
92987
92988     /**
92989      * @cfg {String} [clearCls='x-clear']
92990      * The CSS class to be applied to the special clearing div rendered directly after the field
92991      * contents wrapper to provide field clearing.
92992      */
92993     clearCls: Ext.baseCSSPrefix + 'clear',
92994
92995     /**
92996      * @cfg {String} [invalidCls='x-form-invalid']
92997      * The CSS class to use when marking the component invalid.
92998      */
92999     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
93000
93001     /**
93002      * @cfg {String} fieldLabel
93003      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
93004      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
93005      * configs.
93006      */
93007     fieldLabel: undefined,
93008
93009     /**
93010      * @cfg {String} labelAlign
93011      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
93012      * <ul>
93013      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
93014      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
93015      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
93016      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
93017      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
93018      * </ul>
93019      */
93020     labelAlign : 'left',
93021
93022     /**
93023      * @cfg {Number} labelWidth
93024      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
93025      * to "left" or "right".
93026      */
93027     labelWidth: 100,
93028
93029     /**
93030      * @cfg {Number} labelPad
93031      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
93032      */
93033     labelPad : 5,
93034
93035     /**
93036      * @cfg {String} labelSeparator
93037      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
93038      */
93039     labelSeparator : ':',
93040
93041     /**
93042      * @cfg {String} labelStyle
93043      * A CSS style specification string to apply directly to this field's label.
93044      */
93045
93046     /**
93047      * @cfg {Boolean} hideLabel
93048      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
93049      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
93050      */
93051     hideLabel: false,
93052
93053     /**
93054      * @cfg {Boolean} hideEmptyLabel
93055      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
93056      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
93057      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
93058      * to line up with other labeled fields in the same form.</p>
93059      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
93060      * the {@link #hideLabel} config to <tt>true</tt>.</p>
93061      */
93062     hideEmptyLabel: true,
93063
93064     /**
93065      * @cfg {Boolean} preventMark
93066      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
93067      */
93068     preventMark: false,
93069
93070     /**
93071      * @cfg {Boolean} autoFitErrors
93072      * Whether to adjust the component's body area to make room for 'side' or 'under'
93073      * {@link #msgTarget error messages}.
93074      */
93075     autoFitErrors: true,
93076
93077     /**
93078      * @cfg {String} msgTarget <p>The location where the error message text should display.
93079      * Must be one of the following values:</p>
93080      * <div class="mdetail-params"><ul>
93081      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
93082      * <div class="subdesc"><b>{@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager.init} must have been called for this setting to work.</b></div></li>
93083      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
93084      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
93085      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
93086      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
93087      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
93088      * </ul></div>
93089      */
93090     msgTarget: 'qtip',
93091
93092     /**
93093      * @cfg {String} activeError
93094      * If specified, then the component will be displayed with this value as its active error when
93095      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
93096      * change it after component creation.
93097      */
93098
93099
93100     /**
93101      * Performs initialization of this mixin. Component classes using this mixin should call this method
93102      * during their own initialization.
93103      */
93104     initLabelable: function() {
93105         this.addCls(this.formItemCls);
93106
93107         this.addEvents(
93108             /**
93109              * @event errorchange
93110              * Fires when the active error message is changed via {@link #setActiveError}.
93111              * @param {Ext.form.Labelable} this
93112              * @param {String} error The active error message
93113              */
93114             'errorchange'
93115         );
93116     },
93117
93118     /**
93119      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
93120      * overridden to provide
93121      * @return {String} The configured field label, or empty string if not defined
93122      */
93123     getFieldLabel: function() {
93124         return this.fieldLabel || '';
93125     },
93126
93127     /**
93128      * @protected
93129      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
93130      * @return {Object} The template arguments
93131      */
93132     getLabelableRenderData: function() {
93133         var me = this,
93134             labelAlign = me.labelAlign,
93135             labelCls = me.labelCls,
93136             labelClsExtra = me.labelClsExtra,
93137             labelPad = me.labelPad,
93138             labelStyle;
93139
93140         // Calculate label styles up front rather than in the Field layout for speed; this
93141         // is safe because label alignment/width/pad are not expected to change.
93142         if (labelAlign === 'top') {
93143             labelStyle = 'margin-bottom:' + labelPad + 'px;';
93144         } else {
93145             labelStyle = 'margin-right:' + labelPad + 'px;';
93146             // Add the width for border-box browsers; will be set by the Field layout for content-box
93147             if (Ext.isBorderBox) {
93148                 labelStyle += 'width:' + me.labelWidth + 'px;';
93149             }
93150         }
93151
93152         return Ext.copyTo(
93153             {
93154                 inputId: me.getInputId(),
93155                 fieldLabel: me.getFieldLabel(),
93156                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
93157                 labelStyle: labelStyle + (me.labelStyle || ''),
93158                 subTplMarkup: me.getSubTplMarkup()
93159             },
93160             me,
93161             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
93162             true
93163         );
93164     },
93165
93166     onLabelableRender: function () {
93167         this.addChildEls(
93168             /**
93169              * @property labelEl
93170              * @type Ext.Element
93171              * The label Element for this component. Only available after the component has been rendered.
93172              */
93173             'labelEl',
93174
93175             /**
93176              * @property bodyEl
93177              * @type Ext.Element
93178              * The div Element wrapping the component's contents. Only available after the component has been rendered.
93179              */
93180             'bodyEl',
93181
93182             /**
93183              * @property errorEl
93184              * @type Ext.Element
93185              * The div Element that will contain the component's error message(s). Note that depending on the
93186              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
93187              * presentation, but will always be present in the DOM for use by assistive technologies.
93188              */
93189             'errorEl'
93190         );
93191     },
93192
93193     /**
93194      * @protected
93195      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
93196      * be implemented by classes including this mixin as needed.
93197      * @return {String} The markup to be inserted
93198      */
93199     getSubTplMarkup: function() {
93200         return '';
93201     },
93202
93203     /**
93204      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
93205      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
93206      * @return {String} The input id
93207      */
93208     getInputId: function() {
93209         return '';
93210     },
93211
93212     /**
93213      * Gets the active error message for this component, if any. This does not trigger
93214      * validation on its own, it merely returns any message that the component may already hold.
93215      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
93216      */
93217     getActiveError : function() {
93218         return this.activeError || '';
93219     },
93220
93221     /**
93222      * Tells whether the field currently has an active error message. This does not trigger
93223      * validation on its own, it merely looks for any message that the component may already hold.
93224      * @return {Boolean}
93225      */
93226     hasActiveError: function() {
93227         return !!this.getActiveError();
93228     },
93229
93230     /**
93231      * Sets the active error message to the given string. This replaces the entire error message
93232      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
93233      * messages and formats them according to the {@link #activeErrorsTpl}.
93234      *
93235      * Note that this only updates the error message element's text and attributes, you'll have
93236      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93237      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93238      *
93239      * @param {String} msg The error message
93240      */
93241     setActiveError: function(msg) {
93242         this.activeError = msg;
93243         this.activeErrors = [msg];
93244         this.renderActiveError();
93245     },
93246
93247     /**
93248      * Gets an Array of any active error messages currently applied to the field. This does not trigger
93249      * validation on its own, it merely returns any messages that the component may already hold.
93250      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
93251      */
93252     getActiveErrors: function() {
93253         return this.activeErrors || [];
93254     },
93255
93256     /**
93257      * Set the active error message to an Array of error messages. The messages are formatted into
93258      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
93259      * which allows setting the entire error contents with a single string.
93260      *
93261      * Note that this only updates the error message element's text and attributes, you'll have
93262      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93263      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93264      *
93265      * @param {String[]} errors The error messages
93266      */
93267     setActiveErrors: function(errors) {
93268         this.activeErrors = errors;
93269         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
93270         this.renderActiveError();
93271     },
93272
93273     /**
93274      * Clears the active error message(s).
93275      *
93276      * Note that this only clears the error message element's text and attributes, you'll have
93277      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93278      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
93279      */
93280     unsetActiveError: function() {
93281         delete this.activeError;
93282         delete this.activeErrors;
93283         this.renderActiveError();
93284     },
93285
93286     /**
93287      * @private
93288      * Updates the rendered DOM to match the current activeError. This only updates the content and
93289      * attributes, you'll have to call doComponentLayout to actually update the display.
93290      */
93291     renderActiveError: function() {
93292         var me = this,
93293             activeError = me.getActiveError(),
93294             hasError = !!activeError;
93295
93296         if (activeError !== me.lastActiveError) {
93297             me.fireEvent('errorchange', me, activeError);
93298             me.lastActiveError = activeError;
93299         }
93300
93301         if (me.rendered && !me.isDestroyed && !me.preventMark) {
93302             // Add/remove invalid class
93303             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
93304
93305             // Update the aria-invalid attribute
93306             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
93307
93308             // Update the errorEl with the error message text
93309             me.errorEl.dom.innerHTML = activeError;
93310         }
93311     },
93312
93313     /**
93314      * Applies a set of default configuration values to this Labelable instance. For each of the
93315      * properties in the given object, check if this component hasOwnProperty that config; if not
93316      * then it's inheriting a default value from its prototype and we should apply the default value.
93317      * @param {Object} defaults The defaults to apply to the object.
93318      */
93319     setFieldDefaults: function(defaults) {
93320         var me = this;
93321         Ext.iterate(defaults, function(key, val) {
93322             if (!me.hasOwnProperty(key)) {
93323                 me[key] = val;
93324             }
93325         });
93326     },
93327
93328     /**
93329      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
93330      * Note for implementors: if at all possible this method should be overridden with a custom implementation
93331      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
93332      */
93333     getBodyNaturalWidth: function() {
93334         return this.bodyEl.getWidth();
93335     }
93336
93337 });
93338
93339 /**
93340  * @docauthor Jason Johnston <jason@sencha.com>
93341  *
93342  * This mixin provides a common interface for the logical behavior and state of form fields, including:
93343  *
93344  * - Getter and setter methods for field values
93345  * - Events and methods for tracking value and validity changes
93346  * - Methods for triggering validation
93347  *
93348  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
93349  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
93350  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
93351  * plus methods that bind the Field value getters and setters to the input field's value.
93352  *
93353  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
93354  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
93355  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
93356  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
93357  * is called during the component's initialization.
93358  */
93359 Ext.define('Ext.form.field.Field', {
93360     /**
93361      * @property {Boolean} isFormField
93362      * Flag denoting that this component is a Field. Always true.
93363      */
93364     isFormField : true,
93365
93366     /**
93367      * @cfg {Object} value
93368      * A value to initialize this field with.
93369      */
93370
93371     /**
93372      * @cfg {String} name
93373      * The name of the field. By default this is used as the parameter name when including the
93374      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
93375      * being included in the form submit, set {@link #submitValue} to false.
93376      */
93377
93378     /**
93379      * @cfg {Boolean} disabled
93380      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
93381      */
93382     disabled : false,
93383
93384     /**
93385      * @cfg {Boolean} submitValue
93386      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
93387      * not disabled.
93388      */
93389     submitValue: true,
93390
93391     /**
93392      * @cfg {Boolean} validateOnChange
93393      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
93394      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
93395      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
93396      * typing.
93397      *
93398      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
93399      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
93400      * manually.
93401      *
93402      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
93403      * detected.
93404      */
93405     validateOnChange: true,
93406
93407     /**
93408      * @private
93409      */
93410     suspendCheckChange: 0,
93411
93412     /**
93413      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
93414      * their own initialization process.
93415      */
93416     initField: function() {
93417         this.addEvents(
93418             /**
93419              * @event change
93420              * Fires when a user-initiated change is detected in the value of the field.
93421              * @param {Ext.form.field.Field} this
93422              * @param {Object} newValue The new value
93423              * @param {Object} oldValue The original value
93424              */
93425             'change',
93426             /**
93427              * @event validitychange
93428              * Fires when a change in the field's validity is detected.
93429              * @param {Ext.form.field.Field} this
93430              * @param {Boolean} isValid Whether or not the field is now valid
93431              */
93432             'validitychange',
93433             /**
93434              * @event dirtychange
93435              * Fires when a change in the field's {@link #isDirty} state is detected.
93436              * @param {Ext.form.field.Field} this
93437              * @param {Boolean} isDirty Whether or not the field is now dirty
93438              */
93439             'dirtychange'
93440         );
93441
93442         this.initValue();
93443     },
93444
93445     /**
93446      * Initializes the field's value based on the initial config.
93447      */
93448     initValue: function() {
93449         var me = this;
93450
93451         /**
93452          * @property {Object} originalValue
93453          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
93454          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
93455          */
93456         me.originalValue = me.lastValue = me.value;
93457
93458         // Set the initial value - prevent validation on initial set
93459         me.suspendCheckChange++;
93460         me.setValue(me.value);
93461         me.suspendCheckChange--;
93462     },
93463
93464     /**
93465      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
93466      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
93467      * @return {String} name The field {@link Ext.form.field.Field#name name}
93468      */
93469     getName: function() {
93470         return this.name;
93471     },
93472
93473     /**
93474      * Returns the current data value of the field. The type of value returned is particular to the type of the
93475      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
93476      * @return {Object} value The field value
93477      */
93478     getValue: function() {
93479         return this.value;
93480     },
93481
93482     /**
93483      * Sets a data value into the field and runs the change detection and validation.
93484      * @param {Object} value The value to set
93485      * @return {Ext.form.field.Field} this
93486      */
93487     setValue: function(value) {
93488         var me = this;
93489         me.value = value;
93490         me.checkChange();
93491         return me;
93492     },
93493
93494     /**
93495      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
93496      * to provide custom comparison logic appropriate for the particular field's data type.
93497      * @param {Object} value1 The first value to compare
93498      * @param {Object} value2 The second value to compare
93499      * @return {Boolean} True if the values are equal, false if inequal.
93500      */
93501     isEqual: function(value1, value2) {
93502         return String(value1) === String(value2);
93503     },
93504     
93505     /**
93506      * Returns whether two values are logically equal.
93507      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
93508      * @private
93509      * @param {Object} value1 The first value to compare
93510      * @param {Object} value2 The second value to compare
93511      * @return {Boolean} True if the values are equal, false if inequal.
93512      */
93513     isEqualAsString: function(value1, value2){
93514         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
93515     },
93516
93517     /**
93518      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
93519      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
93520      * its current stringified value. More advanced field implementations may return more than one name-value pair.
93521      *
93522      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93523      * validated}.
93524      *
93525      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93526      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93527      * submitted.
93528      */
93529     getSubmitData: function() {
93530         var me = this,
93531             data = null;
93532         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
93533             data = {};
93534             data[me.getName()] = '' + me.getValue();
93535         }
93536         return data;
93537     },
93538
93539     /**
93540      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
93541      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
93542      * being this field's {@link #getName name} and the value being its current data value. More advanced field
93543      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
93544      * field names in the Model.
93545      *
93546      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93547      * validated}.
93548      *
93549      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93550      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93551      * submitted.
93552      */
93553     getModelData: function() {
93554         var me = this,
93555             data = null;
93556         if (!me.disabled && !me.isFileUpload()) {
93557             data = {};
93558             data[me.getName()] = me.getValue();
93559         }
93560         return data;
93561     },
93562
93563     /**
93564      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
93565      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
93566      */
93567     reset : function(){
93568         var me = this;
93569
93570         me.setValue(me.originalValue);
93571         me.clearInvalid();
93572         // delete here so we reset back to the original state
93573         delete me.wasValid;
93574     },
93575
93576     /**
93577      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
93578      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
93579      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
93580      */
93581     resetOriginalValue: function() {
93582         this.originalValue = this.getValue();
93583         this.checkDirty();
93584     },
93585
93586     /**
93587      * Checks whether the value of the field has changed since the last time it was checked.
93588      * If the value has changed, it:
93589      *
93590      * 1. Fires the {@link #change change event},
93591      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
93592      *    {@link #validitychange validitychange event} if the validity has changed, and
93593      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
93594      *    if it has changed.
93595      */
93596     checkChange: function() {
93597         if (!this.suspendCheckChange) {
93598             var me = this,
93599                 newVal = me.getValue(),
93600                 oldVal = me.lastValue;
93601             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
93602                 me.lastValue = newVal;
93603                 me.fireEvent('change', me, newVal, oldVal);
93604                 me.onChange(newVal, oldVal);
93605             }
93606         }
93607     },
93608
93609     /**
93610      * @private
93611      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
93612      * config is enabled, and invokes the dirty check.
93613      */
93614     onChange: function(newVal, oldVal) {
93615         if (this.validateOnChange) {
93616             this.validate();
93617         }
93618         this.checkDirty();
93619     },
93620
93621     /**
93622      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
93623      * Will always return false if the field is disabled.
93624      *
93625      * Note that if the owning {@link Ext.form.Basic form} was configured with
93626      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
93627      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
93628      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
93629      * false otherwise.
93630      */
93631     isDirty : function() {
93632         var me = this;
93633         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
93634     },
93635
93636     /**
93637      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
93638      * fires the {@link #dirtychange} event.
93639      */
93640     checkDirty: function() {
93641         var me = this,
93642             isDirty = me.isDirty();
93643         if (isDirty !== me.wasDirty) {
93644             me.fireEvent('dirtychange', me, isDirty);
93645             me.onDirtyChange(isDirty);
93646             me.wasDirty = isDirty;
93647         }
93648     },
93649
93650     /**
93651      * @private Called when the field's dirty state changes.
93652      * @param {Boolean} isDirty
93653      */
93654     onDirtyChange: Ext.emptyFn,
93655
93656     /**
93657      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
93658      * internally during validation and would not usually need to be used manually.
93659      *
93660      * Each subclass should override or augment the return value to provide their own errors.
93661      *
93662      * @param {Object} value The value to get errors for (defaults to the current field value)
93663      * @return {String[]} All error messages for this field; an empty Array if none.
93664      */
93665     getErrors: function(value) {
93666         return [];
93667     },
93668
93669     /**
93670      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93671      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
93672      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
93673      *
93674      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
93675      * message display.
93676      *
93677      * @return {Boolean} True if the value is valid, else false
93678      */
93679     isValid : function() {
93680         var me = this;
93681         return me.disabled || Ext.isEmpty(me.getErrors());
93682     },
93683
93684     /**
93685      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93686      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
93687      * **Note**: {@link #disabled} fields are always treated as valid.
93688      *
93689      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
93690      * To validate without side-effects, use {@link #isValid}.
93691      *
93692      * @return {Boolean} True if the value is valid, else false
93693      */
93694     validate : function() {
93695         var me = this,
93696             isValid = me.isValid();
93697         if (isValid !== me.wasValid) {
93698             me.wasValid = isValid;
93699             me.fireEvent('validitychange', me, isValid);
93700         }
93701         return isValid;
93702     },
93703
93704     /**
93705      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
93706      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
93707      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
93708      * change.
93709      * @param {Object} fn A function containing the transaction code
93710      */
93711     batchChanges: function(fn) {
93712         try {
93713             this.suspendCheckChange++;
93714             fn();
93715         } catch(e){
93716             throw e;
93717         } finally {
93718             this.suspendCheckChange--;
93719         }
93720         this.checkChange();
93721     },
93722
93723     /**
93724      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
93725      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
93726      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
93727      * input element.
93728      * @return {Boolean}
93729      */
93730     isFileUpload: function() {
93731         return false;
93732     },
93733
93734     /**
93735      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
93736      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
93737      * returned, so this method should also create a replacement.
93738      * @return {HTMLElement}
93739      */
93740     extractFileInput: function() {
93741         return null;
93742     },
93743
93744     /**
93745      * @method markInvalid
93746      * Associate one or more error messages with this field. Components using this mixin should implement this method to
93747      * update the component's rendering to display the messages.
93748      *
93749      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
93750      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
93751      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93752      *
93753      * @param {String/String[]} errors The error message(s) for the field.
93754      */
93755     markInvalid: Ext.emptyFn,
93756
93757     /**
93758      * @method clearInvalid
93759      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
93760      * update the components rendering to clear any existing messages.
93761      *
93762      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
93763      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
93764      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93765      */
93766     clearInvalid: Ext.emptyFn
93767
93768 });
93769
93770 /**
93771  * @class Ext.layout.component.field.Field
93772  * @extends Ext.layout.component.Component
93773  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
93774  * the form control, label, and error message treatment.
93775  * @private
93776  */
93777 Ext.define('Ext.layout.component.field.Field', {
93778
93779     /* Begin Definitions */
93780
93781     alias: ['layout.field'],
93782
93783     extend: 'Ext.layout.component.Component',
93784
93785     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
93786
93787     /* End Definitions */
93788
93789     type: 'field',
93790
93791     beforeLayout: function(width, height) {
93792         var me = this;
93793         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
93794     },
93795
93796     onLayout: function(width, height) {
93797         var me = this,
93798             owner = me.owner,
93799             labelStrategy = me.getLabelStrategy(),
93800             errorStrategy = me.getErrorStrategy(),
93801             isDefined = Ext.isDefined,
93802             isNumber = Ext.isNumber,
93803             lastSize, autoWidth, autoHeight, info, undef;
93804
93805         lastSize = me.lastComponentSize || {};
93806         if (!isDefined(width)) {
93807             width = lastSize.width;
93808             if (width < 0) { //first pass lastComponentSize.width is -Infinity
93809                 width = undef;
93810             }
93811         }
93812         if (!isDefined(height)) {
93813             height = lastSize.height;
93814             if (height < 0) { //first pass lastComponentSize.height is -Infinity
93815                 height = undef;
93816             }
93817         }
93818         autoWidth = !isNumber(width);
93819         autoHeight = !isNumber(height);
93820
93821         info = {
93822             autoWidth: autoWidth,
93823             autoHeight: autoHeight,
93824             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
93825             height: height,
93826             setOuterWidth: false, //whether the outer el width should be set to the calculated width
93827
93828             // insets for the bodyEl from each side of the component layout area
93829             insets: {
93830                 top: 0,
93831                 right: 0,
93832                 bottom: 0,
93833                 left: 0
93834             }
93835         };
93836
93837         // NOTE the order of calculating insets and setting styles here is very important; we must first
93838         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
93839         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
93840
93841         // perform preparation on the label and error (setting css classes, qtips, etc.)
93842         labelStrategy.prepare(owner, info);
93843         errorStrategy.prepare(owner, info);
93844
93845         // calculate the horizontal insets for the label and error
93846         labelStrategy.adjustHorizInsets(owner, info);
93847         errorStrategy.adjustHorizInsets(owner, info);
93848
93849         // set horizontal styles for label and error based on the current insets
93850         labelStrategy.layoutHoriz(owner, info);
93851         errorStrategy.layoutHoriz(owner, info);
93852
93853         // calculate the vertical insets for the label and error
93854         labelStrategy.adjustVertInsets(owner, info);
93855         errorStrategy.adjustVertInsets(owner, info);
93856
93857         // set vertical styles for label and error based on the current insets
93858         labelStrategy.layoutVert(owner, info);
93859         errorStrategy.layoutVert(owner, info);
93860
93861         // perform sizing of the elements based on the final dimensions and insets
93862         if (autoWidth && autoHeight) {
93863             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
93864             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
93865         } else {
93866             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
93867         }
93868         me.sizeBody(info);
93869
93870         me.activeError = owner.getActiveError();
93871     },
93872     
93873     onFocus: function(){
93874         this.getErrorStrategy().onFocus(this.owner);    
93875     },
93876
93877
93878     /**
93879      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
93880      */
93881     sizeBody: function(info) {
93882         var me = this,
93883             owner = me.owner,
93884             insets = info.insets,
93885             totalWidth = info.width,
93886             totalHeight = info.height,
93887             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
93888             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
93889
93890         // size the bodyEl
93891         me.setElementSize(owner.bodyEl, width, height);
93892
93893         // size the bodyEl's inner contents if necessary
93894         me.sizeBodyContents(width, height);
93895     },
93896
93897     /**
93898      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
93899      * default, subclasses can override to handle their specific contents.
93900      * @param {Number} width The bodyEl width
93901      * @param {Number} height The bodyEl height
93902      */
93903     sizeBodyContents: Ext.emptyFn,
93904
93905
93906     /**
93907      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
93908      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
93909      */
93910     getLabelStrategy: function() {
93911         var me = this,
93912             strategies = me.labelStrategies,
93913             labelAlign = me.owner.labelAlign;
93914         return strategies[labelAlign] || strategies.base;
93915     },
93916
93917     /**
93918      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
93919      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
93920      */
93921     getErrorStrategy: function() {
93922         var me = this,
93923             owner = me.owner,
93924             strategies = me.errorStrategies,
93925             msgTarget = owner.msgTarget;
93926         return !owner.preventMark && Ext.isString(msgTarget) ?
93927                 (strategies[msgTarget] || strategies.elementId) :
93928                 strategies.none;
93929     },
93930
93931
93932
93933     /**
93934      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
93935      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
93936      */
93937     labelStrategies: (function() {
93938         var applyIf = Ext.applyIf,
93939             emptyFn = Ext.emptyFn,
93940             base = {
93941                 prepare: function(owner, info) {
93942                     var cls = owner.labelCls + '-' + owner.labelAlign,
93943                         labelEl = owner.labelEl;
93944                     if (labelEl && !labelEl.hasCls(cls)) {
93945                         labelEl.addCls(cls);
93946                     }
93947                 },
93948                 adjustHorizInsets: emptyFn,
93949                 adjustVertInsets: emptyFn,
93950                 layoutHoriz: emptyFn,
93951                 layoutVert: emptyFn
93952             },
93953             left = applyIf({
93954                 prepare: function(owner, info) {
93955                     base.prepare(owner, info);
93956                     // If auto width, add the label width to the body's natural width.
93957                     if (info.autoWidth) {
93958                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
93959                     }
93960                     // Must set outer width to prevent field from wrapping below floated label
93961                     info.setOuterWidth = true;
93962                 },
93963                 adjustHorizInsets: function(owner, info) {
93964                     if (owner.labelEl) {
93965                         info.insets.left += owner.labelWidth + owner.labelPad;
93966                     }
93967                 },
93968                 layoutHoriz: function(owner, info) {
93969                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
93970                     // setting the width style because it needs to account for the final calculated
93971                     // padding/border styles for the label. So we set the width programmatically here to
93972                     // normalize content-box sizing, while letting border-box browsers use the original
93973                     // width style.
93974                     var labelEl = owner.labelEl;
93975                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
93976                         labelEl.setWidth(owner.labelWidth);
93977                         owner.isLabelSized = true;
93978                     }
93979                 }
93980             }, base);
93981
93982
93983         return {
93984             base: base,
93985
93986             /**
93987              * Label displayed above the bodyEl
93988              */
93989             top: applyIf({
93990                 adjustVertInsets: function(owner, info) {
93991                     var labelEl = owner.labelEl;
93992                     if (labelEl) {
93993                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
93994                                            labelEl.getFrameWidth('tb') + owner.labelPad;
93995                     }
93996                 }
93997             }, base),
93998
93999             /**
94000              * Label displayed to the left of the bodyEl
94001              */
94002             left: left,
94003
94004             /**
94005              * Same as left, only difference is text-align in CSS
94006              */
94007             right: left
94008         };
94009     })(),
94010
94011
94012
94013     /**
94014      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
94015      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
94016      */
94017     errorStrategies: (function() {
94018         function setDisplayed(el, displayed) {
94019             var wasDisplayed = el.getStyle('display') !== 'none';
94020             if (displayed !== wasDisplayed) {
94021                 el.setDisplayed(displayed);
94022             }
94023         }
94024
94025         function setStyle(el, name, value) {
94026             if (el.getStyle(name) !== value) {
94027                 el.setStyle(name, value);
94028             }
94029         }
94030         
94031         function showTip(owner) {
94032             var tip = Ext.layout.component.field.Field.tip,
94033                 target;
94034                 
94035             if (tip && tip.isVisible()) {
94036                 target = tip.activeTarget;
94037                 if (target && target.el === owner.getActionEl().dom) {
94038                     tip.toFront(true);
94039                 }
94040             }
94041         }
94042
94043         var applyIf = Ext.applyIf,
94044             emptyFn = Ext.emptyFn,
94045             base = {
94046                 prepare: function(owner) {
94047                     setDisplayed(owner.errorEl, false);
94048                 },
94049                 adjustHorizInsets: emptyFn,
94050                 adjustVertInsets: emptyFn,
94051                 layoutHoriz: emptyFn,
94052                 layoutVert: emptyFn,
94053                 onFocus: emptyFn
94054             };
94055
94056         return {
94057             none: base,
94058
94059             /**
94060              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
94061              */
94062             side: applyIf({
94063                 prepare: function(owner) {
94064                     var errorEl = owner.errorEl;
94065                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
94066                     Ext.layout.component.field.Field.initTip();
94067                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94068                     setDisplayed(errorEl, owner.hasActiveError());
94069                 },
94070                 adjustHorizInsets: function(owner, info) {
94071                     if (owner.autoFitErrors && owner.hasActiveError()) {
94072                         info.insets.right += owner.errorEl.getWidth();
94073                     }
94074                 },
94075                 layoutHoriz: function(owner, info) {
94076                     if (owner.hasActiveError()) {
94077                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
94078                     }
94079                 },
94080                 layoutVert: function(owner, info) {
94081                     if (owner.hasActiveError()) {
94082                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
94083                     }
94084                 },
94085                 onFocus: showTip
94086             }, base),
94087
94088             /**
94089              * Error message displayed underneath the bodyEl
94090              */
94091             under: applyIf({
94092                 prepare: function(owner) {
94093                     var errorEl = owner.errorEl,
94094                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
94095                     if (!errorEl.hasCls(cls)) {
94096                         errorEl.addCls(cls);
94097                     }
94098                     setDisplayed(errorEl, owner.hasActiveError());
94099                 },
94100                 adjustVertInsets: function(owner, info) {
94101                     if (owner.autoFitErrors) {
94102                         info.insets.bottom += owner.errorEl.getHeight();
94103                     }
94104                 },
94105                 layoutHoriz: function(owner, info) {
94106                     var errorEl = owner.errorEl,
94107                         insets = info.insets;
94108
94109                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
94110                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
94111                 }
94112             }, base),
94113
94114             /**
94115              * Error displayed as QuickTip on hover of the field container
94116              */
94117             qtip: applyIf({
94118                 prepare: function(owner) {
94119                     setDisplayed(owner.errorEl, false);
94120                     Ext.layout.component.field.Field.initTip();
94121                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94122                 },
94123                 onFocus: showTip
94124             }, base),
94125
94126             /**
94127              * Error displayed as title tip on hover of the field container
94128              */
94129             title: applyIf({
94130                 prepare: function(owner) {
94131                     setDisplayed(owner.errorEl, false);
94132                     owner.el.dom.title = owner.getActiveError() || '';
94133                 }
94134             }, base),
94135
94136             /**
94137              * Error message displayed as content of an element with a given id elsewhere in the app
94138              */
94139             elementId: applyIf({
94140                 prepare: function(owner) {
94141                     setDisplayed(owner.errorEl, false);
94142                     var targetEl = Ext.fly(owner.msgTarget);
94143                     if (targetEl) {
94144                         targetEl.dom.innerHTML = owner.getActiveError() || '';
94145                         targetEl.setDisplayed(owner.hasActiveError());
94146                     }
94147                 }
94148             }, base)
94149         };
94150     })(),
94151
94152     statics: {
94153         /**
94154          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
94155          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
94156          */
94157         initTip: function() {
94158             var tip = this.tip;
94159             if (!tip) {
94160                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
94161                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
94162                     renderTo: Ext.getBody()
94163                 });
94164                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
94165             }
94166         },
94167
94168         /**
94169          * Destroy the error tip instance.
94170          */
94171         destroyTip: function() {
94172             var tip = this.tip;
94173             if (tip) {
94174                 tip.destroy();
94175                 delete this.tip;
94176             }
94177         }
94178     }
94179
94180 });
94181
94182 /**
94183  * @singleton
94184  * @alternateClassName Ext.form.VTypes
94185  *
94186  * This is a singleton object which contains a set of commonly used field validation functions
94187  * and provides a mechanism for creating reusable custom field validations.
94188  * The following field validation functions are provided out of the box:
94189  *
94190  * - {@link #alpha}
94191  * - {@link #alphanum}
94192  * - {@link #email}
94193  * - {@link #url}
94194  *
94195  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
94196  *
94197  *     Ext.create('Ext.form.field.Text', {
94198  *         fieldLabel: 'Email Address',
94199  *         name: 'email',
94200  *         vtype: 'email' // applies email validation rules to this field
94201  *     });
94202  *
94203  * To create custom VTypes:
94204  *
94205  *     // custom Vtype for vtype:'time'
94206  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
94207  *     Ext.apply(Ext.form.field.VTypes, {
94208  *         //  vtype validation function
94209  *         time: function(val, field) {
94210  *             return timeTest.test(val);
94211  *         },
94212  *         // vtype Text property: The error text to display when the validation function returns false
94213  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
94214  *         // vtype Mask property: The keystroke filter mask
94215  *         timeMask: /[\d\s:amp]/i
94216  *     });
94217  *
94218  * In the above example the `time` function is the validator that will run when field validation occurs,
94219  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
94220  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
94221  *
94222  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
94223  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
94224  *
94225  *     Ext.create('Ext.form.field.Text', {
94226  *         fieldLabel: 'Departure Time',
94227  *         name: 'departureTime',
94228  *         vtype: 'time' // applies custom time validation rules to this field
94229  *     });
94230  *
94231  * Another example of a custom validator:
94232  *
94233  *     // custom Vtype for vtype:'IPAddress'
94234  *     Ext.apply(Ext.form.field.VTypes, {
94235  *         IPAddress:  function(v) {
94236  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
94237  *         },
94238  *         IPAddressText: 'Must be a numeric IP address',
94239  *         IPAddressMask: /[\d\.]/i
94240  *     });
94241  *
94242  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
94243  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
94244  */
94245 Ext.define('Ext.form.field.VTypes', (function(){
94246     // closure these in so they are only created once.
94247     var alpha = /^[a-zA-Z_]+$/,
94248         alphanum = /^[a-zA-Z0-9_]+$/,
94249         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
94250         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
94251
94252     // All these messages and functions are configurable
94253     return {
94254         singleton: true,
94255         alternateClassName: 'Ext.form.VTypes',
94256
94257         /**
94258          * The function used to validate email addresses. Note that this is a very basic validation - complete
94259          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
94260          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
94261          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
94262          * to validate the following emails:
94263          *
94264          * - `barney@example.de`
94265          * - `barney.rubble@example.com`
94266          * - `barney-rubble@example.coop`
94267          * - `barney+rubble@example.com`
94268          *
94269          * [1]: http://en.wikipedia.org/wiki/E-mail_address
94270          *
94271          * @param {String} value The email address
94272          * @return {Boolean} true if the RegExp test passed, and false if not.
94273          */
94274         'email' : function(v){
94275             return email.test(v);
94276         },
94277         /**
94278          * @property {String} emailText
94279          * The error text to display when the email validation function returns false.
94280          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
94281          */
94282         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
94283         /**
94284          * @property {RegExp} emailMask
94285          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
94286          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
94287          */
94288         'emailMask' : /[a-z0-9_\.\-@\+]/i,
94289
94290         /**
94291          * The function used to validate URLs
94292          * @param {String} value The URL
94293          * @return {Boolean} true if the RegExp test passed, and false if not.
94294          */
94295         'url' : function(v){
94296             return url.test(v);
94297         },
94298         /**
94299          * @property {String} urlText
94300          * The error text to display when the url validation function returns false.
94301          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
94302          */
94303         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
94304
94305         /**
94306          * The function used to validate alpha values
94307          * @param {String} value The value
94308          * @return {Boolean} true if the RegExp test passed, and false if not.
94309          */
94310         'alpha' : function(v){
94311             return alpha.test(v);
94312         },
94313         /**
94314          * @property {String} alphaText
94315          * The error text to display when the alpha validation function returns false.
94316          * Defaults to: 'This field should only contain letters and _'
94317          */
94318         'alphaText' : 'This field should only contain letters and _',
94319         /**
94320          * @property {RegExp} alphaMask
94321          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
94322          */
94323         'alphaMask' : /[a-z_]/i,
94324
94325         /**
94326          * The function used to validate alphanumeric values
94327          * @param {String} value The value
94328          * @return {Boolean} true if the RegExp test passed, and false if not.
94329          */
94330         'alphanum' : function(v){
94331             return alphanum.test(v);
94332         },
94333         /**
94334          * @property {String} alphanumText
94335          * The error text to display when the alphanumeric validation function returns false.
94336          * Defaults to: 'This field should only contain letters, numbers and _'
94337          */
94338         'alphanumText' : 'This field should only contain letters, numbers and _',
94339         /**
94340          * @property {RegExp} alphanumMask
94341          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
94342          */
94343         'alphanumMask' : /[a-z0-9_]/i
94344     };
94345 })());
94346
94347 /**
94348  * @private
94349  * @class Ext.layout.component.field.Text
94350  * @extends Ext.layout.component.field.Field
94351  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
94352  */
94353 Ext.define('Ext.layout.component.field.Text', {
94354     extend: 'Ext.layout.component.field.Field',
94355     alias: 'layout.textfield',
94356     requires: ['Ext.util.TextMetrics'],
94357
94358     type: 'textfield',
94359
94360
94361     /**
94362      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
94363      * changed since the last layout.
94364      */
94365     beforeLayout: function(width, height) {
94366         var me = this,
94367             owner = me.owner,
94368             lastValue = this.lastValue,
94369             value = owner.getRawValue();
94370         this.lastValue = value;
94371         return me.callParent(arguments) || (owner.grow && value !== lastValue);
94372     },
94373
94374
94375     /**
94376      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
94377      * {@link Ext.form.field.Text#grow} configurations.
94378      * @param {Number} width The bodyEl width
94379      * @param {Number} height The bodyEl height
94380      */
94381     sizeBodyContents: function(width, height) {
94382         var size = this.adjustForGrow(width, height);
94383         this.setElementSize(this.owner.inputEl, size[0], size[1]);
94384     },
94385
94386
94387     /**
94388      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94389      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
94390      * @param {Number} width The bodyEl width
94391      * @param {Number} height The bodyEl height
94392      * @return {Number[]} [inputElWidth, inputElHeight]
94393      */
94394     adjustForGrow: function(width, height) {
94395         var me = this,
94396             owner = me.owner,
94397             inputEl, value, calcWidth,
94398             result = [width, height];
94399
94400         if (owner.grow) {
94401             inputEl = owner.inputEl;
94402
94403             // Find the width that contains the whole text value
94404             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
94405             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
94406
94407             // Constrain
94408             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
94409                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
94410         }
94411
94412         return result;
94413     }
94414
94415 });
94416
94417 /**
94418  * @private
94419  * @class Ext.layout.component.field.TextArea
94420  * @extends Ext.layout.component.field.Field
94421  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
94422  */
94423 Ext.define('Ext.layout.component.field.TextArea', {
94424     extend: 'Ext.layout.component.field.Text',
94425     alias: 'layout.textareafield',
94426
94427     type: 'textareafield',
94428
94429
94430     /**
94431      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94432      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
94433      * textfield layout's implementation to handle height rather than width.
94434      * @param {Number} width The bodyEl width
94435      * @param {Number} height The bodyEl height
94436      * @return {Number[]} [inputElWidth, inputElHeight]
94437      */
94438     adjustForGrow: function(width, height) {
94439         var me = this,
94440             owner = me.owner,
94441             inputEl, value, max,
94442             curWidth, curHeight, calcHeight,
94443             result = [width, height];
94444
94445         if (owner.grow) {
94446             inputEl = owner.inputEl;
94447             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
94448             curHeight = inputEl.getHeight();
94449
94450             // Get and normalize the field value for measurement
94451             value = inputEl.dom.value || '&#160;';
94452             value += owner.growAppend;
94453
94454             // Translate newlines to <br> tags
94455             value = value.replace(/\n/g, '<br>');
94456
94457             // Find the height that contains the whole text value
94458             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
94459                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
94460
94461             // Constrain
94462             max = owner.growMax;
94463             if (Ext.isNumber(height)) {
94464                 max = Math.min(max, height);
94465             }
94466             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
94467         }
94468
94469         return result;
94470     }
94471
94472 });
94473 /**
94474  * @class Ext.layout.container.Anchor
94475  * @extends Ext.layout.container.Container
94476  * 
94477  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
94478  * If the container is resized, all anchored items are automatically rerendered according to their
94479  * `{@link #anchor}` rules.
94480  *
94481  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
94482  * config, and should generally not need to be created directly via the new keyword.
94483  * 
94484  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
94485  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
94486  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
94487  *
94488  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
94489  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
94490  * logic if necessary.  
94491  *
94492  *     @example
94493  *     Ext.create('Ext.Panel', {
94494  *         width: 500,
94495  *         height: 400,
94496  *         title: "AnchorLayout Panel",
94497  *         layout: 'anchor',
94498  *         renderTo: Ext.getBody(),
94499  *         items: [
94500  *             {
94501  *                 xtype: 'panel',
94502  *                 title: '75% Width and 20% Height',
94503  *                 anchor: '75% 20%'
94504  *             },
94505  *             {
94506  *                 xtype: 'panel',
94507  *                 title: 'Offset -300 Width & -200 Height',
94508  *                 anchor: '-300 -200'          
94509  *             },
94510  *             {
94511  *                 xtype: 'panel',
94512  *                 title: 'Mixed Offset and Percent',
94513  *                 anchor: '-250 20%'
94514  *             }
94515  *         ]
94516  *     });
94517  */
94518 Ext.define('Ext.layout.container.Anchor', {
94519
94520     /* Begin Definitions */
94521
94522     alias: 'layout.anchor',
94523     extend: 'Ext.layout.container.Container',
94524     alternateClassName: 'Ext.layout.AnchorLayout',
94525
94526     /* End Definitions */
94527
94528     /**
94529      * @cfg {String} anchor
94530      *
94531      * This configuation option is to be applied to **child `items`** of a container managed by
94532      * this layout (ie. configured with `layout:'anchor'`).
94533      *
94534      * This value is what tells the layout how an item should be anchored to the container. `items`
94535      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
94536      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
94537      * The following types of anchor values are supported:
94538      *
94539      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
94540      *
94541      *   The first anchor is the percentage width that the item should take up within the container, and the
94542      *   second is the percentage height.  For example:
94543      *
94544      *       // two values specified
94545      *       anchor: '100% 50%' // render item complete width of the container and
94546      *                          // 1/2 height of the container
94547      *       // one value specified
94548      *       anchor: '100%'     // the width value; the height will default to auto
94549      *
94550      * - **Offsets** : Any positive or negative integer value.
94551      *
94552      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
94553      *   and the second is the offset from the bottom edge. For example:
94554      *
94555      *       // two values specified
94556      *       anchor: '-50 -100' // render item the complete width of the container
94557      *                          // minus 50 pixels and
94558      *                          // the complete height minus 100 pixels.
94559      *       // one value specified
94560      *       anchor: '-50'      // anchor value is assumed to be the right offset value
94561      *                          // bottom offset will default to 0
94562      *
94563      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
94564      *
94565      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
94566      *   order for these to have any effect.
94567      *   
94568      * - **Mixed** :
94569      *
94570      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
94571      *   right edge by 50 pixels and 75% of the container's height use:
94572      *   
94573      *       anchor:   '-50 75%'
94574      */
94575     type: 'anchor',
94576
94577     /**
94578      * @cfg {String} defaultAnchor
94579      * Default anchor for all child <b>container</b> items applied if no anchor or specific width is set on the child item.  Defaults to '100%'.
94580      */
94581     defaultAnchor: '100%',
94582
94583     parseAnchorRE: /^(r|right|b|bottom)$/i,
94584
94585     // private
94586     onLayout: function() {
94587         this.callParent(arguments);
94588
94589         var me = this,
94590             size = me.getLayoutTargetSize(),
94591             owner = me.owner,
94592             target = me.getTarget(),
94593             ownerWidth = size.width,
94594             ownerHeight = size.height,
94595             overflow = target.getStyle('overflow'),
94596             components = me.getVisibleItems(owner),
94597             len = components.length,
94598             boxes = [],
94599             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
94600             i, el, cleaner;
94601
94602         if (ownerWidth < 20 && ownerHeight < 20) {
94603             return;
94604         }
94605
94606         // Anchor layout uses natural HTML flow to arrange the child items.
94607         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
94608         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
94609         if (!me.clearEl) {
94610             me.clearEl = target.createChild({
94611                 cls: Ext.baseCSSPrefix + 'clear',
94612                 role: 'presentation'
94613             });
94614         }
94615
94616         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94617         if (!Ext.supports.RightMargin) {
94618             cleaner = Ext.Element.getRightMarginFixCleaner(target);
94619             target.addCls(Ext.baseCSSPrefix + 'inline-children');
94620         }
94621
94622         for (i = 0; i < len; i++) {
94623             component = components[i];
94624             el = component.el;
94625
94626             anchorSpec = component.anchorSpec;
94627             if (anchorSpec) {
94628                 if (anchorSpec.right) {
94629                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
94630                 } else {
94631                     calcWidth = undefined;
94632                 }
94633                 if (anchorSpec.bottom) {
94634                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
94635                 } else {
94636                     calcHeight = undefined;
94637                 }
94638
94639                 boxes.push({
94640                     component: component,
94641                     anchor: true,
94642                     width: calcWidth || undefined,
94643                     height: calcHeight || undefined
94644                 });
94645             } else {
94646                 boxes.push({
94647                     component: component,
94648                     anchor: false
94649                 });
94650             }
94651         }
94652
94653         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94654         if (!Ext.supports.RightMargin) {
94655             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
94656             cleaner();
94657         }
94658
94659         for (i = 0; i < len; i++) {
94660             box = boxes[i];
94661             me.setItemSize(box.component, box.width, box.height);
94662         }
94663
94664         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
94665             newTargetSize = me.getLayoutTargetSize();
94666             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
94667                 me.adjustmentPass = true;
94668                 me.onLayout();
94669             }
94670         }
94671
94672         delete me.adjustmentPass;
94673     },
94674
94675     // private
94676     parseAnchor: function(a, start, cstart) {
94677         if (a && a != 'none') {
94678             var ratio;
94679             // standard anchor
94680             if (this.parseAnchorRE.test(a)) {
94681                 var diff = cstart - start;
94682                 return function(v) {
94683                     return v - diff;
94684                 };
94685             }    
94686             // percentage
94687             else if (a.indexOf('%') != -1) {
94688                 ratio = parseFloat(a.replace('%', '')) * 0.01;
94689                 return function(v) {
94690                     return Math.floor(v * ratio);
94691                 };
94692             }    
94693             // simple offset adjustment
94694             else {
94695                 a = parseInt(a, 10);
94696                 if (!isNaN(a)) {
94697                     return function(v) {
94698                         return v + a;
94699                     };
94700                 }
94701             }
94702         }
94703         return null;
94704     },
94705
94706     // private
94707     adjustWidthAnchor: function(value, comp) {
94708         return value;
94709     },
94710
94711     // private
94712     adjustHeightAnchor: function(value, comp) {
94713         return value;
94714     },
94715
94716     configureItem: function(item) {
94717         var me = this,
94718             owner = me.owner,
94719             anchor= item.anchor,
94720             anchorsArray,
94721             anchorSpec,
94722             anchorWidth,
94723             anchorHeight;
94724
94725         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
94726             item.anchor = anchor = me.defaultAnchor;
94727         }
94728
94729         // find the container anchoring size
94730         if (owner.anchorSize) {
94731             if (typeof owner.anchorSize == 'number') {
94732                 anchorWidth = owner.anchorSize;
94733             }
94734             else {
94735                 anchorWidth = owner.anchorSize.width;
94736                 anchorHeight = owner.anchorSize.height;
94737             }
94738         }
94739         else {
94740             anchorWidth = owner.initialConfig.width;
94741             anchorHeight = owner.initialConfig.height;
94742         }
94743
94744         if (anchor) {
94745             // cache all anchor values
94746             anchorsArray = anchor.split(' ');
94747             item.anchorSpec = anchorSpec = {
94748                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
94749                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
94750             };
94751
94752             if (anchorSpec.right) {
94753                 item.layoutManagedWidth = 1;
94754             } else {
94755                 item.layoutManagedWidth = 2;
94756             }
94757
94758             if (anchorSpec.bottom) {
94759                 item.layoutManagedHeight = 1;
94760             } else {
94761                 item.layoutManagedHeight = 2;
94762             }
94763         } else {
94764             item.layoutManagedWidth = 2;
94765             item.layoutManagedHeight = 2;
94766         }
94767         this.callParent(arguments);
94768     }
94769
94770 });
94771 /**
94772  * @class Ext.form.action.Load
94773  * @extends Ext.form.action.Action
94774  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
94775  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
94776  * {@link Ext.form.Basic#load load}ing.</p>
94777  * <p><u><b>Response Packet Criteria</b></u></p>
94778  * <p>A response packet <b>must</b> contain:
94779  * <div class="mdetail-params"><ul>
94780  * <li><b><code>success</code></b> property : Boolean</li>
94781  * <li><b><code>data</code></b> property : Object</li>
94782  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
94783  * The individual value object for each Field is passed to the Field's
94784  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
94785  * </ul></div>
94786  * <p><u><b>JSON Packets</b></u></p>
94787  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
94788 var myFormPanel = new Ext.form.Panel({
94789     title: 'Client and routing info',
94790     items: [{
94791         fieldLabel: 'Client',
94792         name: 'clientName'
94793     }, {
94794         fieldLabel: 'Port of loading',
94795         name: 'portOfLoading'
94796     }, {
94797         fieldLabel: 'Port of discharge',
94798         name: 'portOfDischarge'
94799     }]
94800 });
94801 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
94802     url: '/getRoutingInfo.php',
94803     params: {
94804         consignmentRef: myConsignmentRef
94805     },
94806     failure: function(form, action) {
94807         Ext.Msg.alert("Load failed", action.result.errorMessage);
94808     }
94809 });
94810 </code></pre>
94811  * a <b>success response</b> packet may look like this:</p><pre><code>
94812 {
94813     success: true,
94814     data: {
94815         clientName: "Fred. Olsen Lines",
94816         portOfLoading: "FXT",
94817         portOfDischarge: "OSL"
94818     }
94819 }</code></pre>
94820  * while a <b>failure response</b> packet may look like this:</p><pre><code>
94821 {
94822     success: false,
94823     errorMessage: "Consignment reference not found"
94824 }</code></pre>
94825  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
94826  * callback or event handler methods. The object decoded from this JSON is available in the
94827  * {@link Ext.form.action.Action#result result} property.</p>
94828  */
94829 Ext.define('Ext.form.action.Load', {
94830     extend:'Ext.form.action.Action',
94831     requires: ['Ext.data.Connection'],
94832     alternateClassName: 'Ext.form.Action.Load',
94833     alias: 'formaction.load',
94834
94835     type: 'load',
94836
94837     /**
94838      * @private
94839      */
94840     run: function() {
94841         Ext.Ajax.request(Ext.apply(
94842             this.createCallback(),
94843             {
94844                 method: this.getMethod(),
94845                 url: this.getUrl(),
94846                 headers: this.headers,
94847                 params: this.getParams()
94848             }
94849         ));
94850     },
94851
94852     /**
94853      * @private
94854      */
94855     onSuccess: function(response){
94856         var result = this.processResponse(response),
94857             form = this.form;
94858         if (result === true || !result.success || !result.data) {
94859             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
94860             form.afterAction(this, false);
94861             return;
94862         }
94863         form.clearInvalid();
94864         form.setValues(result.data);
94865         form.afterAction(this, true);
94866     },
94867
94868     /**
94869      * @private
94870      */
94871     handleResponse: function(response) {
94872         var reader = this.form.reader,
94873             rs, data;
94874         if (reader) {
94875             rs = reader.read(response);
94876             data = rs.records && rs.records[0] ? rs.records[0].data : null;
94877             return {
94878                 success : rs.success,
94879                 data : data
94880             };
94881         }
94882         return Ext.decode(response.responseText);
94883     }
94884 });
94885
94886
94887 /**
94888  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
94889  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
94890  * their prior size, and can be {@link #minimize}d.
94891  *
94892  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
94893  * grouping, activation, to front, to back and other application-specific behavior.
94894  *
94895  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
94896  * {@link Ext.Component#renderTo renderTo}.
94897  *
94898  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
94899  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
94900  * in the required manner.**
94901  *
94902  *     @example
94903  *     Ext.create('Ext.window.Window', {
94904  *         title: 'Hello',
94905  *         height: 200,
94906  *         width: 400,
94907  *         layout: 'fit',
94908  *         items: {  // Let's put an empty grid in just to illustrate fit layout
94909  *             xtype: 'grid',
94910  *             border: false,
94911  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
94912  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
94913  *         }
94914  *     }).show();
94915  */
94916 Ext.define('Ext.window.Window', {
94917     extend: 'Ext.panel.Panel',
94918
94919     alternateClassName: 'Ext.Window',
94920
94921     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
94922
94923     alias: 'widget.window',
94924
94925     /**
94926      * @cfg {Number} x
94927      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
94928      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94929      */
94930
94931     /**
94932      * @cfg {Number} y
94933      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
94934      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94935      */
94936
94937     /**
94938      * @cfg {Boolean} [modal=false]
94939      * True to make the window modal and mask everything behind it when displayed, false to display it without
94940      * restricting access to other UI elements.
94941      */
94942
94943     /**
94944      * @cfg {String/Ext.Element} [animateTarget=null]
94945      * Id or element from which the window should animate while opening.
94946      */
94947
94948     /**
94949      * @cfg {String/Number/Ext.Component} defaultFocus
94950      * Specifies a Component to receive focus when this Window is focused.
94951      *
94952      * This may be one of:
94953      *
94954      *   - The index of a footer Button.
94955      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
94956      *   - A Component.
94957      */
94958
94959     /**
94960      * @cfg {Function} onEsc
94961      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
94962      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
94963      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
94964      */
94965
94966     /**
94967      * @cfg {Boolean} [collapsed=false]
94968      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
94969      * is true (the default) it will override the `collapsed` config and the window will always be
94970      * expanded when shown.
94971      */
94972
94973     /**
94974      * @cfg {Boolean} [maximized=false]
94975      * True to initially display the window in a maximized state.
94976      */
94977
94978     /**
94979     * @cfg {String} [baseCls='x-window']
94980     * The base CSS class to apply to this panel's element.
94981     */
94982     baseCls: Ext.baseCSSPrefix + 'window',
94983
94984     /**
94985      * @cfg {Boolean/Object} resizable
94986      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
94987      *
94988      * This may also be specified as a config object to Ext.resizer.Resizer
94989      */
94990     resizable: true,
94991
94992     /**
94993      * @cfg {Boolean} draggable
94994      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
94995      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
94996      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
94997      */
94998     draggable: true,
94999
95000     /**
95001      * @cfg {Boolean} constrain
95002      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
95003      * element. By default the window will be rendered to document.body. To render and constrain the window within
95004      * another element specify {@link #renderTo}. Optionally the header only can be constrained
95005      * using {@link #constrainHeader}.
95006      */
95007     constrain: false,
95008
95009     /**
95010      * @cfg {Boolean} constrainHeader
95011      * True to constrain the window header within its containing element (allowing the window body to fall outside of
95012      * its containing element) or false to allow the header to fall outside its containing element.
95013      * Optionally the entire window can be constrained using {@link #constrain}.
95014      */
95015     constrainHeader: false,
95016
95017     /**
95018      * @cfg {Boolean} plain
95019      * True to render the window body with a transparent background so that it will blend into the framing elements,
95020      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
95021      * from the surrounding frame.
95022      */
95023     plain: false,
95024
95025     /**
95026      * @cfg {Boolean} minimizable
95027      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
95028      * and disallow minimizing the window. Note that this button provides no implementation -- the
95029      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
95030      * minimize behavior implemented for this option to be useful.
95031      */
95032     minimizable: false,
95033
95034     /**
95035      * @cfg {Boolean} maximizable
95036      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
95037      * and disallow maximizing the window. Note that when a window is maximized, the tool button
95038      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
95039      * the window to its previous size.
95040      */
95041     maximizable: false,
95042
95043     // inherit docs
95044     minHeight: 100,
95045
95046     // inherit docs
95047     minWidth: 200,
95048
95049     /**
95050      * @cfg {Boolean} expandOnShow
95051      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
95052      * {@link #collapsed}) when displayed.
95053      */
95054     expandOnShow: true,
95055
95056     // inherited docs, same default
95057     collapsible: false,
95058
95059     /**
95060      * @cfg {Boolean} closable
95061      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
95062      * disallow closing the window.
95063      *
95064      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
95065      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
95066      * Window and its content meaning that it may not be reused.
95067      *
95068      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
95069      */
95070     closable: true,
95071
95072     /**
95073      * @cfg {Boolean} hidden
95074      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
95075      */
95076     hidden: true,
95077
95078     // Inherit docs from Component. Windows render to the body on first show.
95079     autoRender: true,
95080
95081     // Inherit docs from Component. Windows hide using visibility.
95082     hideMode: 'visibility',
95083
95084     /** @cfg {Boolean} floating @hide Windows are always floating*/
95085     floating: true,
95086
95087     ariaRole: 'alertdialog',
95088
95089     itemCls: 'x-window-item',
95090
95091     overlapHeader: true,
95092
95093     ignoreHeaderBorderManagement: true,
95094
95095     // private
95096     initComponent: function() {
95097         var me = this;
95098         me.callParent();
95099         me.addEvents(
95100             /**
95101              * @event activate
95102              * Fires after the window has been visually activated via {@link #setActive}.
95103              * @param {Ext.window.Window} this
95104              */
95105
95106             /**
95107              * @event deactivate
95108              * Fires after the window has been visually deactivated via {@link #setActive}.
95109              * @param {Ext.window.Window} this
95110              */
95111
95112             /**
95113              * @event resize
95114              * Fires after the window has been resized.
95115              * @param {Ext.window.Window} this
95116              * @param {Number} width The window's new width
95117              * @param {Number} height The window's new height
95118              */
95119             'resize',
95120
95121             /**
95122              * @event maximize
95123              * Fires after the window has been maximized.
95124              * @param {Ext.window.Window} this
95125              */
95126             'maximize',
95127
95128             /**
95129              * @event minimize
95130              * Fires after the window has been minimized.
95131              * @param {Ext.window.Window} this
95132              */
95133             'minimize',
95134
95135             /**
95136              * @event restore
95137              * Fires after the window has been restored to its original size after being maximized.
95138              * @param {Ext.window.Window} this
95139              */
95140             'restore'
95141         );
95142
95143         if (me.plain) {
95144             me.addClsWithUI('plain');
95145         }
95146
95147         if (me.modal) {
95148             me.ariaRole = 'dialog';
95149         }
95150     },
95151
95152     // State Management
95153     // private
95154
95155     initStateEvents: function(){
95156         var events = this.stateEvents;
95157         // push on stateEvents if they don't exist
95158         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
95159             if (Ext.Array.indexOf(events, event)) {
95160                 events.push(event);
95161             }
95162         });
95163         this.callParent();
95164     },
95165
95166     getState: function() {
95167         var me = this,
95168             state = me.callParent() || {},
95169             maximized = !!me.maximized;
95170
95171         state.maximized = maximized;
95172         Ext.apply(state, {
95173             size: maximized ? me.restoreSize : me.getSize(),
95174             pos: maximized ? me.restorePos : me.getPosition()
95175         });
95176         return state;
95177     },
95178
95179     applyState: function(state){
95180         var me = this;
95181
95182         if (state) {
95183             me.maximized = state.maximized;
95184             if (me.maximized) {
95185                 me.hasSavedRestore = true;
95186                 me.restoreSize = state.size;
95187                 me.restorePos = state.pos;
95188             } else {
95189                 Ext.apply(me, {
95190                     width: state.size.width,
95191                     height: state.size.height,
95192                     x: state.pos[0],
95193                     y: state.pos[1]
95194                 });
95195             }
95196         }
95197     },
95198
95199     // private
95200     onMouseDown: function (e) {
95201         var preventFocus;
95202             
95203         if (this.floating) {
95204             if (Ext.fly(e.getTarget()).focusable()) {
95205                 preventFocus = true;
95206             }
95207             this.toFront(preventFocus);
95208         }
95209     },
95210
95211     // private
95212     onRender: function(ct, position) {
95213         var me = this;
95214         me.callParent(arguments);
95215         me.focusEl = me.el;
95216
95217         // Double clicking a header will toggleMaximize
95218         if (me.maximizable) {
95219             me.header.on({
95220                 dblclick: {
95221                     fn: me.toggleMaximize,
95222                     element: 'el',
95223                     scope: me
95224                 }
95225             });
95226         }
95227     },
95228
95229     // private
95230     afterRender: function() {
95231         var me = this,
95232             hidden = me.hidden,
95233             keyMap;
95234
95235         me.hidden = false;
95236         // Component's afterRender sizes and positions the Component
95237         me.callParent();
95238         me.hidden = hidden;
95239
95240         // Create the proxy after the size has been applied in Component.afterRender
95241         me.proxy = me.getProxy();
95242
95243         // clickToRaise
95244         me.mon(me.el, 'mousedown', me.onMouseDown, me);
95245         
95246         // allow the element to be focusable
95247         me.el.set({
95248             tabIndex: -1
95249         });
95250
95251         // Initialize
95252         if (me.maximized) {
95253             me.maximized = false;
95254             me.maximize();
95255         }
95256
95257         if (me.closable) {
95258             keyMap = me.getKeyMap();
95259             keyMap.on(27, me.onEsc, me);
95260
95261             //if (hidden) { ? would be consistent w/before/afterShow...
95262                 keyMap.disable();
95263             //}
95264         }
95265
95266         if (!hidden) {
95267             me.syncMonitorWindowResize();
95268             me.doConstrain();
95269         }
95270     },
95271
95272     /**
95273      * @private
95274      * @override
95275      * Override Component.initDraggable.
95276      * Window uses the header element as the delegate.
95277      */
95278     initDraggable: function() {
95279         var me = this,
95280             ddConfig;
95281
95282         if (!me.header) {
95283             me.updateHeader(true);
95284         }
95285
95286         /*
95287          * Check the header here again. If for whatever reason it wasn't created in
95288          * updateHeader (preventHeader) then we'll just ignore the rest since the
95289          * header acts as the drag handle.
95290          */
95291         if (me.header) {
95292             ddConfig = Ext.applyIf({
95293                 el: me.el,
95294                 delegate: '#' + me.header.id
95295             }, me.draggable);
95296
95297             // Add extra configs if Window is specified to be constrained
95298             if (me.constrain || me.constrainHeader) {
95299                 ddConfig.constrain = me.constrain;
95300                 ddConfig.constrainDelegate = me.constrainHeader;
95301                 ddConfig.constrainTo = me.constrainTo || me.container;
95302             }
95303
95304             /**
95305              * @property {Ext.util.ComponentDragger} dd
95306              * If this Window is configured {@link #draggable}, this property will contain an instance of
95307              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
95308              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
95309              *
95310              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
95311              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
95312              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
95313              */
95314             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
95315             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
95316         }
95317     },
95318
95319     // private
95320     onEsc: function(k, e) {
95321         e.stopEvent();
95322         this[this.closeAction]();
95323     },
95324
95325     // private
95326     beforeDestroy: function() {
95327         var me = this;
95328         if (me.rendered) {
95329             delete this.animateTarget;
95330             me.hide();
95331             Ext.destroy(
95332                 me.keyMap
95333             );
95334         }
95335         me.callParent();
95336     },
95337
95338     /**
95339      * @private
95340      * @override
95341      * Contribute class-specific tools to the header.
95342      * Called by Panel's initTools.
95343      */
95344     addTools: function() {
95345         var me = this;
95346
95347         // Call Panel's initTools
95348         me.callParent();
95349
95350         if (me.minimizable) {
95351             me.addTool({
95352                 type: 'minimize',
95353                 handler: Ext.Function.bind(me.minimize, me, [])
95354             });
95355         }
95356         if (me.maximizable) {
95357             me.addTool({
95358                 type: 'maximize',
95359                 handler: Ext.Function.bind(me.maximize, me, [])
95360             });
95361             me.addTool({
95362                 type: 'restore',
95363                 handler: Ext.Function.bind(me.restore, me, []),
95364                 hidden: true
95365             });
95366         }
95367     },
95368
95369     /**
95370      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
95371      * Container itself will receive focus.
95372      */
95373     getFocusEl: function() {
95374         var me = this,
95375             f = me.focusEl,
95376             defaultComp = me.defaultButton || me.defaultFocus,
95377             t = typeof db,
95378             el,
95379             ct;
95380
95381         if (Ext.isDefined(defaultComp)) {
95382             if (Ext.isNumber(defaultComp)) {
95383                 f = me.query('button')[defaultComp];
95384             } else if (Ext.isString(defaultComp)) {
95385                 f = me.down('#' + defaultComp);
95386             } else {
95387                 f = defaultComp;
95388             }
95389         }
95390         return f || me.focusEl;
95391     },
95392
95393     // private
95394     beforeShow: function() {
95395         this.callParent();
95396
95397         if (this.expandOnShow) {
95398             this.expand(false);
95399         }
95400     },
95401
95402     // private
95403     afterShow: function(animateTarget) {
95404         var me = this,
95405             animating = animateTarget || me.animateTarget;
95406
95407
95408         // No constraining code needs to go here.
95409         // Component.onShow constrains the Component. *If the constrain config is true*
95410
95411         // Perform superclass's afterShow tasks
95412         // Which might include animating a proxy from an animateTarget
95413         me.callParent(arguments);
95414
95415         if (me.maximized) {
95416             me.fitContainer();
95417         }
95418
95419         me.syncMonitorWindowResize();
95420         if (!animating) {
95421             me.doConstrain();
95422         }
95423
95424         if (me.keyMap) {
95425             me.keyMap.enable();
95426         }
95427     },
95428
95429     // private
95430     doClose: function() {
95431         var me = this;
95432
95433         // Being called as callback after going through the hide call below
95434         if (me.hidden) {
95435             me.fireEvent('close', me);
95436             if (me.closeAction == 'destroy') {
95437                 this.destroy();
95438             }
95439         } else {
95440             // close after hiding
95441             me.hide(me.animateTarget, me.doClose, me);
95442         }
95443     },
95444
95445     // private
95446     afterHide: function() {
95447         var me = this;
95448
95449         // No longer subscribe to resizing now that we're hidden
95450         me.syncMonitorWindowResize();
95451
95452         // Turn off keyboard handling once window is hidden
95453         if (me.keyMap) {
95454             me.keyMap.disable();
95455         }
95456
95457         // Perform superclass's afterHide tasks.
95458         me.callParent(arguments);
95459     },
95460
95461     // private
95462     onWindowResize: function() {
95463         if (this.maximized) {
95464             this.fitContainer();
95465         }
95466         this.doConstrain();
95467     },
95468
95469     /**
95470      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
95471      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
95472      * the minimize event can be handled or this method can be overridden.
95473      * @return {Ext.window.Window} this
95474      */
95475     minimize: function() {
95476         this.fireEvent('minimize', this);
95477         return this;
95478     },
95479
95480     afterCollapse: function() {
95481         var me = this;
95482
95483         if (me.maximizable) {
95484             me.tools.maximize.hide();
95485             me.tools.restore.hide();
95486         }
95487         if (me.resizer) {
95488             me.resizer.disable();
95489         }
95490         me.callParent(arguments);
95491     },
95492
95493     afterExpand: function() {
95494         var me = this;
95495
95496         if (me.maximized) {
95497             me.tools.restore.show();
95498         } else if (me.maximizable) {
95499             me.tools.maximize.show();
95500         }
95501         if (me.resizer) {
95502             me.resizer.enable();
95503         }
95504         me.callParent(arguments);
95505     },
95506
95507     /**
95508      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
95509      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
95510      * @return {Ext.window.Window} this
95511      */
95512     maximize: function() {
95513         var me = this;
95514
95515         if (!me.maximized) {
95516             me.expand(false);
95517             if (!me.hasSavedRestore) {
95518                 me.restoreSize = me.getSize();
95519                 me.restorePos = me.getPosition(true);
95520             }
95521             if (me.maximizable) {
95522                 me.tools.maximize.hide();
95523                 me.tools.restore.show();
95524             }
95525             me.maximized = true;
95526             me.el.disableShadow();
95527
95528             if (me.dd) {
95529                 me.dd.disable();
95530             }
95531             if (me.collapseTool) {
95532                 me.collapseTool.hide();
95533             }
95534             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
95535             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95536
95537             me.syncMonitorWindowResize();
95538             me.setPosition(0, 0);
95539             me.fitContainer();
95540             me.fireEvent('maximize', me);
95541         }
95542         return me;
95543     },
95544
95545     /**
95546      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
95547      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
95548      * @return {Ext.window.Window} this
95549      */
95550     restore: function() {
95551         var me = this,
95552             tools = me.tools;
95553
95554         if (me.maximized) {
95555             delete me.hasSavedRestore;
95556             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
95557
95558             // Toggle tool visibility
95559             if (tools.restore) {
95560                 tools.restore.hide();
95561             }
95562             if (tools.maximize) {
95563                 tools.maximize.show();
95564             }
95565             if (me.collapseTool) {
95566                 me.collapseTool.show();
95567             }
95568
95569             // Restore the position/sizing
95570             me.setPosition(me.restorePos);
95571             me.setSize(me.restoreSize);
95572
95573             // Unset old position/sizing
95574             delete me.restorePos;
95575             delete me.restoreSize;
95576
95577             me.maximized = false;
95578
95579             me.el.enableShadow(true);
95580
95581             // Allow users to drag and drop again
95582             if (me.dd) {
95583                 me.dd.enable();
95584             }
95585
95586             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95587
95588             me.syncMonitorWindowResize();
95589             me.doConstrain();
95590             me.fireEvent('restore', me);
95591         }
95592         return me;
95593     },
95594
95595     /**
95596      * Synchronizes the presence of our listener for window resize events. This method
95597      * should be called whenever this status might change.
95598      * @private
95599      */
95600     syncMonitorWindowResize: function () {
95601         var me = this,
95602             currentlyMonitoring = me._monitoringResize,
95603             // all the states where we should be listening to window resize:
95604             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
95605             // all the states where we veto this:
95606             veto = me.hidden || me.destroying || me.isDestroyed;
95607
95608         if (yes && !veto) {
95609             // we should be listening...
95610             if (!currentlyMonitoring) {
95611                 // but we aren't, so set it up
95612                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
95613                 me._monitoringResize = true;
95614             }
95615         } else if (currentlyMonitoring) {
95616             // we should not be listening, but we are, so tear it down
95617             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
95618             me._monitoringResize = false;
95619         }
95620     },
95621
95622     /**
95623      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
95624      * state of the window.
95625      * @return {Ext.window.Window} this
95626      */
95627     toggleMaximize: function() {
95628         return this[this.maximized ? 'restore': 'maximize']();
95629     }
95630
95631     /**
95632      * @cfg {Boolean} autoWidth @hide
95633      * Absolute positioned element and therefore cannot support autoWidth.
95634      * A width is a required configuration.
95635      **/
95636 });
95637
95638 /**
95639  * @docauthor Jason Johnston <jason@sencha.com>
95640  *
95641  * Base class for form fields that provides default event handling, rendering, and other common functionality
95642  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
95643  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
95644  *
95645  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
95646  * rather than creating instances of this class directly. However if you are implementing a custom form field,
95647  * using this as the parent class is recommended.
95648  *
95649  * # Values and Conversions
95650  *
95651  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
95652  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
95653  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
95654  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
95655  * input, this value data type can not always be directly used in the rendered field.
95656  *
95657  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
95658  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
95659  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
95660  *
95661  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
95662  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
95663  * should override these methods to handle the conversion.
95664  *
95665  * # Rendering
95666  *
95667  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
95668  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
95669  * field renderings.
95670  *
95671  * # Example usage:
95672  *
95673  *     @example
95674  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
95675  *     // searchUrl when the Enter key is pressed.222
95676  *     Ext.define('Ext.form.SearchField', {
95677  *         extend: 'Ext.form.field.Base',
95678  *         alias: 'widget.searchfield',
95679  *     
95680  *         inputType: 'search',
95681  *     
95682  *         // Config defining the search URL
95683  *         searchUrl: 'http://www.google.com/search?q={0}',
95684  *     
95685  *         // Add specialkey listener
95686  *         initComponent: function() {
95687  *             this.callParent();
95688  *             this.on('specialkey', this.checkEnterKey, this);
95689  *         },
95690  *     
95691  *         // Handle enter key presses, execute the search if the field has a value
95692  *         checkEnterKey: function(field, e) {
95693  *             var value = this.getValue();
95694  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
95695  *                 location.href = Ext.String.format(this.searchUrl, value);
95696  *             }
95697  *         }
95698  *     });
95699  *     
95700  *     Ext.create('Ext.form.Panel', {
95701  *         title: 'BaseField Example',
95702  *         bodyPadding: 5,
95703  *         width: 250,
95704  *     
95705  *         // Fields will be arranged vertically, stretched to full width
95706  *         layout: 'anchor',
95707  *         defaults: {
95708  *             anchor: '100%'
95709  *         },
95710  *         items: [{
95711  *             xtype: 'searchfield',
95712  *             fieldLabel: 'Search',
95713  *             name: 'query'
95714  *         }],
95715  *         renderTo: Ext.getBody()
95716  *     });
95717  */
95718 Ext.define('Ext.form.field.Base', {
95719     extend: 'Ext.Component',
95720     mixins: {
95721         labelable: 'Ext.form.Labelable',
95722         field: 'Ext.form.field.Field'
95723     },
95724     alias: 'widget.field',
95725     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
95726     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
95727
95728     /**
95729      * @cfg {Ext.XTemplate} fieldSubTpl
95730      * The content of the field body is defined by this config option.
95731      */
95732     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
95733         '<input id="{id}" type="{type}" ',
95734         '<tpl if="name">name="{name}" </tpl>',
95735         '<tpl if="size">size="{size}" </tpl>',
95736         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
95737         'class="{fieldCls} {typeCls}" autocomplete="off" />',
95738         {
95739             compiled: true,
95740             disableFormats: true
95741         }
95742     ],
95743
95744     /**
95745      * @cfg {String} name
95746      * The name of the field. This is used as the parameter name when including the field value
95747      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
95748      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
95749      */
95750
95751     /**
95752      * @cfg {String} inputType
95753      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
95754      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
95755      * fall back to 'text'.
95756      *
95757      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
95758      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
95759      * a plain unstyled file input you can use a BaseField with inputType:'file'.
95760      */
95761     inputType: 'text',
95762
95763     /**
95764      * @cfg {Number} tabIndex
95765      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
95766      * applyTo
95767      */
95768
95769     /**
95770      * @cfg {String} invalidText
95771      * The error text to use when marking a field invalid and no message is provided
95772      */
95773     invalidText : 'The value in this field is invalid',
95774
95775     /**
95776      * @cfg {String} [fieldCls='x-form-field']
95777      * The default CSS class for the field input
95778      */
95779     fieldCls : Ext.baseCSSPrefix + 'form-field',
95780
95781     /**
95782      * @cfg {String} fieldStyle
95783      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
95784      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
95785      * the style after initialization.
95786      */
95787
95788     /**
95789      * @cfg {String} [focusCls='x-form-focus']
95790      * The CSS class to use when the field receives focus
95791      */
95792     focusCls : Ext.baseCSSPrefix + 'form-focus',
95793
95794     /**
95795      * @cfg {String} dirtyCls
95796      * The CSS class to use when the field value {@link #isDirty is dirty}.
95797      */
95798     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
95799
95800     /**
95801      * @cfg {String[]} checkChangeEvents
95802      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
95803      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
95804      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
95805      *
95806      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
95807      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
95808      * browsers; the only known exceptions at the time of writing are:
95809      *
95810      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
95811      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
95812      *     and textareas
95813      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
95814      *
95815      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
95816      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
95817      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
95818      * such a task automatically.
95819      */
95820     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
95821                         ['change', 'propertychange'] :
95822                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
95823
95824     /**
95825      * @cfg {Number} checkChangeBuffer
95826      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
95827      * Defaults to 50 milliseconds.
95828      */
95829     checkChangeBuffer: 50,
95830
95831     componentLayout: 'field',
95832
95833     /**
95834      * @cfg {Boolean} readOnly
95835      * true to mark the field as readOnly in HTML.
95836      *
95837      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
95838      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
95839      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
95840      */
95841     readOnly : false,
95842
95843     /**
95844      * @cfg {String} readOnlyCls
95845      * The CSS class applied to the component's main element when it is {@link #readOnly}.
95846      */
95847     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
95848
95849     /**
95850      * @cfg {String} inputId
95851      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
95852      * configure this manually, you must make sure it is unique in the document.
95853      */
95854
95855     /**
95856      * @cfg {Boolean} validateOnBlur
95857      * Whether the field should validate when it loses focus. This will cause fields to be validated
95858      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
95859      * along the way. See also {@link #validateOnChange}.
95860      */
95861     validateOnBlur: true,
95862
95863     // private
95864     hasFocus : false,
95865
95866     baseCls: Ext.baseCSSPrefix + 'field',
95867
95868     maskOnDisable: false,
95869
95870     // private
95871     initComponent : function() {
95872         var me = this;
95873
95874         me.callParent();
95875
95876         me.subTplData = me.subTplData || {};
95877
95878         me.addEvents(
95879             /**
95880              * @event focus
95881              * Fires when this field receives input focus.
95882              * @param {Ext.form.field.Base} this
95883              */
95884             'focus',
95885             /**
95886              * @event blur
95887              * Fires when this field loses input focus.
95888              * @param {Ext.form.field.Base} this
95889              */
95890             'blur',
95891             /**
95892              * @event specialkey
95893              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
95894              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
95895              * pressed. For example:
95896              *
95897              *     var form = new Ext.form.Panel({
95898              *         ...
95899              *         items: [{
95900              *                 fieldLabel: 'Field 1',
95901              *                 name: 'field1',
95902              *                 allowBlank: false
95903              *             },{
95904              *                 fieldLabel: 'Field 2',
95905              *                 name: 'field2',
95906              *                 listeners: {
95907              *                     specialkey: function(field, e){
95908              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
95909              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
95910              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
95911              *                             var form = field.up('form').getForm();
95912              *                             form.submit();
95913              *                         }
95914              *                     }
95915              *                 }
95916              *             }
95917              *         ],
95918              *         ...
95919              *     });
95920              *
95921              * @param {Ext.form.field.Base} this
95922              * @param {Ext.EventObject} e The event object
95923              */
95924             'specialkey'
95925         );
95926
95927         // Init mixins
95928         me.initLabelable();
95929         me.initField();
95930
95931         // Default name to inputId
95932         if (!me.name) {
95933             me.name = me.getInputId();
95934         }
95935     },
95936
95937     /**
95938      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
95939      * automatically generated.
95940      */
95941     getInputId: function() {
95942         return this.inputId || (this.inputId = Ext.id());
95943     },
95944
95945     /**
95946      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
95947      * @return {Object} The template data
95948      * @template
95949      */
95950     getSubTplData: function() {
95951         var me = this,
95952             type = me.inputType,
95953             inputId = me.getInputId();
95954
95955         return Ext.applyIf(me.subTplData, {
95956             id: inputId,
95957             cmpId: me.id,
95958             name: me.name || inputId,
95959             type: type,
95960             size: me.size || 20,
95961             cls: me.cls,
95962             fieldCls: me.fieldCls,
95963             tabIdx: me.tabIndex,
95964             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
95965         });
95966     },
95967
95968     afterRender: function() {
95969         this.callParent();
95970         
95971         if (this.inputEl) {
95972             this.inputEl.selectable();
95973         }
95974     },
95975
95976     /**
95977      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
95978      */
95979     getSubTplMarkup: function() {
95980         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
95981     },
95982
95983     initRenderTpl: function() {
95984         var me = this;
95985         if (!me.hasOwnProperty('renderTpl')) {
95986             me.renderTpl = me.getTpl('labelableRenderTpl');
95987         }
95988         return me.callParent();
95989     },
95990
95991     initRenderData: function() {
95992         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
95993     },
95994
95995     /**
95996      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
95997      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
95998      * Ext.Element#applyStyles}.
95999      */
96000     setFieldStyle: function(style) {
96001         var me = this,
96002             inputEl = me.inputEl;
96003         if (inputEl) {
96004             inputEl.applyStyles(style);
96005         }
96006         me.fieldStyle = style;
96007     },
96008
96009     // private
96010     onRender : function() {
96011         var me = this,
96012             fieldStyle = me.fieldStyle;
96013
96014         me.onLabelableRender();
96015
96016         /**
96017          * @property {Ext.Element} inputEl
96018          * The input Element for this Field. Only available after the field has been rendered.
96019          */
96020         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
96021
96022         me.callParent(arguments);
96023
96024         // Make the stored rawValue get set as the input element's value
96025         me.setRawValue(me.rawValue);
96026
96027         if (me.readOnly) {
96028             me.setReadOnly(true);
96029         }
96030         if (me.disabled) {
96031             me.disable();
96032         }
96033         if (fieldStyle) {
96034             me.setFieldStyle(fieldStyle);
96035         }
96036
96037         me.renderActiveError();
96038     },
96039
96040     initAria: function() {
96041         var me = this;
96042         me.callParent();
96043
96044         // Associate the field to the error message element
96045         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
96046     },
96047
96048     getFocusEl: function() {
96049         return this.inputEl;
96050     },
96051
96052     isFileUpload: function() {
96053         return this.inputType === 'file';
96054     },
96055
96056     extractFileInput: function() {
96057         var me = this,
96058             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
96059             clone;
96060         if (fileInput) {
96061             clone = fileInput.cloneNode(true);
96062             fileInput.parentNode.replaceChild(clone, fileInput);
96063             me.inputEl = Ext.get(clone);
96064         }
96065         return fileInput;
96066     },
96067
96068     // private override to use getSubmitValue() as a convenience
96069     getSubmitData: function() {
96070         var me = this,
96071             data = null,
96072             val;
96073         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
96074             val = me.getSubmitValue();
96075             if (val !== null) {
96076                 data = {};
96077                 data[me.getName()] = val;
96078             }
96079         }
96080         return data;
96081     },
96082
96083     /**
96084      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
96085      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
96086      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
96087      *
96088      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
96089      * successfully {@link #validate validated}.
96090      *
96091      * @return {String} The value to be submitted, or null.
96092      */
96093     getSubmitValue: function() {
96094         return this.processRawValue(this.getRawValue());
96095     },
96096
96097     /**
96098      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
96099      * normalized and converted value see {@link #getValue}.
96100      * @return {String} value The raw String value of the field
96101      */
96102     getRawValue: function() {
96103         var me = this,
96104             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
96105         me.rawValue = v;
96106         return v;
96107     },
96108
96109     /**
96110      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
96111      * validation. To set the value with these additional inspections see {@link #setValue}.
96112      * @param {Object} value The value to set
96113      * @return {Object} value The field value that is set
96114      */
96115     setRawValue: function(value) {
96116         var me = this;
96117         value = Ext.value(value, '');
96118         me.rawValue = value;
96119
96120         // Some Field subclasses may not render an inputEl
96121         if (me.inputEl) {
96122             me.inputEl.dom.value = value;
96123         }
96124         return value;
96125     },
96126
96127     /**
96128      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
96129      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
96130      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
96131      * to a String for display in the field.
96132      *
96133      * See {@link #rawToValue} for the opposite conversion.
96134      *
96135      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
96136      * to an empty string.
96137      *
96138      * @param {Object} value The mixed-type value to convert to the raw representation.
96139      * @return {Object} The converted raw value.
96140      */
96141     valueToRaw: function(value) {
96142         return '' + Ext.value(value, '');
96143     },
96144
96145     /**
96146      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
96147      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
96148      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
96149      *
96150      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
96151      * to the desired object type.
96152      *
96153      * See {@link #valueToRaw} for the opposite conversion.
96154      *
96155      * The base implementation does no conversion, returning the raw value untouched.
96156      *
96157      * @param {Object} rawValue
96158      * @return {Object} The converted value.
96159      */
96160     rawToValue: function(rawValue) {
96161         return rawValue;
96162     },
96163
96164     /**
96165      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
96166      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
96167      * nothing; individual subclasses may override this as needed.
96168      *
96169      * @param {Object} value The unprocessed string value
96170      * @return {Object} The processed string value
96171      */
96172     processRawValue: function(value) {
96173         return value;
96174     },
96175
96176     /**
96177      * Returns the current data value of the field. The type of value returned is particular to the type of the
96178      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
96179      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
96180      * @return {Object} value The field value
96181      */
96182     getValue: function() {
96183         var me = this,
96184             val = me.rawToValue(me.processRawValue(me.getRawValue()));
96185         me.value = val;
96186         return val;
96187     },
96188
96189     /**
96190      * Sets a data value into the field and runs the change detection and validation. To set the value directly
96191      * without these inspections see {@link #setRawValue}.
96192      * @param {Object} value The value to set
96193      * @return {Ext.form.field.Field} this
96194      */
96195     setValue: function(value) {
96196         var me = this;
96197         me.setRawValue(me.valueToRaw(value));
96198         return me.mixins.field.setValue.call(me, value);
96199     },
96200
96201
96202     //private
96203     onDisable: function() {
96204         var me = this,
96205             inputEl = me.inputEl;
96206         me.callParent();
96207         if (inputEl) {
96208             inputEl.dom.disabled = true;
96209         }
96210     },
96211
96212     //private
96213     onEnable: function() {
96214         var me = this,
96215             inputEl = me.inputEl;
96216         me.callParent();
96217         if (inputEl) {
96218             inputEl.dom.disabled = false;
96219         }
96220     },
96221
96222     /**
96223      * Sets the read only state of this field.
96224      * @param {Boolean} readOnly Whether the field should be read only.
96225      */
96226     setReadOnly: function(readOnly) {
96227         var me = this,
96228             inputEl = me.inputEl;
96229         if (inputEl) {
96230             inputEl.dom.readOnly = readOnly;
96231             inputEl.dom.setAttribute('aria-readonly', readOnly);
96232         }
96233         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
96234         me.readOnly = readOnly;
96235     },
96236
96237     // private
96238     fireKey: function(e){
96239         if(e.isSpecialKey()){
96240             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
96241         }
96242     },
96243
96244     // private
96245     initEvents : function(){
96246         var me = this,
96247             inputEl = me.inputEl,
96248             onChangeTask,
96249             onChangeEvent;
96250         if (inputEl) {
96251             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
96252             me.mon(inputEl, 'focus', me.onFocus, me);
96253
96254             // standardise buffer across all browsers + OS-es for consistent event order.
96255             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
96256             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
96257
96258             // listen for immediate value changes
96259             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
96260             me.onChangeEvent = onChangeEvent = function() {
96261                 onChangeTask.delay(me.checkChangeBuffer);
96262             };
96263             Ext.each(me.checkChangeEvents, function(eventName) {
96264                 if (eventName === 'propertychange') {
96265                     me.usesPropertychange = true;
96266                 }
96267                 me.mon(inputEl, eventName, onChangeEvent);
96268             }, me);
96269         }
96270         me.callParent();
96271     },
96272
96273     doComponentLayout: function() {
96274         var me = this,
96275             inputEl = me.inputEl,
96276             usesPropertychange = me.usesPropertychange,
96277             ename = 'propertychange',
96278             onChangeEvent = me.onChangeEvent;
96279
96280         // In IE if propertychange is one of the checkChangeEvents, we need to remove
96281         // the listener prior to layout and re-add it after, to prevent it from firing
96282         // needlessly for attribute and style changes applied to the inputEl.
96283         if (usesPropertychange) {
96284             me.mun(inputEl, ename, onChangeEvent);
96285         }
96286         me.callParent(arguments);
96287         if (usesPropertychange) {
96288             me.mon(inputEl, ename, onChangeEvent);
96289         }
96290     },
96291
96292     // private
96293     preFocus: Ext.emptyFn,
96294
96295     // private
96296     onFocus: function() {
96297         var me = this,
96298             focusCls = me.focusCls,
96299             inputEl = me.inputEl;
96300         me.preFocus();
96301         if (focusCls && inputEl) {
96302             inputEl.addCls(focusCls);
96303         }
96304         if (!me.hasFocus) {
96305             me.hasFocus = true;
96306             me.componentLayout.onFocus();
96307             me.fireEvent('focus', me);
96308         }
96309     },
96310
96311     // private
96312     beforeBlur : Ext.emptyFn,
96313
96314     // private
96315     onBlur : function(){
96316         var me = this,
96317             focusCls = me.focusCls,
96318             inputEl = me.inputEl;
96319
96320         if (me.destroying) {
96321             return;
96322         }
96323
96324         me.beforeBlur();
96325         if (focusCls && inputEl) {
96326             inputEl.removeCls(focusCls);
96327         }
96328         if (me.validateOnBlur) {
96329             me.validate();
96330         }
96331         me.hasFocus = false;
96332         me.fireEvent('blur', me);
96333         me.postBlur();
96334     },
96335
96336     // private
96337     postBlur : Ext.emptyFn,
96338
96339
96340     /**
96341      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
96342      * @param {Boolean} isDirty
96343      */
96344     onDirtyChange: function(isDirty) {
96345         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
96346     },
96347
96348
96349     /**
96350      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
96351      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
96352      * always treated as valid.
96353      *
96354      * @return {Boolean} True if the value is valid, else false
96355      */
96356     isValid : function() {
96357         var me = this;
96358         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
96359     },
96360
96361
96362     /**
96363      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
96364      * {@link #markInvalid} and false is returned, otherwise true is returned.
96365      *
96366      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
96367      * onwards {@link #getErrors} should be overridden instead.
96368      *
96369      * @param {Object} value The value to validate
96370      * @return {Boolean} True if all validations passed, false if one or more failed
96371      */
96372     validateValue: function(value) {
96373         var me = this,
96374             errors = me.getErrors(value),
96375             isValid = Ext.isEmpty(errors);
96376         if (!me.preventMark) {
96377             if (isValid) {
96378                 me.clearInvalid();
96379             } else {
96380                 me.markInvalid(errors);
96381             }
96382         }
96383
96384         return isValid;
96385     },
96386
96387     /**
96388      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
96389      * display the messages and applying {@link #invalidCls} to the field's UI element.
96390      *
96391      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
96392      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
96393      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96394      *
96395      * @param {String/String[]} errors The validation message(s) to display.
96396      */
96397     markInvalid : function(errors) {
96398         // Save the message and fire the 'invalid' event
96399         var me = this,
96400             oldMsg = me.getActiveError();
96401         me.setActiveErrors(Ext.Array.from(errors));
96402         if (oldMsg !== me.getActiveError()) {
96403             me.doComponentLayout();
96404         }
96405     },
96406
96407     /**
96408      * Clear any invalid styles/messages for this field.
96409      *
96410      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
96411      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
96412      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96413      */
96414     clearInvalid : function() {
96415         // Clear the message and fire the 'valid' event
96416         var me = this,
96417             hadError = me.hasActiveError();
96418         me.unsetActiveError();
96419         if (hadError) {
96420             me.doComponentLayout();
96421         }
96422     },
96423
96424     /**
96425      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
96426      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
96427      */
96428     renderActiveError: function() {
96429         var me = this,
96430             hasError = me.hasActiveError();
96431         if (me.inputEl) {
96432             // Add/remove invalid class
96433             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
96434         }
96435         me.mixins.labelable.renderActiveError.call(me);
96436     },
96437
96438
96439     getActionEl: function() {
96440         return this.inputEl || this.el;
96441     }
96442
96443 });
96444
96445 /**
96446  * @docauthor Jason Johnston <jason@sencha.com>
96447  *
96448  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
96449  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
96450  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
96451  *
96452  * # Validation
96453  *
96454  * The Text field has a useful set of validations built in:
96455  *
96456  * - {@link #allowBlank} for making the field required
96457  * - {@link #minLength} for requiring a minimum value length
96458  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
96459  *   as the `maxlength` attribute on the input element)
96460  * - {@link #regex} to specify a custom regular expression for validation
96461  *
96462  * In addition, custom validations may be added:
96463  *
96464  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
96465  *   custom validation logic
96466  * - {@link #validator} allows a custom arbitrary function to be called during validation
96467  *
96468  * The details around how and when each of these validation options get used are described in the
96469  * documentation for {@link #getErrors}.
96470  *
96471  * By default, the field value is checked for validity immediately while the user is typing in the
96472  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
96473  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
96474  * {@link Ext.form.Panel} class documentation.
96475  *
96476  * # Masking and Character Stripping
96477  *
96478  * Text fields can be configured with custom regular expressions to be applied to entered values before
96479  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
96480  *
96481  * # Example usage
96482  *
96483  *     @example
96484  *     Ext.create('Ext.form.Panel', {
96485  *         title: 'Contact Info',
96486  *         width: 300,
96487  *         bodyPadding: 10,
96488  *         renderTo: Ext.getBody(),
96489  *         items: [{
96490  *             xtype: 'textfield',
96491  *             name: 'name',
96492  *             fieldLabel: 'Name',
96493  *             allowBlank: false  // requires a non-empty value
96494  *         }, {
96495  *             xtype: 'textfield',
96496  *             name: 'email',
96497  *             fieldLabel: 'Email Address',
96498  *             vtype: 'email'  // requires value to be a valid email address format
96499  *         }]
96500  *     });
96501  */
96502 Ext.define('Ext.form.field.Text', {
96503     extend:'Ext.form.field.Base',
96504     alias: 'widget.textfield',
96505     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
96506     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
96507
96508     /**
96509      * @cfg {String} vtypeText
96510      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
96511      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
96512      */
96513
96514     /**
96515      * @cfg {RegExp} stripCharsRe
96516      * A JavaScript RegExp object used to strip unwanted content from the value
96517      * before validation. If <tt>stripCharsRe</tt> is specified,
96518      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
96519      * This does not change the value of the field.
96520      */
96521
96522     /**
96523      * @cfg {Number} size
96524      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
96525      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
96526      */
96527     size: 20,
96528
96529     /**
96530      * @cfg {Boolean} [grow=false]
96531      * true if this field should automatically grow and shrink to its content
96532      */
96533
96534     /**
96535      * @cfg {Number} growMin
96536      * The minimum width to allow when `{@link #grow} = true`
96537      */
96538     growMin : 30,
96539
96540     /**
96541      * @cfg {Number} growMax
96542      * The maximum width to allow when `{@link #grow} = true`
96543      */
96544     growMax : 800,
96545
96546     /**
96547      * @cfg {String} growAppend
96548      * A string that will be appended to the field's current value for the purposes of calculating the target field
96549      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
96550      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
96551      * width is adjusted.
96552      */
96553     growAppend: 'W',
96554
96555     /**
96556      * @cfg {String} vtype
96557      * A validation type name as defined in {@link Ext.form.field.VTypes}
96558      */
96559
96560     /**
96561      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
96562      * typed) that do not match.
96563      * Note: It dose not filter characters already in the input.
96564      */
96565
96566     /**
96567      * @cfg {Boolean} [disableKeyFilter=false]
96568      * Specify true to disable input keystroke filtering
96569      */
96570
96571     /**
96572      * @cfg {Boolean} allowBlank
96573      * Specify false to validate that the value's length is > 0
96574      */
96575     allowBlank : true,
96576
96577     /**
96578      * @cfg {Number} minLength
96579      * Minimum input field length required
96580      */
96581     minLength : 0,
96582
96583     /**
96584      * @cfg {Number} maxLength
96585      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
96586      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
96587      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
96588      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
96589      */
96590     maxLength : Number.MAX_VALUE,
96591
96592     /**
96593      * @cfg {Boolean} enforceMaxLength
96594      * True to set the maxLength property on the underlying input field. Defaults to false
96595      */
96596
96597     /**
96598      * @cfg {String} minLengthText
96599      * Error text to display if the **{@link #minLength minimum length}** validation fails.
96600      */
96601     minLengthText : 'The minimum length for this field is {0}',
96602
96603     /**
96604      * @cfg {String} maxLengthText
96605      * Error text to display if the **{@link #maxLength maximum length}** validation fails
96606      */
96607     maxLengthText : 'The maximum length for this field is {0}',
96608
96609     /**
96610      * @cfg {Boolean} [selectOnFocus=false]
96611      * true to automatically select any existing field text when the field receives input focus
96612      */
96613
96614     /**
96615      * @cfg {String} blankText
96616      * The error text to display if the **{@link #allowBlank}** validation fails
96617      */
96618     blankText : 'This field is required',
96619
96620     /**
96621      * @cfg {Function} validator
96622      * A custom validation function to be called during field validation ({@link #getErrors}).
96623      * If specified, this function will be called first, allowing the developer to override the default validation
96624      * process.
96625      *
96626      * This function will be passed the following parameters:
96627      *
96628      * @cfg {Object} validator.value The current field value
96629      * @cfg {Boolean/String} validator.return
96630      *
96631      * - True if the value is valid
96632      * - An error message if the value is invalid
96633      */
96634
96635     /**
96636      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
96637      * If the test fails, the field will be marked invalid using
96638      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
96639      */
96640
96641     /**
96642      * @cfg {String} regexText
96643      * The error text to display if **{@link #regex}** is used and the test fails during validation
96644      */
96645     regexText : '',
96646
96647     /**
96648      * @cfg {String} emptyText
96649      * The default text to place into an empty field.
96650      *
96651      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
96652      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
96653      * false.
96654      *
96655      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
96656      * avoided.
96657      */
96658
96659     /**
96660      * @cfg {String} [emptyCls='x-form-empty-field']
96661      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
96662      * This class is automatically added and removed as needed depending on the current field value.
96663      */
96664     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
96665
96666     ariaRole: 'textbox',
96667
96668     /**
96669      * @cfg {Boolean} [enableKeyEvents=false]
96670      * true to enable the proxying of key events for the HTML input field
96671      */
96672
96673     componentLayout: 'textfield',
96674
96675     initComponent : function(){
96676         this.callParent();
96677         this.addEvents(
96678             /**
96679              * @event autosize
96680              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
96681              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
96682              * developer to apply additional logic at runtime to resize the field if needed.
96683              * @param {Ext.form.field.Text} this This text field
96684              * @param {Number} width The new field width
96685              */
96686             'autosize',
96687
96688             /**
96689              * @event keydown
96690              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96691              * @param {Ext.form.field.Text} this This text field
96692              * @param {Ext.EventObject} e
96693              */
96694             'keydown',
96695             /**
96696              * @event keyup
96697              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96698              * @param {Ext.form.field.Text} this This text field
96699              * @param {Ext.EventObject} e
96700              */
96701             'keyup',
96702             /**
96703              * @event keypress
96704              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96705              * @param {Ext.form.field.Text} this This text field
96706              * @param {Ext.EventObject} e
96707              */
96708             'keypress'
96709         );
96710     },
96711
96712     // private
96713     initEvents : function(){
96714         var me = this,
96715             el = me.inputEl;
96716
96717         me.callParent();
96718         if(me.selectOnFocus || me.emptyText){
96719             me.mon(el, 'mousedown', me.onMouseDown, me);
96720         }
96721         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
96722             me.mon(el, 'keypress', me.filterKeys, me);
96723         }
96724
96725         if (me.enableKeyEvents) {
96726             me.mon(el, {
96727                 scope: me,
96728                 keyup: me.onKeyUp,
96729                 keydown: me.onKeyDown,
96730                 keypress: me.onKeyPress
96731             });
96732         }
96733     },
96734
96735     /**
96736      * @private
96737      * Override. Treat undefined and null values as equal to an empty string value.
96738      */
96739     isEqual: function(value1, value2) {
96740         return this.isEqualAsString(value1, value2);
96741     },
96742
96743     /**
96744      * @private
96745      * If grow=true, invoke the autoSize method when the field's value is changed.
96746      */
96747     onChange: function() {
96748         this.callParent();
96749         this.autoSize();
96750     },
96751
96752     afterRender: function(){
96753         var me = this;
96754         if (me.enforceMaxLength) {
96755             me.inputEl.dom.maxLength = me.maxLength;
96756         }
96757         me.applyEmptyText();
96758         me.autoSize();
96759         me.callParent();
96760     },
96761
96762     onMouseDown: function(e){
96763         var me = this;
96764         if(!me.hasFocus){
96765             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
96766         }
96767     },
96768
96769     /**
96770      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
96771      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
96772      * to the raw value.
96773      * @param {String} value The unprocessed string value
96774      * @return {String} The processed string value
96775      */
96776     processRawValue: function(value) {
96777         var me = this,
96778             stripRe = me.stripCharsRe,
96779             newValue;
96780
96781         if (stripRe) {
96782             newValue = value.replace(stripRe, '');
96783             if (newValue !== value) {
96784                 me.setRawValue(newValue);
96785                 value = newValue;
96786             }
96787         }
96788         return value;
96789     },
96790
96791     //private
96792     onDisable: function(){
96793         this.callParent();
96794         if (Ext.isIE) {
96795             this.inputEl.dom.unselectable = 'on';
96796         }
96797     },
96798
96799     //private
96800     onEnable: function(){
96801         this.callParent();
96802         if (Ext.isIE) {
96803             this.inputEl.dom.unselectable = '';
96804         }
96805     },
96806
96807     onKeyDown: function(e) {
96808         this.fireEvent('keydown', this, e);
96809     },
96810
96811     onKeyUp: function(e) {
96812         this.fireEvent('keyup', this, e);
96813     },
96814
96815     onKeyPress: function(e) {
96816         this.fireEvent('keypress', this, e);
96817     },
96818
96819     /**
96820      * Resets the current field value to the originally-loaded value and clears any validation messages.
96821      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
96822      */
96823     reset : function(){
96824         this.callParent();
96825         this.applyEmptyText();
96826     },
96827
96828     applyEmptyText : function(){
96829         var me = this,
96830             emptyText = me.emptyText,
96831             isEmpty;
96832
96833         if (me.rendered && emptyText) {
96834             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
96835
96836             if (Ext.supports.Placeholder) {
96837                 me.inputEl.dom.placeholder = emptyText;
96838             } else if (isEmpty) {
96839                 me.setRawValue(emptyText);
96840             }
96841
96842             //all browsers need this because of a styling issue with chrome + placeholders.
96843             //the text isnt vertically aligned when empty (and using the placeholder)
96844             if (isEmpty) {
96845                 me.inputEl.addCls(me.emptyCls);
96846             }
96847
96848             me.autoSize();
96849         }
96850     },
96851
96852     // private
96853     preFocus : function(){
96854         var me = this,
96855             inputEl = me.inputEl,
96856             emptyText = me.emptyText,
96857             isEmpty;
96858
96859         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
96860             me.setRawValue('');
96861             isEmpty = true;
96862             inputEl.removeCls(me.emptyCls);
96863         } else if (Ext.supports.Placeholder) {
96864             me.inputEl.removeCls(me.emptyCls);
96865         }
96866         if (me.selectOnFocus || isEmpty) {
96867             inputEl.dom.select();
96868         }
96869     },
96870
96871     onFocus: function() {
96872         var me = this;
96873         me.callParent(arguments);
96874         if (me.emptyText) {
96875             me.autoSize();
96876         }
96877     },
96878
96879     // private
96880     postBlur : function(){
96881         this.applyEmptyText();
96882     },
96883
96884     // private
96885     filterKeys : function(e){
96886         /*
96887          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
96888          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
96889          * so we can still process these special characters.
96890          */
96891         if (e.ctrlKey && !e.altKey) {
96892             return;
96893         }
96894         var key = e.getKey(),
96895             charCode = String.fromCharCode(e.getCharCode());
96896
96897         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
96898             return;
96899         }
96900
96901         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
96902             return;
96903         }
96904         if(!this.maskRe.test(charCode)){
96905             e.stopEvent();
96906         }
96907     },
96908
96909     /**
96910      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
96911      * the current value of the input element if the field has been rendered, ignoring the value if it is the
96912      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
96913      * @return {String} The raw String value of the field
96914      */
96915     getRawValue: function() {
96916         var me = this,
96917             v = me.callParent();
96918         if (v === me.emptyText) {
96919             v = '';
96920         }
96921         return v;
96922     },
96923
96924     /**
96925      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
96926      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
96927      * @param {Object} value The value to set
96928      * @return {Ext.form.field.Text} this
96929      */
96930     setValue: function(value) {
96931         var me = this,
96932             inputEl = me.inputEl;
96933
96934         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
96935             inputEl.removeCls(me.emptyCls);
96936         }
96937
96938         me.callParent(arguments);
96939
96940         me.applyEmptyText();
96941         return me;
96942     },
96943
96944     /**
96945      * Validates a value according to the field's validation rules and returns an array of errors
96946      * for any failing validations. Validation rules are processed in the following order:
96947      *
96948      * 1. **Field specific validator**
96949      *
96950      *     A validator offers a way to customize and reuse a validation specification.
96951      *     If a field is configured with a `{@link #validator}`
96952      *     function, it will be passed the current field value.  The `{@link #validator}`
96953      *     function is expected to return either:
96954      *
96955      *     - Boolean `true`  if the value is valid (validation continues).
96956      *     - a String to represent the invalid message if invalid (validation halts).
96957      *
96958      * 2. **Basic Validation**
96959      *
96960      *     If the `{@link #validator}` has not halted validation,
96961      *     basic validation proceeds as follows:
96962      *
96963      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
96964      *
96965      *         Depending on the configuration of `{@link #allowBlank}`, a
96966      *         blank field will cause validation to halt at this step and return
96967      *         Boolean true or false accordingly.
96968      *
96969      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
96970      *
96971      *         If the passed value does not satisfy the `{@link #minLength}`
96972      *         specified, validation halts.
96973      *
96974      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
96975      *
96976      *         If the passed value does not satisfy the `{@link #maxLength}`
96977      *         specified, validation halts.
96978      *
96979      * 3. **Preconfigured Validation Types (VTypes)**
96980      *
96981      *     If none of the prior validation steps halts validation, a field
96982      *     configured with a `{@link #vtype}` will utilize the
96983      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
96984      *     If invalid, either the field's `{@link #vtypeText}` or
96985      *     the VTypes vtype Text property will be used for the invalid message.
96986      *     Keystrokes on the field will be filtered according to the VTypes
96987      *     vtype Mask property.
96988      *
96989      * 4. **Field specific regex test**
96990      *
96991      *     If none of the prior validation steps halts validation, a field's
96992      *     configured <code>{@link #regex}</code> test will be processed.
96993      *     The invalid message for this test is configured with `{@link #regexText}`
96994      *
96995      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
96996      * @return {String[]} Array of any validation errors
96997      */
96998     getErrors: function(value) {
96999         var me = this,
97000             errors = me.callParent(arguments),
97001             validator = me.validator,
97002             emptyText = me.emptyText,
97003             allowBlank = me.allowBlank,
97004             vtype = me.vtype,
97005             vtypes = Ext.form.field.VTypes,
97006             regex = me.regex,
97007             format = Ext.String.format,
97008             msg;
97009
97010         value = value || me.processRawValue(me.getRawValue());
97011
97012         if (Ext.isFunction(validator)) {
97013             msg = validator.call(me, value);
97014             if (msg !== true) {
97015                 errors.push(msg);
97016             }
97017         }
97018
97019         if (value.length < 1 || value === emptyText) {
97020             if (!allowBlank) {
97021                 errors.push(me.blankText);
97022             }
97023             //if value is blank, there cannot be any additional errors
97024             return errors;
97025         }
97026
97027         if (value.length < me.minLength) {
97028             errors.push(format(me.minLengthText, me.minLength));
97029         }
97030
97031         if (value.length > me.maxLength) {
97032             errors.push(format(me.maxLengthText, me.maxLength));
97033         }
97034
97035         if (vtype) {
97036             if(!vtypes[vtype](value, me)){
97037                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
97038             }
97039         }
97040
97041         if (regex && !regex.test(value)) {
97042             errors.push(me.regexText || me.invalidText);
97043         }
97044
97045         return errors;
97046     },
97047
97048     /**
97049      * Selects text in this field
97050      * @param {Number} [start=0] The index where the selection should start
97051      * @param {Number} [end] The index where the selection should end (defaults to the text length)
97052      */
97053     selectText : function(start, end){
97054         var me = this,
97055             v = me.getRawValue(),
97056             doFocus = true,
97057             el = me.inputEl.dom,
97058             undef,
97059             range;
97060
97061         if (v.length > 0) {
97062             start = start === undef ? 0 : start;
97063             end = end === undef ? v.length : end;
97064             if (el.setSelectionRange) {
97065                 el.setSelectionRange(start, end);
97066             }
97067             else if(el.createTextRange) {
97068                 range = el.createTextRange();
97069                 range.moveStart('character', start);
97070                 range.moveEnd('character', end - v.length);
97071                 range.select();
97072             }
97073             doFocus = Ext.isGecko || Ext.isOpera;
97074         }
97075         if (doFocus) {
97076             me.focus();
97077         }
97078     },
97079
97080     /**
97081      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
97082      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
97083      */
97084     autoSize: function() {
97085         var me = this,
97086             width;
97087         if (me.grow && me.rendered) {
97088             me.doComponentLayout();
97089             width = me.inputEl.getWidth();
97090             if (width !== me.lastInputWidth) {
97091                 me.fireEvent('autosize', width);
97092                 me.lastInputWidth = width;
97093             }
97094         }
97095     },
97096
97097     initAria: function() {
97098         this.callParent();
97099         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
97100     },
97101
97102     /**
97103      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
97104      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
97105      * performance. Overrides Labelable method.
97106      * @protected
97107      */
97108     getBodyNaturalWidth: function() {
97109         return Math.round(this.size * 6.5) + 20;
97110     }
97111
97112 });
97113
97114 /**
97115  * @docauthor Robert Dougan <rob@sencha.com>
97116  *
97117  * This class creates a multiline text field, which can be used as a direct replacement for traditional
97118  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
97119  * fit its content.
97120  *
97121  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
97122  *
97123  * Example usage:
97124  *
97125  *     @example
97126  *     Ext.create('Ext.form.FormPanel', {
97127  *         title      : 'Sample TextArea',
97128  *         width      : 400,
97129  *         bodyPadding: 10,
97130  *         renderTo   : Ext.getBody(),
97131  *         items: [{
97132  *             xtype     : 'textareafield',
97133  *             grow      : true,
97134  *             name      : 'message',
97135  *             fieldLabel: 'Message',
97136  *             anchor    : '100%'
97137  *         }]
97138  *     });
97139  *
97140  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
97141  * These allow you to set the minimum and maximum grow heights for the textarea.
97142  */
97143 Ext.define('Ext.form.field.TextArea', {
97144     extend:'Ext.form.field.Text',
97145     alias: ['widget.textareafield', 'widget.textarea'],
97146     alternateClassName: 'Ext.form.TextArea',
97147     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
97148
97149     fieldSubTpl: [
97150         '<textarea id="{id}" ',
97151             '<tpl if="name">name="{name}" </tpl>',
97152             '<tpl if="rows">rows="{rows}" </tpl>',
97153             '<tpl if="cols">cols="{cols}" </tpl>',
97154             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97155             'class="{fieldCls} {typeCls}" ',
97156             'autocomplete="off">',
97157         '</textarea>',
97158         {
97159             compiled: true,
97160             disableFormats: true
97161         }
97162     ],
97163
97164     /**
97165      * @cfg {Number} growMin
97166      * The minimum height to allow when {@link #grow}=true
97167      */
97168     growMin: 60,
97169
97170     /**
97171      * @cfg {Number} growMax
97172      * The maximum height to allow when {@link #grow}=true
97173      */
97174     growMax: 1000,
97175
97176     /**
97177      * @cfg {String} growAppend
97178      * A string that will be appended to the field's current value for the purposes of calculating the target field
97179      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
97180      * always a space below the current line.
97181      */
97182     growAppend: '\n-',
97183
97184     /**
97185      * @cfg {Number} cols
97186      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97187      * configured {@link #width} and is not given a width by its container's layout.
97188      */
97189     cols: 20,
97190
97191     /**
97192      * @cfg {Number} cols
97193      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97194      * configured {@link #width} and is not given a width by its container's layout.
97195      */
97196     rows: 4,
97197
97198     /**
97199      * @cfg {Boolean} enterIsSpecial
97200      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
97201      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
97202      * textarea.
97203      */
97204     enterIsSpecial: false,
97205
97206     /**
97207      * @cfg {Boolean} preventScrollbars
97208      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
97209      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
97210      */
97211     preventScrollbars: false,
97212
97213     // private
97214     componentLayout: 'textareafield',
97215
97216     // private
97217     onRender: function(ct, position) {
97218         var me = this;
97219         Ext.applyIf(me.subTplData, {
97220             cols: me.cols,
97221             rows: me.rows
97222         });
97223
97224         me.callParent(arguments);
97225     },
97226
97227     // private
97228     afterRender: function(){
97229         var me = this;
97230
97231         me.callParent(arguments);
97232
97233         if (me.grow) {
97234             if (me.preventScrollbars) {
97235                 me.inputEl.setStyle('overflow', 'hidden');
97236             }
97237             me.inputEl.setHeight(me.growMin);
97238         }
97239     },
97240
97241     // private
97242     fireKey: function(e) {
97243         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
97244             this.fireEvent('specialkey', this, e);
97245         }
97246     },
97247
97248     /**
97249      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
97250      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
97251      */
97252     autoSize: function() {
97253         var me = this,
97254             height;
97255
97256         if (me.grow && me.rendered) {
97257             me.doComponentLayout();
97258             height = me.inputEl.getHeight();
97259             if (height !== me.lastInputHeight) {
97260                 me.fireEvent('autosize', height);
97261                 me.lastInputHeight = height;
97262             }
97263         }
97264     },
97265
97266     // private
97267     initAria: function() {
97268         this.callParent(arguments);
97269         this.getActionEl().dom.setAttribute('aria-multiline', true);
97270     },
97271
97272     /**
97273      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
97274      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
97275      * would hurt performance. Overrides Labelable method.
97276      * @protected
97277      */
97278     getBodyNaturalWidth: function() {
97279         return Math.round(this.cols * 6.5) + 20;
97280     }
97281
97282 });
97283
97284
97285 /**
97286  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
97287  * alias `Ext.Msg` can also be used.
97288  *
97289  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
97290  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
97291  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
97292  * (see the `function` parameter for {@link #show} for more details).
97293  *
97294  * Basic alert
97295  *
97296  *     @example
97297  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
97298  *
97299  * Prompt for user data and process the result using a callback
97300  *
97301  *     @example
97302  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
97303  *         if (btn == 'ok'){
97304  *             // process text value and close...
97305  *         }
97306  *     });
97307  *
97308  * Show a dialog using config options
97309  *
97310  *     @example
97311  *     Ext.Msg.show({
97312  *          title:'Save Changes?',
97313  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
97314  *          buttons: Ext.Msg.YESNOCANCEL,
97315  *          icon: Ext.Msg.QUESTION
97316  *     });
97317  */
97318 Ext.define('Ext.window.MessageBox', {
97319     extend: 'Ext.window.Window',
97320
97321     requires: [
97322         'Ext.toolbar.Toolbar',
97323         'Ext.form.field.Text',
97324         'Ext.form.field.TextArea',
97325         'Ext.button.Button',
97326         'Ext.layout.container.Anchor',
97327         'Ext.layout.container.HBox',
97328         'Ext.ProgressBar'
97329     ],
97330
97331     alias: 'widget.messagebox',
97332
97333     /**
97334      * Button config that displays a single OK button
97335      * @type Number
97336      */
97337     OK : 1,
97338     /**
97339      * Button config that displays a single Yes button
97340      * @type Number
97341      */
97342     YES : 2,
97343     /**
97344      * Button config that displays a single No button
97345      * @type Number
97346      */
97347     NO : 4,
97348     /**
97349      * Button config that displays a single Cancel button
97350      * @type Number
97351      */
97352     CANCEL : 8,
97353     /**
97354      * Button config that displays OK and Cancel buttons
97355      * @type Number
97356      */
97357     OKCANCEL : 9,
97358     /**
97359      * Button config that displays Yes and No buttons
97360      * @type Number
97361      */
97362     YESNO : 6,
97363     /**
97364      * Button config that displays Yes, No and Cancel buttons
97365      * @type Number
97366      */
97367     YESNOCANCEL : 14,
97368     /**
97369      * The CSS class that provides the INFO icon image
97370      * @type String
97371      */
97372     INFO : 'ext-mb-info',
97373     /**
97374      * The CSS class that provides the WARNING icon image
97375      * @type String
97376      */
97377     WARNING : 'ext-mb-warning',
97378     /**
97379      * The CSS class that provides the QUESTION icon image
97380      * @type String
97381      */
97382     QUESTION : 'ext-mb-question',
97383     /**
97384      * The CSS class that provides the ERROR icon image
97385      * @type String
97386      */
97387     ERROR : 'ext-mb-error',
97388
97389     // hide it by offsets. Windows are hidden on render by default.
97390     hideMode: 'offsets',
97391     closeAction: 'hide',
97392     resizable: false,
97393     title: '&#160;',
97394
97395     width: 600,
97396     height: 500,
97397     minWidth: 250,
97398     maxWidth: 600,
97399     minHeight: 110,
97400     maxHeight: 500,
97401     constrain: true,
97402
97403     cls: Ext.baseCSSPrefix + 'message-box',
97404
97405     layout: {
97406         type: 'anchor'
97407     },
97408
97409     /**
97410      * The default height in pixels of the message box's multiline textarea if displayed.
97411      * @type Number
97412      */
97413     defaultTextHeight : 75,
97414     /**
97415      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
97416      * for setting a different minimum width than text-only dialogs may need.
97417      * @type Number
97418      */
97419     minProgressWidth : 250,
97420     /**
97421      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
97422      * for setting a different minimum width than text-only dialogs may need.
97423      * @type Number
97424      */
97425     minPromptWidth: 250,
97426     /**
97427      * An object containing the default button text strings that can be overriden for localized language support.
97428      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
97429      * resource file for handling language support across the framework.
97430      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
97431      * @type Object
97432      */
97433     buttonText: {
97434         ok: 'OK',
97435         yes: 'Yes',
97436         no: 'No',
97437         cancel: 'Cancel'
97438     },
97439
97440     buttonIds: [
97441         'ok', 'yes', 'no', 'cancel'
97442     ],
97443
97444     titleText: {
97445         confirm: 'Confirm',
97446         prompt: 'Prompt',
97447         wait: 'Loading...',
97448         alert: 'Attention'
97449     },
97450
97451     iconHeight: 35,
97452
97453     makeButton: function(btnIdx) {
97454         var btnId = this.buttonIds[btnIdx];
97455         return Ext.create('Ext.button.Button', {
97456             handler: this.btnCallback,
97457             itemId: btnId,
97458             scope: this,
97459             text: this.buttonText[btnId],
97460             minWidth: 75
97461         });
97462     },
97463
97464     btnCallback: function(btn) {
97465         var me = this,
97466             value,
97467             field;
97468
97469         if (me.cfg.prompt || me.cfg.multiline) {
97470             if (me.cfg.multiline) {
97471                 field = me.textArea;
97472             } else {
97473                 field = me.textField;
97474             }
97475             value = field.getValue();
97476             field.reset();
97477         }
97478
97479         // Important not to have focus remain in the hidden Window; Interferes with DnD.
97480         btn.blur();
97481         me.hide();
97482         me.userCallback(btn.itemId, value, me.cfg);
97483     },
97484
97485     hide: function() {
97486         var me = this;
97487         me.dd.endDrag();
97488         me.progressBar.reset();
97489         me.removeCls(me.cfg.cls);
97490         me.callParent();
97491     },
97492
97493     initComponent: function() {
97494         var me = this,
97495             i, button;
97496
97497         me.title = '&#160;';
97498
97499         me.topContainer = Ext.create('Ext.container.Container', {
97500             anchor: '100%',
97501             style: {
97502                 padding: '10px',
97503                 overflow: 'hidden'
97504             },
97505             items: [
97506                 me.iconComponent = Ext.create('Ext.Component', {
97507                     cls: 'ext-mb-icon',
97508                     width: 50,
97509                     height: me.iconHeight,
97510                     style: {
97511                         'float': 'left'
97512                     }
97513                 }),
97514                 me.promptContainer = Ext.create('Ext.container.Container', {
97515                     layout: {
97516                         type: 'anchor'
97517                     },
97518                     items: [
97519                         me.msg = Ext.create('Ext.Component', {
97520                             autoEl: { tag: 'span' },
97521                             cls: 'ext-mb-text'
97522                         }),
97523                         me.textField = Ext.create('Ext.form.field.Text', {
97524                             anchor: '100%',
97525                             enableKeyEvents: true,
97526                             listeners: {
97527                                 keydown: me.onPromptKey,
97528                                 scope: me
97529                             }
97530                         }),
97531                         me.textArea = Ext.create('Ext.form.field.TextArea', {
97532                             anchor: '100%',
97533                             height: 75
97534                         })
97535                     ]
97536                 })
97537             ]
97538         });
97539         me.progressBar = Ext.create('Ext.ProgressBar', {
97540             anchor: '-10',
97541             style: 'margin-left:10px'
97542         });
97543
97544         me.items = [me.topContainer, me.progressBar];
97545
97546         // Create the buttons based upon passed bitwise config
97547         me.msgButtons = [];
97548         for (i = 0; i < 4; i++) {
97549             button = me.makeButton(i);
97550             me.msgButtons[button.itemId] = button;
97551             me.msgButtons.push(button);
97552         }
97553         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
97554             ui: 'footer',
97555             dock: 'bottom',
97556             layout: {
97557                 pack: 'center'
97558             },
97559             items: [
97560                 me.msgButtons[0],
97561                 me.msgButtons[1],
97562                 me.msgButtons[2],
97563                 me.msgButtons[3]
97564             ]
97565         });
97566         me.dockedItems = [me.bottomTb];
97567
97568         me.callParent();
97569     },
97570
97571     onPromptKey: function(textField, e) {
97572         var me = this,
97573             blur;
97574
97575         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
97576             if (me.msgButtons.ok.isVisible()) {
97577                 blur = true;
97578                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
97579             } else if (me.msgButtons.yes.isVisible()) {
97580                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
97581                 blur = true;
97582             }
97583
97584             if (blur) {
97585                 me.textField.blur();
97586             }
97587         }
97588     },
97589
97590     reconfigure: function(cfg) {
97591         var me = this,
97592             buttons = cfg.buttons || 0,
97593             hideToolbar = true,
97594             initialWidth = me.maxWidth,
97595             i;
97596
97597         cfg = cfg || {};
97598         me.cfg = cfg;
97599         if (cfg.width) {
97600             initialWidth = cfg.width;
97601         }
97602
97603         // Default to allowing the Window to take focus.
97604         delete me.defaultFocus;
97605
97606         // clear any old animateTarget
97607         me.animateTarget = cfg.animateTarget || undefined;
97608
97609         // Defaults to modal
97610         me.modal = cfg.modal !== false;
97611
97612         // Show the title
97613         if (cfg.title) {
97614             me.setTitle(cfg.title||'&#160;');
97615         }
97616
97617         if (!me.rendered) {
97618             me.width = initialWidth;
97619             me.render(Ext.getBody());
97620         } else {
97621             me.setSize(initialWidth, me.maxHeight);
97622         }
97623         me.setPosition(-10000, -10000);
97624
97625         // Hide or show the close tool
97626         me.closable = cfg.closable && !cfg.wait;
97627         me.header.child('[type=close]').setVisible(cfg.closable !== false);
97628
97629         // Hide or show the header
97630         if (!cfg.title && !me.closable) {
97631             me.header.hide();
97632         } else {
97633             me.header.show();
97634         }
97635
97636         // Default to dynamic drag: drag the window, not a ghost
97637         me.liveDrag = !cfg.proxyDrag;
97638
97639         // wrap the user callback
97640         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
97641
97642         // Hide or show the icon Component
97643         me.setIcon(cfg.icon);
97644
97645         // Hide or show the message area
97646         if (cfg.msg) {
97647             me.msg.update(cfg.msg);
97648             me.msg.show();
97649         } else {
97650             me.msg.hide();
97651         }
97652
97653         // Hide or show the input field
97654         if (cfg.prompt || cfg.multiline) {
97655             me.multiline = cfg.multiline;
97656             if (cfg.multiline) {
97657                 me.textArea.setValue(cfg.value);
97658                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
97659                 me.textArea.show();
97660                 me.textField.hide();
97661                 me.defaultFocus = me.textArea;
97662             } else {
97663                 me.textField.setValue(cfg.value);
97664                 me.textArea.hide();
97665                 me.textField.show();
97666                 me.defaultFocus = me.textField;
97667             }
97668         } else {
97669             me.textArea.hide();
97670             me.textField.hide();
97671         }
97672
97673         // Hide or show the progress bar
97674         if (cfg.progress || cfg.wait) {
97675             me.progressBar.show();
97676             me.updateProgress(0, cfg.progressText);
97677             if(cfg.wait === true){
97678                 me.progressBar.wait(cfg.waitConfig);
97679             }
97680         } else {
97681             me.progressBar.hide();
97682         }
97683
97684         // Hide or show buttons depending on flag value sent.
97685         for (i = 0; i < 4; i++) {
97686             if (buttons & Math.pow(2, i)) {
97687
97688                 // Default to focus on the first visible button if focus not already set
97689                 if (!me.defaultFocus) {
97690                     me.defaultFocus = me.msgButtons[i];
97691                 }
97692                 me.msgButtons[i].show();
97693                 hideToolbar = false;
97694             } else {
97695                 me.msgButtons[i].hide();
97696             }
97697         }
97698
97699         // Hide toolbar if no buttons to show
97700         if (hideToolbar) {
97701             me.bottomTb.hide();
97702         } else {
97703             me.bottomTb.show();
97704         }
97705     },
97706
97707     /**
97708      * Displays a new message box, or reinitializes an existing message box, based on the config options
97709      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
97710      * although those calls are basic shortcuts and do not support all of the config options allowed here.
97711      * @param {Object} config The following config options are supported: <ul>
97712      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
97713      * opens and closes (defaults to undefined)</div></li>
97714      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
97715      * <li>Ext.window.MessageBox.OK</li>
97716      * <li>Ext.window.MessageBox.YES</li>
97717      * <li>Ext.window.MessageBox.NO</li>
97718      * <li>Ext.window.MessageBox.CANCEL</li>
97719      * </ul>Or false to not show any buttons (defaults to false)</div></li>
97720      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
97721      * progress and wait dialogs will ignore this property and always hide the close button as they can only
97722      * be closed programmatically.</div></li>
97723      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
97724      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
97725      * if displayed (defaults to 75)</div></li>
97726      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
97727      * by clicking on the configured buttons, or on the dialog close button, or by pressing
97728      * the return button to enter input.
97729      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
97730      * actions and can only be closed programmatically, so any required function should be called
97731      * by the same code after it closes the dialog. Parameters passed:<ul>
97732      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
97733      * <li><tt>ok</tt></li>
97734      * <li><tt>yes</tt></li>
97735      * <li><tt>no</tt></li>
97736      * <li><tt>cancel</tt></li>
97737      * </ul></div></div></li>
97738      * <li><b>text</b> : String<div class="sub-desc">Value of the input field if either <tt><a href="#show-option-prompt" ext:member="show-option-prompt" ext:cls="Ext.window.MessageBox">prompt</a></tt>
97739      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
97740      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
97741      * </ul></p></div></li>
97742      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
97743      * <li><b>icon</b> : String<div class="sub-desc">A CSS class that provides a background image to be used as the body icon for the
97744      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
97745      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
97746      * add an optional header icon (defaults to '')</div></li>
97747      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
97748      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
97749      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
97750      * displayed (defaults to true)</div></li>
97751      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
97752      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
97753      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
97754      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
97755      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97756      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
97757      * <li><a id="show-option-prompt"></a><b>prompt</b> : Boolean<div class="sub-desc">True to prompt the user to enter single-line text (defaults to false)</div></li>
97758      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
97759      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
97760      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
97761      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97762      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
97763      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
97764      * </ul>
97765      * Example usage:
97766      * <pre><code>
97767 Ext.Msg.show({
97768 title: 'Address',
97769 msg: 'Please enter your address:',
97770 width: 300,
97771 buttons: Ext.Msg.OKCANCEL,
97772 multiline: true,
97773 fn: saveAddress,
97774 animateTarget: 'addAddressBtn',
97775 icon: Ext.window.MessageBox.INFO
97776 });
97777 </code></pre>
97778      * @return {Ext.window.MessageBox} this
97779      */
97780     show: function(cfg) {
97781         var me = this;
97782
97783         me.reconfigure(cfg);
97784         me.addCls(cfg.cls);
97785         if (cfg.animateTarget) {
97786             me.doAutoSize(true);
97787             me.callParent();
97788         } else {
97789             me.callParent();
97790             me.doAutoSize(true);
97791         }
97792         return me;
97793     },
97794
97795     afterShow: function(){
97796         if (this.animateTarget) {
97797             this.center();
97798         }
97799         this.callParent(arguments);
97800     },
97801
97802     doAutoSize: function(center) {
97803         var me = this,
97804             icon = me.iconComponent,
97805             iconHeight = me.iconHeight;
97806
97807         if (!Ext.isDefined(me.frameWidth)) {
97808             me.frameWidth = me.el.getWidth() - me.body.getWidth();
97809         }
97810
97811         // reset to the original dimensions
97812         icon.setHeight(iconHeight);
97813
97814         // Allow per-invocation override of minWidth
97815         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
97816
97817         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
97818         // then constraining it to within the max with. Then adding up constituent element heights.
97819         me.topContainer.doLayout();
97820         if (Ext.isIE6 || Ext.isIEQuirks) {
97821             // In IE quirks, the initial full width of the prompt fields will prevent the container element
97822             // from collapsing once sized down, so temporarily force them to a small width. They'll get
97823             // layed out to their final width later when setting the final window size.
97824             me.textField.setCalculatedSize(9);
97825             me.textArea.setCalculatedSize(9);
97826         }
97827         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
97828             height = (me.header.rendered ? me.header.getHeight() : 0) +
97829             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
97830             me.progressBar.getHeight() +
97831             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
97832
97833         // Update to the size of the content, this way the text won't wrap under the icon.
97834         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
97835         me.setSize(width + me.frameWidth, height + me.frameWidth);
97836         if (center) {
97837             me.center();
97838         }
97839         return me;
97840     },
97841
97842     updateText: function(text) {
97843         this.msg.update(text);
97844         return this.doAutoSize(true);
97845     },
97846
97847     /**
97848      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
97849      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
97850      * to clear any existing icon. This method must be called before the MessageBox is shown.
97851      * The following built-in icon classes are supported, but you can also pass in a custom class name:
97852      * <pre>
97853 Ext.window.MessageBox.INFO
97854 Ext.window.MessageBox.WARNING
97855 Ext.window.MessageBox.QUESTION
97856 Ext.window.MessageBox.ERROR
97857      *</pre>
97858      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
97859      * @return {Ext.window.MessageBox} this
97860      */
97861     setIcon : function(icon) {
97862         var me = this;
97863         me.iconComponent.removeCls(me.iconCls);
97864         if (icon) {
97865             me.iconComponent.show();
97866             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
97867             me.iconComponent.addCls(me.iconCls = icon);
97868         } else {
97869             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
97870             me.iconComponent.hide();
97871         }
97872         return me;
97873     },
97874
97875     /**
97876      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
97877      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
97878      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
97879      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
97880      * @param {String} [progressText=''] The progress text to display inside the progress bar.
97881      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
97882      * so that any existing body text will not get overwritten by default unless a new value is passed in)
97883      * @return {Ext.window.MessageBox} this
97884      */
97885     updateProgress : function(value, progressText, msg){
97886         this.progressBar.updateProgress(value, progressText);
97887         if (msg){
97888             this.updateText(msg);
97889         }
97890         return this;
97891     },
97892
97893     onEsc: function() {
97894         if (this.closable !== false) {
97895             this.callParent(arguments);
97896         }
97897     },
97898
97899     /**
97900      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
97901      * If a callback function is passed it will be called after the user clicks either button,
97902      * and the id of the button that was clicked will be passed as the only parameter to the callback
97903      * (could also be the top-right close button).
97904      * @param {String} title The title bar text
97905      * @param {String} msg The message box body text
97906      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97907      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97908      * @return {Ext.window.MessageBox} this
97909      */
97910     confirm: function(cfg, msg, fn, scope) {
97911         if (Ext.isString(cfg)) {
97912             cfg = {
97913                 title: cfg,
97914                 icon: 'ext-mb-question',
97915                 msg: msg,
97916                 buttons: this.YESNO,
97917                 callback: fn,
97918                 scope: scope
97919             };
97920         }
97921         return this.show(cfg);
97922     },
97923
97924     /**
97925      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
97926      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
97927      * clicks either button, and the id of the button that was clicked (could also be the top-right
97928      * close button) and the text that was entered will be passed as the two parameters to the callback.
97929      * @param {String} title The title bar text
97930      * @param {String} msg The message box body text
97931      * @param {Function} [fn] The callback function invoked after the message box is closed
97932      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97933      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
97934      * property, or the height in pixels to create the textbox/
97935      * @param {String} [value=''] Default value of the text input element
97936      * @return {Ext.window.MessageBox} this
97937      */
97938     prompt : function(cfg, msg, fn, scope, multiline, value){
97939         if (Ext.isString(cfg)) {
97940             cfg = {
97941                 prompt: true,
97942                 title: cfg,
97943                 minWidth: this.minPromptWidth,
97944                 msg: msg,
97945                 buttons: this.OKCANCEL,
97946                 callback: fn,
97947                 scope: scope,
97948                 multiline: multiline,
97949                 value: value
97950             };
97951         }
97952         return this.show(cfg);
97953     },
97954
97955     /**
97956      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
97957      * interaction while waiting for a long-running process to complete that does not have defined intervals.
97958      * You are responsible for closing the message box when the process is complete.
97959      * @param {String} msg The message box body text
97960      * @param {String} title (optional) The title bar text
97961      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
97962      * @return {Ext.window.MessageBox} this
97963      */
97964     wait : function(cfg, title, config){
97965         if (Ext.isString(cfg)) {
97966             cfg = {
97967                 title : title,
97968                 msg : cfg,
97969                 closable: false,
97970                 wait: true,
97971                 modal: true,
97972                 minWidth: this.minProgressWidth,
97973                 waitConfig: config
97974             };
97975         }
97976         return this.show(cfg);
97977     },
97978
97979     /**
97980      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
97981      * If a callback function is passed it will be called after the user clicks the button, and the
97982      * id of the button that was clicked will be passed as the only parameter to the callback
97983      * (could also be the top-right close button).
97984      * @param {String} title The title bar text
97985      * @param {String} msg The message box body text
97986      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97987      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97988      * @return {Ext.window.MessageBox} this
97989      */
97990     alert: function(cfg, msg, fn, scope) {
97991         if (Ext.isString(cfg)) {
97992             cfg = {
97993                 title : cfg,
97994                 msg : msg,
97995                 buttons: this.OK,
97996                 fn: fn,
97997                 scope : scope,
97998                 minWidth: this.minWidth
97999             };
98000         }
98001         return this.show(cfg);
98002     },
98003
98004     /**
98005      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
98006      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
98007      * and closing the message box when the process is complete.
98008      * @param {String} title The title bar text
98009      * @param {String} msg The message box body text
98010      * @param {String} [progressText=''] The text to display inside the progress bar
98011      * @return {Ext.window.MessageBox} this
98012      */
98013     progress : function(cfg, msg, progressText){
98014         if (Ext.isString(cfg)) {
98015             cfg = {
98016                 title: cfg,
98017                 msg: msg,
98018                 progress: true,
98019                 progressText: progressText
98020             };
98021         }
98022         return this.show(cfg);
98023     }
98024 }, function() {
98025     /**
98026      * @class Ext.MessageBox
98027      * @alternateClassName Ext.Msg
98028      * @extends Ext.window.MessageBox
98029      * @singleton
98030      * Singleton instance of {@link Ext.window.MessageBox}.
98031      */
98032     Ext.MessageBox = Ext.Msg = new this();
98033 });
98034 /**
98035  * @class Ext.form.Basic
98036  * @extends Ext.util.Observable
98037  *
98038  * Provides input field management, validation, submission, and form loading services for the collection
98039  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
98040  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
98041  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
98042  *
98043  * ## Form Actions
98044  *
98045  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
98046  * See the various Action implementations for specific details of each one's functionality, as well as the
98047  * documentation for {@link #doAction} which details the configuration options that can be specified in
98048  * each action call.
98049  *
98050  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
98051  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
98052  * {@link #standardSubmit} config option.
98053  *
98054  * ## File uploads
98055  *
98056  * File uploads are not performed using normal 'Ajax' techniques; see the description for
98057  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
98058  *
98059  * ## Example usage:
98060  *
98061  *     Ext.create('Ext.form.Panel', {
98062  *         title: 'Basic Form',
98063  *         renderTo: Ext.getBody(),
98064  *         bodyPadding: 5,
98065  *         width: 350,
98066  *
98067  *         // Any configuration items here will be automatically passed along to
98068  *         // the Ext.form.Basic instance when it gets created.
98069  *
98070  *         // The form will submit an AJAX request to this URL when submitted
98071  *         url: 'save-form.php',
98072  *
98073  *         items: [{
98074  *             fieldLabel: 'Field',
98075  *             name: 'theField'
98076  *         }],
98077  *
98078  *         buttons: [{
98079  *             text: 'Submit',
98080  *             handler: function() {
98081  *                 // The getForm() method returns the Ext.form.Basic instance:
98082  *                 var form = this.up('form').getForm();
98083  *                 if (form.isValid()) {
98084  *                     // Submit the Ajax request and handle the response
98085  *                     form.submit({
98086  *                         success: function(form, action) {
98087  *                            Ext.Msg.alert('Success', action.result.msg);
98088  *                         },
98089  *                         failure: function(form, action) {
98090  *                             Ext.Msg.alert('Failed', action.result.msg);
98091  *                         }
98092  *                     });
98093  *                 }
98094  *             }
98095  *         }]
98096  *     });
98097  *
98098  * @docauthor Jason Johnston <jason@sencha.com>
98099  */
98100 Ext.define('Ext.form.Basic', {
98101     extend: 'Ext.util.Observable',
98102     alternateClassName: 'Ext.form.BasicForm',
98103     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
98104                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
98105
98106     /**
98107      * Creates new form.
98108      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
98109      * @param {Object} config Configuration options. These are normally specified in the config to the
98110      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
98111      */
98112     constructor: function(owner, config) {
98113         var me = this,
98114             onItemAddOrRemove = me.onItemAddOrRemove;
98115
98116         /**
98117          * @property owner
98118          * @type Ext.container.Container
98119          * The container component to which this BasicForm is attached.
98120          */
98121         me.owner = owner;
98122
98123         // Listen for addition/removal of fields in the owner container
98124         me.mon(owner, {
98125             add: onItemAddOrRemove,
98126             remove: onItemAddOrRemove,
98127             scope: me
98128         });
98129
98130         Ext.apply(me, config);
98131
98132         // Normalize the paramOrder to an Array
98133         if (Ext.isString(me.paramOrder)) {
98134             me.paramOrder = me.paramOrder.split(/[\s,|]/);
98135         }
98136
98137         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
98138
98139         me.addEvents(
98140             /**
98141              * @event beforeaction
98142              * Fires before any action is performed. Return false to cancel the action.
98143              * @param {Ext.form.Basic} this
98144              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
98145              */
98146             'beforeaction',
98147             /**
98148              * @event actionfailed
98149              * Fires when an action fails.
98150              * @param {Ext.form.Basic} this
98151              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
98152              */
98153             'actionfailed',
98154             /**
98155              * @event actioncomplete
98156              * Fires when an action is completed.
98157              * @param {Ext.form.Basic} this
98158              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
98159              */
98160             'actioncomplete',
98161             /**
98162              * @event validitychange
98163              * Fires when the validity of the entire form changes.
98164              * @param {Ext.form.Basic} this
98165              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
98166              */
98167             'validitychange',
98168             /**
98169              * @event dirtychange
98170              * Fires when the dirty state of the entire form changes.
98171              * @param {Ext.form.Basic} this
98172              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
98173              */
98174             'dirtychange'
98175         );
98176         me.callParent();
98177     },
98178
98179     /**
98180      * Do any post constructor initialization
98181      * @private
98182      */
98183     initialize: function(){
98184         this.initialized = true;
98185         this.onValidityChange(!this.hasInvalidField());
98186     },
98187
98188     /**
98189      * @cfg {String} method
98190      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
98191      */
98192
98193     /**
98194      * @cfg {Ext.data.reader.Reader} reader
98195      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
98196      * data when executing 'load' actions. This is optional as there is built-in
98197      * support for processing JSON responses.
98198      */
98199
98200     /**
98201      * @cfg {Ext.data.reader.Reader} errorReader
98202      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
98203      * read field error messages returned from 'submit' actions. This is optional
98204      * as there is built-in support for processing JSON responses.</p>
98205      * <p>The Records which provide messages for the invalid Fields must use the
98206      * Field name (or id) as the Record ID, and must contain a field called 'msg'
98207      * which contains the error message.</p>
98208      * <p>The errorReader does not have to be a full-blown implementation of a
98209      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
98210      * which returns an Array of Records in an object with the following
98211      * structure:</p><pre><code>
98212 {
98213     records: recordArray
98214 }
98215 </code></pre>
98216      */
98217
98218     /**
98219      * @cfg {String} url
98220      * The URL to use for form actions if one isn't supplied in the
98221      * {@link #doAction doAction} options.
98222      */
98223
98224     /**
98225      * @cfg {Object} baseParams
98226      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
98227      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
98228      */
98229
98230     /**
98231      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
98232      */
98233     timeout: 30,
98234
98235     /**
98236      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
98237      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
98238      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
98239      * forms.
98240      * Such as the following:<pre><code>
98241 api: {
98242     load: App.ss.MyProfile.load,
98243     submit: App.ss.MyProfile.submit
98244 }
98245 </code></pre>
98246      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
98247      * to customize how the load method is invoked.
98248      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
98249      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
98250      */
98251
98252     /**
98253      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
98254      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
98255      * <code>load</code> configuration.</p>
98256      * <p>Specify the params in the order in which they must be executed on the
98257      * server-side as either (1) an Array of String values, or (2) a String of params
98258      * delimited by either whitespace, comma, or pipe. For example,
98259      * any of the following would be acceptable:</p><pre><code>
98260 paramOrder: ['param1','param2','param3']
98261 paramOrder: 'param1 param2 param3'
98262 paramOrder: 'param1,param2,param3'
98263 paramOrder: 'param1|param2|param'
98264      </code></pre>
98265      */
98266
98267     /**
98268      * @cfg {Boolean} paramsAsHash
98269      * Only used for the <code>{@link #api}</code>
98270      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
98271      * single hash collection of named arguments. Providing a
98272      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
98273      */
98274     paramsAsHash: false,
98275
98276     /**
98277      * @cfg {String} waitTitle
98278      * The default title to show for the waiting message box
98279      */
98280     waitTitle: 'Please Wait...',
98281
98282     /**
98283      * @cfg {Boolean} trackResetOnLoad
98284      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
98285      * when the form was first created.
98286      */
98287     trackResetOnLoad: false,
98288
98289     /**
98290      * @cfg {Boolean} standardSubmit
98291      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
98292      * All of the field values, plus any additional params configured via {@link #baseParams}
98293      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
98294      */
98295
98296     /**
98297      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
98298      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
98299      * element by passing it or its id or mask the form itself by passing in true.
98300      */
98301
98302
98303     // Private
98304     wasDirty: false,
98305
98306
98307     /**
98308      * Destroys this object.
98309      */
98310     destroy: function() {
98311         this.clearListeners();
98312         this.checkValidityTask.cancel();
98313     },
98314
98315     /**
98316      * @private
98317      * Handle addition or removal of descendant items. Invalidates the cached list of fields
98318      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
98319      * for state change events on added fields, and tracks components with formBind=true.
98320      */
98321     onItemAddOrRemove: function(parent, child) {
98322         var me = this,
98323             isAdding = !!child.ownerCt,
98324             isContainer = child.isContainer;
98325
98326         function handleField(field) {
98327             // Listen for state change events on fields
98328             me[isAdding ? 'mon' : 'mun'](field, {
98329                 validitychange: me.checkValidity,
98330                 dirtychange: me.checkDirty,
98331                 scope: me,
98332                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
98333             });
98334             // Flush the cached list of fields
98335             delete me._fields;
98336         }
98337
98338         if (child.isFormField) {
98339             handleField(child);
98340         } else if (isContainer) {
98341             // Walk down
98342             if (child.isDestroyed) {
98343                 // the container is destroyed, this means we may have child fields, so here
98344                 // we just invalidate all the fields to be sure.
98345                 delete me._fields;
98346             } else {
98347                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
98348             }
98349         }
98350
98351         // Flush the cached list of formBind components
98352         delete this._boundItems;
98353
98354         // Check form bind, but only after initial add. Batch it to prevent excessive validation
98355         // calls when many fields are being added at once.
98356         if (me.initialized) {
98357             me.checkValidityTask.delay(10);
98358         }
98359     },
98360
98361     /**
98362      * Return all the {@link Ext.form.field.Field} components in the owner container.
98363      * @return {Ext.util.MixedCollection} Collection of the Field objects
98364      */
98365     getFields: function() {
98366         var fields = this._fields;
98367         if (!fields) {
98368             fields = this._fields = Ext.create('Ext.util.MixedCollection');
98369             fields.addAll(this.owner.query('[isFormField]'));
98370         }
98371         return fields;
98372     },
98373
98374     /**
98375      * @private
98376      * Finds and returns the set of all items bound to fields inside this form
98377      * @return {Ext.util.MixedCollection} The set of all bound form field items
98378      */
98379     getBoundItems: function() {
98380         var boundItems = this._boundItems;
98381         
98382         if (!boundItems || boundItems.getCount() === 0) {
98383             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
98384             boundItems.addAll(this.owner.query('[formBind]'));
98385         }
98386         
98387         return boundItems;
98388     },
98389
98390     /**
98391      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
98392      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
98393      */
98394     hasInvalidField: function() {
98395         return !!this.getFields().findBy(function(field) {
98396             var preventMark = field.preventMark,
98397                 isValid;
98398             field.preventMark = true;
98399             isValid = field.isValid();
98400             field.preventMark = preventMark;
98401             return !isValid;
98402         });
98403     },
98404
98405     /**
98406      * Returns true if client-side validation on the form is successful. Any invalid fields will be
98407      * marked as invalid. If you only want to determine overall form validity without marking anything,
98408      * use {@link #hasInvalidField} instead.
98409      * @return Boolean
98410      */
98411     isValid: function() {
98412         var me = this,
98413             invalid;
98414         me.batchLayouts(function() {
98415             invalid = me.getFields().filterBy(function(field) {
98416                 return !field.validate();
98417             });
98418         });
98419         return invalid.length < 1;
98420     },
98421
98422     /**
98423      * Check whether the validity of the entire form has changed since it was last checked, and
98424      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
98425      * when an individual field's validity changes.
98426      */
98427     checkValidity: function() {
98428         var me = this,
98429             valid = !me.hasInvalidField();
98430         if (valid !== me.wasValid) {
98431             me.onValidityChange(valid);
98432             me.fireEvent('validitychange', me, valid);
98433             me.wasValid = valid;
98434         }
98435     },
98436
98437     /**
98438      * @private
98439      * Handle changes in the form's validity. If there are any sub components with
98440      * formBind=true then they are enabled/disabled based on the new validity.
98441      * @param {Boolean} valid
98442      */
98443     onValidityChange: function(valid) {
98444         var boundItems = this.getBoundItems();
98445         if (boundItems) {
98446             boundItems.each(function(cmp) {
98447                 if (cmp.disabled === valid) {
98448                     cmp.setDisabled(!valid);
98449                 }
98450             });
98451         }
98452     },
98453
98454     /**
98455      * <p>Returns true if any fields in this form have changed from their original values.</p>
98456      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
98457      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
98458      * or {@link #loadRecord}.</p>
98459      * @return Boolean
98460      */
98461     isDirty: function() {
98462         return !!this.getFields().findBy(function(f) {
98463             return f.isDirty();
98464         });
98465     },
98466
98467     /**
98468      * Check whether the dirty state of the entire form has changed since it was last checked, and
98469      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
98470      * when an individual field's dirty state changes.
98471      */
98472     checkDirty: function() {
98473         var dirty = this.isDirty();
98474         if (dirty !== this.wasDirty) {
98475             this.fireEvent('dirtychange', this, dirty);
98476             this.wasDirty = dirty;
98477         }
98478     },
98479
98480     /**
98481      * <p>Returns true if the form contains a file upload field. This is used to determine the
98482      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
98483      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
98484      * element containing all the fields is created temporarily and submitted with its
98485      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
98486      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
98487      * but removed after the return data has been gathered.</p>
98488      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
98489      * server is using JSON to send the return object, then the
98490      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
98491      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
98492      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
98493      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
98494      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
98495      * is created containing a <tt>responseText</tt> property in order to conform to the
98496      * requirements of event handlers and callbacks.</p>
98497      * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
98498      * and some server technologies (notably JEE) may require some custom processing in order to
98499      * retrieve parameter names and parameter values from the packet content.</p>
98500      * @return Boolean
98501      */
98502     hasUpload: function() {
98503         return !!this.getFields().findBy(function(f) {
98504             return f.isFileUpload();
98505         });
98506     },
98507
98508     /**
98509      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
98510      * to perform application-specific processing.
98511      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
98512      * or instance of {@link Ext.form.action.Action} to perform.
98513      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
98514      * that will get created, if the <tt>action</tt> argument is a String.
98515      * <p>All of the config options listed below are supported by both the
98516      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98517      * actions unless otherwise noted (custom actions could also accept
98518      * other config options):</p><ul>
98519      *
98520      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
98521      * to the form's {@link #url}.)</div></li>
98522      *
98523      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
98524      * to the form's method, or POST if not defined)</div></li>
98525      *
98526      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
98527      * (defaults to the form's baseParams, or none if not defined)</p>
98528      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
98529      *
98530      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
98531      *
98532      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
98533      * be invoked after a successful response (see top of
98534      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98535      * for a description of what constitutes a successful response).
98536      * The function is passed the following parameters:<ul>
98537      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98538      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98539      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98540      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98541      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98542      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98543      * </ul></div></li></ul></div></li>
98544      *
98545      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
98546      * failed transaction attempt. The function is passed the following parameters:<ul>
98547      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98548      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98549      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98550      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
98551      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98552      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98553      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98554      * </ul></div></li></ul></div></li>
98555      *
98556      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
98557      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
98558      *
98559      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
98560      * Determines whether a Form's fields are validated in a final call to
98561      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
98562      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
98563      *
98564      * @return {Ext.form.Basic} this
98565      */
98566     doAction: function(action, options) {
98567         if (Ext.isString(action)) {
98568             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
98569         }
98570         if (this.fireEvent('beforeaction', this, action) !== false) {
98571             this.beforeAction(action);
98572             Ext.defer(action.run, 100, action);
98573         }
98574         return this;
98575     },
98576
98577     /**
98578      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
98579      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
98580      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
98581      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
98582      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
98583      * <p>The following code:</p><pre><code>
98584 myFormPanel.getForm().submit({
98585     clientValidation: true,
98586     url: 'updateConsignment.php',
98587     params: {
98588         newStatus: 'delivered'
98589     },
98590     success: function(form, action) {
98591        Ext.Msg.alert('Success', action.result.msg);
98592     },
98593     failure: function(form, action) {
98594         switch (action.failureType) {
98595             case Ext.form.action.Action.CLIENT_INVALID:
98596                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
98597                 break;
98598             case Ext.form.action.Action.CONNECT_FAILURE:
98599                 Ext.Msg.alert('Failure', 'Ajax communication failed');
98600                 break;
98601             case Ext.form.action.Action.SERVER_INVALID:
98602                Ext.Msg.alert('Failure', action.result.msg);
98603        }
98604     }
98605 });
98606 </code></pre>
98607      * would process the following server response for a successful submission:<pre><code>
98608 {
98609     "success":true, // note this is Boolean, not string
98610     "msg":"Consignment updated"
98611 }
98612 </code></pre>
98613      * and the following server response for a failed submission:<pre><code>
98614 {
98615     "success":false, // note this is Boolean, not string
98616     "msg":"You do not have permission to perform this operation"
98617 }
98618 </code></pre>
98619      * @return {Ext.form.Basic} this
98620      */
98621     submit: function(options) {
98622         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
98623     },
98624
98625     /**
98626      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
98627      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
98628      * @return {Ext.form.Basic} this
98629      */
98630     load: function(options) {
98631         return this.doAction(this.api ? 'directload' : 'load', options);
98632     },
98633
98634     /**
98635      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
98636      * @param {Ext.data.Model} record The record to edit
98637      * @return {Ext.form.Basic} this
98638      */
98639     updateRecord: function(record) {
98640         var fields = record.fields,
98641             values = this.getFieldValues(),
98642             name,
98643             obj = {};
98644
98645         fields.each(function(f) {
98646             name = f.name;
98647             if (name in values) {
98648                 obj[name] = values[name];
98649             }
98650         });
98651
98652         record.beginEdit();
98653         record.set(obj);
98654         record.endEdit();
98655
98656         return this;
98657     },
98658
98659     /**
98660      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
98661      * {@link Ext.data.Model#raw record data}.
98662      * See also {@link #trackResetOnLoad}.
98663      * @param {Ext.data.Model} record The record to load
98664      * @return {Ext.form.Basic} this
98665      */
98666     loadRecord: function(record) {
98667         this._record = record;
98668         return this.setValues(record.data);
98669     },
98670
98671     /**
98672      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
98673      * @return {Ext.data.Model} The record
98674      */
98675     getRecord: function() {
98676         return this._record;
98677     },
98678
98679     /**
98680      * @private
98681      * Called before an action is performed via {@link #doAction}.
98682      * @param {Ext.form.action.Action} action The Action instance that was invoked
98683      */
98684     beforeAction: function(action) {
98685         var waitMsg = action.waitMsg,
98686             maskCls = Ext.baseCSSPrefix + 'mask-loading',
98687             waitMsgTarget;
98688
98689         // Call HtmlEditor's syncValue before actions
98690         this.getFields().each(function(f) {
98691             if (f.isFormField && f.syncValue) {
98692                 f.syncValue();
98693             }
98694         });
98695
98696         if (waitMsg) {
98697             waitMsgTarget = this.waitMsgTarget;
98698             if (waitMsgTarget === true) {
98699                 this.owner.el.mask(waitMsg, maskCls);
98700             } else if (waitMsgTarget) {
98701                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
98702                 waitMsgTarget.mask(waitMsg, maskCls);
98703             } else {
98704                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
98705             }
98706         }
98707     },
98708
98709     /**
98710      * @private
98711      * Called after an action is performed via {@link #doAction}.
98712      * @param {Ext.form.action.Action} action The Action instance that was invoked
98713      * @param {Boolean} success True if the action completed successfully, false, otherwise.
98714      */
98715     afterAction: function(action, success) {
98716         if (action.waitMsg) {
98717             var MessageBox = Ext.MessageBox,
98718                 waitMsgTarget = this.waitMsgTarget;
98719             if (waitMsgTarget === true) {
98720                 this.owner.el.unmask();
98721             } else if (waitMsgTarget) {
98722                 waitMsgTarget.unmask();
98723             } else {
98724                 MessageBox.updateProgress(1);
98725                 MessageBox.hide();
98726             }
98727         }
98728         if (success) {
98729             if (action.reset) {
98730                 this.reset();
98731             }
98732             Ext.callback(action.success, action.scope || action, [this, action]);
98733             this.fireEvent('actioncomplete', this, action);
98734         } else {
98735             Ext.callback(action.failure, action.scope || action, [this, action]);
98736             this.fireEvent('actionfailed', this, action);
98737         }
98738     },
98739
98740
98741     /**
98742      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
98743      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
98744      * {@link Ext.form.field.Field#getName name or hiddenName}).
98745      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
98746      */
98747     findField: function(id) {
98748         return this.getFields().findBy(function(f) {
98749             return f.id === id || f.getName() === id;
98750         });
98751     },
98752
98753
98754     /**
98755      * Mark fields in this form invalid in bulk.
98756      * @param {Object/Object[]/Ext.data.Errors} errors
98757      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
98758      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
98759      * @return {Ext.form.Basic} this
98760      */
98761     markInvalid: function(errors) {
98762         var me = this;
98763
98764         function mark(fieldId, msg) {
98765             var field = me.findField(fieldId);
98766             if (field) {
98767                 field.markInvalid(msg);
98768             }
98769         }
98770
98771         if (Ext.isArray(errors)) {
98772             Ext.each(errors, function(err) {
98773                 mark(err.id, err.msg);
98774             });
98775         }
98776         else if (errors instanceof Ext.data.Errors) {
98777             errors.each(function(err) {
98778                 mark(err.field, err.message);
98779             });
98780         }
98781         else {
98782             Ext.iterate(errors, mark);
98783         }
98784         return this;
98785     },
98786
98787     /**
98788      * Set values for fields in this form in bulk.
98789      * @param {Object/Object[]} values Either an array in the form:<pre><code>
98790 [{id:'clientName', value:'Fred. Olsen Lines'},
98791  {id:'portOfLoading', value:'FXT'},
98792  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
98793      * or an object hash of the form:<pre><code>
98794 {
98795     clientName: 'Fred. Olsen Lines',
98796     portOfLoading: 'FXT',
98797     portOfDischarge: 'OSL'
98798 }</code></pre>
98799      * @return {Ext.form.Basic} this
98800      */
98801     setValues: function(values) {
98802         var me = this;
98803
98804         function setVal(fieldId, val) {
98805             var field = me.findField(fieldId);
98806             if (field) {
98807                 field.setValue(val);
98808                 if (me.trackResetOnLoad) {
98809                     field.resetOriginalValue();
98810                 }
98811             }
98812         }
98813
98814         if (Ext.isArray(values)) {
98815             // array of objects
98816             Ext.each(values, function(val) {
98817                 setVal(val.id, val.value);
98818             });
98819         } else {
98820             // object hash
98821             Ext.iterate(values, setVal);
98822         }
98823         return this;
98824     },
98825
98826     /**
98827      * Retrieves the fields in the form as a set of key/value pairs, using their
98828      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
98829      * If multiple fields return values under the same name those values will be combined into an Array.
98830      * This is similar to {@link #getFieldValues} except that this method collects only String values for
98831      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
98832      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
98833      * URL-encoded param string. Defaults to false.
98834      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98835      * Defaults to false.
98836      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
98837      * Defaults to false.
98838      * @return {String/Object}
98839      */
98840     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
98841         var values = {};
98842
98843         this.getFields().each(function(field) {
98844             if (!dirtyOnly || field.isDirty()) {
98845                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
98846                 if (Ext.isObject(data)) {
98847                     Ext.iterate(data, function(name, val) {
98848                         if (includeEmptyText && val === '') {
98849                             val = field.emptyText || '';
98850                         }
98851                         if (name in values) {
98852                             var bucket = values[name],
98853                                 isArray = Ext.isArray;
98854                             if (!isArray(bucket)) {
98855                                 bucket = values[name] = [bucket];
98856                             }
98857                             if (isArray(val)) {
98858                                 values[name] = bucket.concat(val);
98859                             } else {
98860                                 bucket.push(val);
98861                             }
98862                         } else {
98863                             values[name] = val;
98864                         }
98865                     });
98866                 }
98867             }
98868         });
98869
98870         if (asString) {
98871             values = Ext.Object.toQueryString(values);
98872         }
98873         return values;
98874     },
98875
98876     /**
98877      * Retrieves the fields in the form as a set of key/value pairs, using their
98878      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
98879      * If multiple fields return values under the same name those values will be combined into an Array.
98880      * This is similar to {@link #getValues} except that this method collects type-specific data values
98881      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
98882      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98883      * Defaults to false.
98884      * @return {Object}
98885      */
98886     getFieldValues: function(dirtyOnly) {
98887         return this.getValues(false, dirtyOnly, false, true);
98888     },
98889
98890     /**
98891      * Clears all invalid field messages in this form.
98892      * @return {Ext.form.Basic} this
98893      */
98894     clearInvalid: function() {
98895         var me = this;
98896         me.batchLayouts(function() {
98897             me.getFields().each(function(f) {
98898                 f.clearInvalid();
98899             });
98900         });
98901         return me;
98902     },
98903
98904     /**
98905      * Resets all fields in this form.
98906      * @return {Ext.form.Basic} this
98907      */
98908     reset: function() {
98909         var me = this;
98910         me.batchLayouts(function() {
98911             me.getFields().each(function(f) {
98912                 f.reset();
98913             });
98914         });
98915         return me;
98916     },
98917
98918     /**
98919      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
98920      * @param {Object} obj The object to be applied
98921      * @return {Ext.form.Basic} this
98922      */
98923     applyToFields: function(obj) {
98924         this.getFields().each(function(f) {
98925             Ext.apply(f, obj);
98926         });
98927         return this;
98928     },
98929
98930     /**
98931      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
98932      * @param {Object} obj The object to be applied
98933      * @return {Ext.form.Basic} this
98934      */
98935     applyIfToFields: function(obj) {
98936         this.getFields().each(function(f) {
98937             Ext.applyIf(f, obj);
98938         });
98939         return this;
98940     },
98941
98942     /**
98943      * @private
98944      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
98945      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
98946      * @param {Function} fn
98947      */
98948     batchLayouts: function(fn) {
98949         var me = this,
98950             suspended = new Ext.util.HashMap();
98951
98952         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
98953         me.getFields().each(function(field) {
98954             var ownerCt = field.ownerCt;
98955             if (!suspended.contains(ownerCt)) {
98956                 suspended.add(ownerCt);
98957                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
98958                 ownerCt.suspendLayout = true;
98959             }
98960         });
98961
98962         // Invoke the function
98963         fn();
98964
98965         // Un-suspend the container layouts
98966         suspended.each(function(id, ct) {
98967             ct.suspendLayout = ct.oldSuspendLayout;
98968             delete ct.oldSuspendLayout;
98969         });
98970
98971         // Trigger a single layout
98972         me.owner.doComponentLayout();
98973     }
98974 });
98975
98976 /**
98977  * @class Ext.form.FieldAncestor
98978
98979 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
98980 items subtree. Adds the following capabilities:
98981
98982 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
98983   instances at any depth within the container.
98984 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
98985   of individual fields at the container level.
98986 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
98987   container, to facilitate uniform configuration of all fields.
98988
98989 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
98990 and should not normally need to be used directly.
98991
98992  * @markdown
98993  * @docauthor Jason Johnston <jason@sencha.com>
98994  */
98995 Ext.define('Ext.form.FieldAncestor', {
98996
98997     /**
98998      * @cfg {Object} fieldDefaults
98999      * <p>If specified, the properties in this object are used as default config values for each
99000      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
99001      * that is added as a descendant of this container. Corresponding values specified in an individual field's
99002      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
99003      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
99004      * options may be specified in the <tt>fieldDefaults</tt>.</p>
99005      * <p>Example:</p>
99006      * <pre><code>new Ext.form.Panel({
99007     fieldDefaults: {
99008         labelAlign: 'left',
99009         labelWidth: 100
99010     },
99011     items: [{
99012         xtype: 'fieldset',
99013         defaults: {
99014             labelAlign: 'top'
99015         },
99016         items: [{
99017             name: 'field1'
99018         }, {
99019             name: 'field2'
99020         }]
99021     }, {
99022         xtype: 'fieldset',
99023         items: [{
99024             name: 'field3',
99025             labelWidth: 150
99026         }, {
99027             name: 'field4'
99028         }]
99029     }]
99030 });</code></pre>
99031      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
99032      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
99033      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
99034      */
99035
99036
99037     /**
99038      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
99039      * of any components importing this mixin.
99040      */
99041     initFieldAncestor: function() {
99042         var me = this,
99043             onSubtreeChange = me.onFieldAncestorSubtreeChange;
99044
99045         me.addEvents(
99046             /**
99047              * @event fieldvaliditychange
99048              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
99049              * container changes.
99050              * @param {Ext.form.FieldAncestor} this
99051              * @param {Ext.form.Labelable} The Field instance whose validity changed
99052              * @param {String} isValid The field's new validity state
99053              */
99054             'fieldvaliditychange',
99055
99056             /**
99057              * @event fielderrorchange
99058              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
99059              * instances within this container.
99060              * @param {Ext.form.FieldAncestor} this
99061              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
99062              * @param {String} error The active error message
99063              */
99064             'fielderrorchange'
99065         );
99066
99067         // Catch addition and removal of descendant fields
99068         me.on('add', onSubtreeChange, me);
99069         me.on('remove', onSubtreeChange, me);
99070
99071         me.initFieldDefaults();
99072     },
99073
99074     /**
99075      * @private Initialize the {@link #fieldDefaults} object
99076      */
99077     initFieldDefaults: function() {
99078         if (!this.fieldDefaults) {
99079             this.fieldDefaults = {};
99080         }
99081     },
99082
99083     /**
99084      * @private
99085      * Handle the addition and removal of components in the FieldAncestor component's child tree.
99086      */
99087     onFieldAncestorSubtreeChange: function(parent, child) {
99088         var me = this,
99089             isAdding = !!child.ownerCt;
99090
99091         function handleCmp(cmp) {
99092             var isLabelable = cmp.isFieldLabelable,
99093                 isField = cmp.isFormField;
99094             if (isLabelable || isField) {
99095                 if (isLabelable) {
99096                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
99097                 }
99098                 if (isField) {
99099                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
99100                 }
99101             }
99102             else if (cmp.isContainer) {
99103                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
99104             }
99105         }
99106         handleCmp(child);
99107     },
99108
99109     /**
99110      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99111      * @param {Ext.form.Labelable} labelable The instance that was added
99112      */
99113     onLabelableAdded: function(labelable) {
99114         var me = this;
99115
99116         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
99117         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
99118
99119         labelable.setFieldDefaults(me.fieldDefaults);
99120     },
99121
99122     /**
99123      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
99124      * @param {Ext.form.field.Field} field The field which was added
99125      */
99126     onFieldAdded: function(field) {
99127         var me = this;
99128         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
99129     },
99130
99131     /**
99132      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99133      * @param {Ext.form.Labelable} labelable The instance that was removed
99134      */
99135     onLabelableRemoved: function(labelable) {
99136         var me = this;
99137         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
99138     },
99139
99140     /**
99141      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
99142      * @param {Ext.form.field.Field} field The field which was removed
99143      */
99144     onFieldRemoved: function(field) {
99145         var me = this;
99146         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
99147     },
99148
99149     /**
99150      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
99151      */
99152     handleFieldValidityChange: function(field, isValid) {
99153         var me = this;
99154         me.fireEvent('fieldvaliditychange', me, field, isValid);
99155         me.onFieldValidityChange();
99156     },
99157
99158     /**
99159      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
99160      */
99161     handleFieldErrorChange: function(labelable, activeError) {
99162         var me = this;
99163         me.fireEvent('fielderrorchange', me, labelable, activeError);
99164         me.onFieldErrorChange();
99165     },
99166
99167     /**
99168      * @protected Fired when the validity of any field within the container changes.
99169      * @param {Ext.form.field.Field} The sub-field whose validity changed
99170      * @param {String} The new validity state
99171      */
99172     onFieldValidityChange: Ext.emptyFn,
99173
99174     /**
99175      * @protected Fired when the error message of any field within the container changes.
99176      * @param {Ext.form.Labelable} The sub-field whose active error changed
99177      * @param {String} The new active error message
99178      */
99179     onFieldErrorChange: Ext.emptyFn
99180
99181 });
99182 /**
99183  * @class Ext.layout.container.CheckboxGroup
99184  * @extends Ext.layout.container.Container
99185  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
99186  * It groups the component's sub-items into columns based on the component's
99187  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
99188  *
99189  */
99190 Ext.define('Ext.layout.container.CheckboxGroup', {
99191     extend: 'Ext.layout.container.Container',
99192     alias: ['layout.checkboxgroup'],
99193
99194
99195     onLayout: function() {
99196         var numCols = this.getColCount(),
99197             shadowCt = this.getShadowCt(),
99198             owner = this.owner,
99199             items = owner.items,
99200             shadowItems = shadowCt.items,
99201             numItems = items.length,
99202             colIndex = 0,
99203             i, numRows;
99204
99205         // Distribute the items into the appropriate column containers. We add directly to the
99206         // containers' items collection rather than calling container.add(), because we need the
99207         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
99208         // in case items have been added, removed, or reordered.
99209
99210         shadowItems.each(function(col) {
99211             col.items.clear();
99212         });
99213
99214         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
99215         // from the CheckboxGroup; adjust to match.
99216         while (shadowItems.length > numCols) {
99217             shadowCt.remove(shadowItems.last());
99218         }
99219         while (shadowItems.length < numCols) {
99220             shadowCt.add({
99221                 xtype: 'container',
99222                 cls: owner.groupCls,
99223                 flex: 1
99224             });
99225         }
99226
99227         if (owner.vertical) {
99228             numRows = Math.ceil(numItems / numCols);
99229             for (i = 0; i < numItems; i++) {
99230                 if (i > 0 && i % numRows === 0) {
99231                     colIndex++;
99232                 }
99233                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99234             }
99235         } else {
99236             for (i = 0; i < numItems; i++) {
99237                 colIndex = i % numCols;
99238                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99239             }
99240         }
99241
99242         if (!shadowCt.rendered) {
99243             shadowCt.render(this.getRenderTarget());
99244         } else {
99245             // Ensure all items are rendered in the correct place in the correct column - this won't
99246             // get done by the column containers themselves if their dimensions are not changing.
99247             shadowItems.each(function(col) {
99248                 var layout = col.getLayout();
99249                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
99250             });
99251         }
99252
99253         shadowCt.doComponentLayout();
99254     },
99255
99256
99257     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
99258     renderItems: Ext.emptyFn,
99259
99260
99261     /**
99262      * @private
99263      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
99264      * into columns.
99265      */
99266     getShadowCt: function() {
99267         var me = this,
99268             shadowCt = me.shadowCt,
99269             owner, items, item, columns, columnsIsArray, numCols, i;
99270
99271         if (!shadowCt) {
99272             // Create the column containers based on the owner's 'columns' config
99273             owner = me.owner;
99274             columns = owner.columns;
99275             columnsIsArray = Ext.isArray(columns);
99276             numCols = me.getColCount();
99277             items = [];
99278             for(i = 0; i < numCols; i++) {
99279                 item = {
99280                     xtype: 'container',
99281                     cls: owner.groupCls
99282                 };
99283                 if (columnsIsArray) {
99284                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
99285                     // numbers, used as relative flex values.
99286                     if (columns[i] < 1) {
99287                         item.flex = columns[i];
99288                     } else {
99289                         item.width = columns[i];
99290                     }
99291                 }
99292                 else {
99293                     // All columns the same width
99294                     item.flex = 1;
99295                 }
99296                 items.push(item);
99297             }
99298
99299             // Create the shadow container; delay rendering until after items are added to the columns
99300             shadowCt = me.shadowCt = Ext.createWidget('container', {
99301                 layout: 'hbox',
99302                 items: items,
99303                 ownerCt: owner
99304             });
99305         }
99306         
99307         return shadowCt;
99308     },
99309
99310
99311     /**
99312      * @private Get the number of columns in the checkbox group
99313      */
99314     getColCount: function() {
99315         var owner = this.owner,
99316             colsCfg = owner.columns;
99317         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
99318     }
99319
99320 });
99321
99322 /**
99323  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
99324  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
99325  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
99326  * This is useful for arranging a group of fields or other components within a single item in a form, so
99327  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
99328  * a single label in a form.
99329  * 
99330  * The container's configured {@link #items} will be layed out within the field body area according to the
99331  * configured {@link #layout} type. The default layout is `'autocontainer'`.
99332  * 
99333  * Like regular fields, FieldContainer can inherit its decoration configuration from the
99334  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
99335  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
99336  * it may itself contain.
99337  * 
99338  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
99339  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
99340  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
99341  *
99342  * # Example
99343  * 
99344  *     @example
99345  *     Ext.create('Ext.form.Panel', {
99346  *         title: 'FieldContainer Example',
99347  *         width: 550,
99348  *         bodyPadding: 10,
99349  * 
99350  *         items: [{
99351  *             xtype: 'fieldcontainer',
99352  *             fieldLabel: 'Last Three Jobs',
99353  *             labelWidth: 100,
99354  * 
99355  *             // The body area will contain three text fields, arranged
99356  *             // horizontally, separated by draggable splitters.
99357  *             layout: 'hbox',
99358  *             items: [{
99359  *                 xtype: 'textfield',
99360  *                 flex: 1
99361  *             }, {
99362  *                 xtype: 'splitter'
99363  *             }, {
99364  *                 xtype: 'textfield',
99365  *                 flex: 1
99366  *             }, {
99367  *                 xtype: 'splitter'
99368  *             }, {
99369  *                 xtype: 'textfield',
99370  *                 flex: 1
99371  *             }]
99372  *         }],
99373  *         renderTo: Ext.getBody()
99374  *     });
99375  * 
99376  * # Usage of fieldDefaults
99377  *
99378  *     @example
99379  *     Ext.create('Ext.form.Panel', {
99380  *         title: 'FieldContainer Example',
99381  *         width: 350,
99382  *         bodyPadding: 10,
99383  * 
99384  *         items: [{
99385  *             xtype: 'fieldcontainer',
99386  *             fieldLabel: 'Your Name',
99387  *             labelWidth: 75,
99388  *             defaultType: 'textfield',
99389  * 
99390  *             // Arrange fields vertically, stretched to full width
99391  *             layout: 'anchor',
99392  *             defaults: {
99393  *                 layout: '100%'
99394  *             },
99395  * 
99396  *             // These config values will be applied to both sub-fields, except
99397  *             // for Last Name which will use its own msgTarget.
99398  *             fieldDefaults: {
99399  *                 msgTarget: 'under',
99400  *                 labelAlign: 'top'
99401  *             },
99402  * 
99403  *             items: [{
99404  *                 fieldLabel: 'First Name',
99405  *                 name: 'firstName'
99406  *             }, {
99407  *                 fieldLabel: 'Last Name',
99408  *                 name: 'lastName',
99409  *                 msgTarget: 'under'
99410  *             }]
99411  *         }],
99412  *         renderTo: Ext.getBody()
99413  *     });
99414  * 
99415  * @docauthor Jason Johnston <jason@sencha.com>
99416  */
99417 Ext.define('Ext.form.FieldContainer', {
99418     extend: 'Ext.container.Container',
99419     mixins: {
99420         labelable: 'Ext.form.Labelable',
99421         fieldAncestor: 'Ext.form.FieldAncestor'
99422     },
99423     alias: 'widget.fieldcontainer',
99424
99425     componentLayout: 'field',
99426
99427     /**
99428      * @cfg {Boolean} combineLabels
99429      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
99430      * generate its label by combining the labels of all the fields it contains. Defaults to false.
99431      */
99432     combineLabels: false,
99433
99434     /**
99435      * @cfg {String} labelConnector
99436      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
99437      * set to true. Defaults to ', '.
99438      */
99439     labelConnector: ', ',
99440
99441     /**
99442      * @cfg {Boolean} combineErrors
99443      * If set to true, the field container will automatically combine and display the validation errors from
99444      * all the fields it contains as a single error on the container, according to the configured
99445      * {@link #msgTarget}. Defaults to false.
99446      */
99447     combineErrors: false,
99448
99449     maskOnDisable: false,
99450
99451     initComponent: function() {
99452         var me = this,
99453             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
99454
99455         // Init mixins
99456         me.initLabelable();
99457         me.initFieldAncestor();
99458
99459         me.callParent();
99460     },
99461
99462     /**
99463      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99464      * @param {Ext.form.Labelable} labelable The instance that was added
99465      */
99466     onLabelableAdded: function(labelable) {
99467         var me = this;
99468         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
99469         me.updateLabel();
99470     },
99471
99472     /**
99473      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99474      * @param {Ext.form.Labelable} labelable The instance that was removed
99475      */
99476     onLabelableRemoved: function(labelable) {
99477         var me = this;
99478         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
99479         me.updateLabel();
99480     },
99481
99482     onRender: function() {
99483         var me = this;
99484
99485         me.onLabelableRender();
99486
99487         me.callParent(arguments);
99488     },
99489
99490     initRenderTpl: function() {
99491         var me = this;
99492         if (!me.hasOwnProperty('renderTpl')) {
99493             me.renderTpl = me.getTpl('labelableRenderTpl');
99494         }
99495         return me.callParent();
99496     },
99497
99498     initRenderData: function() {
99499         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
99500     },
99501
99502     /**
99503      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
99504      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
99505      * this method to provide a custom generated label.
99506      */
99507     getFieldLabel: function() {
99508         var label = this.fieldLabel || '';
99509         if (!label && this.combineLabels) {
99510             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
99511                 return field.getFieldLabel();
99512             }).join(this.labelConnector);
99513         }
99514         return label;
99515     },
99516
99517     /**
99518      * @private Updates the content of the labelEl if it is rendered
99519      */
99520     updateLabel: function() {
99521         var me = this,
99522             label = me.labelEl;
99523         if (label) {
99524             label.update(me.getFieldLabel());
99525         }
99526     },
99527
99528
99529     /**
99530      * @private Fired when the error message of any field within the container changes, and updates the
99531      * combined error message to match.
99532      */
99533     onFieldErrorChange: function(field, activeError) {
99534         if (this.combineErrors) {
99535             var me = this,
99536                 oldError = me.getActiveError(),
99537                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
99538                     return field.hasActiveError();
99539                 }),
99540                 newErrors = me.getCombinedErrors(invalidFields);
99541
99542             if (newErrors) {
99543                 me.setActiveErrors(newErrors);
99544             } else {
99545                 me.unsetActiveError();
99546             }
99547
99548             if (oldError !== me.getActiveError()) {
99549                 me.doComponentLayout();
99550             }
99551         }
99552     },
99553
99554     /**
99555      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
99556      * messages from them. Defaults to prepending each message by the field name and a colon. This
99557      * can be overridden to provide custom combined error message handling, for instance changing
99558      * the format of each message or sorting the array (it is sorted in order of appearance by default).
99559      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
99560      * @return {String[]} The combined list of error messages
99561      */
99562     getCombinedErrors: function(invalidFields) {
99563         var forEach = Ext.Array.forEach,
99564             errors = [];
99565         forEach(invalidFields, function(field) {
99566             forEach(field.getActiveErrors(), function(error) {
99567                 var label = field.getFieldLabel();
99568                 errors.push((label ? label + ': ' : '') + error);
99569             });
99570         });
99571         return errors;
99572     },
99573
99574     getTargetEl: function() {
99575         return this.bodyEl || this.callParent();
99576     }
99577 });
99578
99579 /**
99580  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
99581  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
99582  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
99583  * and {@link #validate validating} the group of checkboxes as a whole.
99584  *
99585  * # Validation
99586  *
99587  * Individual checkbox fields themselves have no default validation behavior, but
99588  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
99589  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
99590  * least one of the checkboxes, the entire group will be highlighted as invalid and the
99591  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
99592  *
99593  * # Layout
99594  *
99595  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
99596  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
99597  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
99598  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
99599  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
99600  *
99601  *     @example
99602  *     Ext.create('Ext.form.Panel', {
99603  *         title: 'Checkbox Group',
99604  *         width: 300,
99605  *         height: 125,
99606  *         bodyPadding: 10,
99607  *         renderTo: Ext.getBody(),
99608  *         items:[{
99609  *             xtype: 'checkboxgroup',
99610  *             fieldLabel: 'Two Columns',
99611  *             // Arrange radio buttons into two columns, distributed vertically
99612  *             columns: 2,
99613  *             vertical: true,
99614  *             items: [
99615  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
99616  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
99617  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
99618  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
99619  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
99620  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
99621  *             ]
99622  *         }]
99623  *     });
99624  */
99625 Ext.define('Ext.form.CheckboxGroup', {
99626     extend:'Ext.form.FieldContainer',
99627     mixins: {
99628         field: 'Ext.form.field.Field'
99629     },
99630     alias: 'widget.checkboxgroup',
99631     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
99632
99633     /**
99634      * @cfg {String} name
99635      * @hide
99636      */
99637
99638     /**
99639      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
99640      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
99641      */
99642
99643     /**
99644      * @cfg {String/Number/Number[]} columns
99645      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
99646      * This config can take several types of values:
99647      *
99648      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
99649      *   distributed based on the width of the overall field container. This is the default.
99650      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
99651      *   will be automatically distributed based on the value of {@link #vertical}.
99652      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
99653      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
99654      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
99655      *   (100%) although if you want the controls to take up the entire field container you should do so.
99656      */
99657     columns : 'auto',
99658
99659     /**
99660      * @cfg {Boolean} vertical
99661      * True to distribute contained controls across columns, completely filling each column top to bottom before
99662      * starting on the next column. The number of controls in each column will be automatically calculated to keep
99663      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
99664      * completely filling each row left to right before starting on the next row.
99665      */
99666     vertical : false,
99667
99668     /**
99669      * @cfg {Boolean} allowBlank
99670      * False to validate that at least one item in the group is checked. If no items are selected at
99671      * validation time, {@link #blankText} will be used as the error text.
99672      */
99673     allowBlank : true,
99674
99675     /**
99676      * @cfg {String} blankText
99677      * Error text to display if the {@link #allowBlank} validation fails
99678      */
99679     blankText : "You must select at least one item in this group",
99680
99681     // private
99682     defaultType : 'checkboxfield',
99683
99684     // private
99685     groupCls : Ext.baseCSSPrefix + 'form-check-group',
99686
99687     /**
99688      * @cfg {String} fieldBodyCls
99689      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
99690      * Defaults to 'x-form-checkboxgroup-body'.
99691      */
99692     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
99693
99694     // private
99695     layout: 'checkboxgroup',
99696
99697     initComponent: function() {
99698         var me = this;
99699         me.callParent();
99700         me.initField();
99701     },
99702
99703     /**
99704      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
99705      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
99706      * after they have been initialized.
99707      * @protected
99708      */
99709     initValue: function() {
99710         var me = this,
99711             valueCfg = me.value;
99712         me.originalValue = me.lastValue = valueCfg || me.getValue();
99713         if (valueCfg) {
99714             me.setValue(valueCfg);
99715         }
99716     },
99717
99718     /**
99719      * When a checkbox is added to the group, monitor it for changes
99720      * @param {Object} field
99721      * @protected
99722      */
99723     onFieldAdded: function(field) {
99724         var me = this;
99725         if (field.isCheckbox) {
99726             me.mon(field, 'change', me.checkChange, me);
99727         }
99728         me.callParent(arguments);
99729     },
99730
99731     onFieldRemoved: function(field) {
99732         var me = this;
99733         if (field.isCheckbox) {
99734             me.mun(field, 'change', me.checkChange, me);
99735         }
99736         me.callParent(arguments);
99737     },
99738
99739     // private override - the group value is a complex object, compare using object serialization
99740     isEqual: function(value1, value2) {
99741         var toQueryString = Ext.Object.toQueryString;
99742         return toQueryString(value1) === toQueryString(value2);
99743     },
99744
99745     /**
99746      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
99747      * is set to true and no items are checked.
99748      * @return {String[]} Array of all validation errors
99749      */
99750     getErrors: function() {
99751         var errors = [];
99752         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
99753             errors.push(this.blankText);
99754         }
99755         return errors;
99756     },
99757
99758     /**
99759      * @private Returns all checkbox components within the container
99760      */
99761     getBoxes: function() {
99762         return this.query('[isCheckbox]');
99763     },
99764
99765     /**
99766      * @private Convenience function which calls the given function for every checkbox in the group
99767      * @param {Function} fn The function to call
99768      * @param {Object} scope (Optional) scope object
99769      */
99770     eachBox: function(fn, scope) {
99771         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
99772     },
99773
99774     /**
99775      * Returns an Array of all checkboxes in the container which are currently checked
99776      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
99777      */
99778     getChecked: function() {
99779         return Ext.Array.filter(this.getBoxes(), function(cb) {
99780             return cb.getValue();
99781         });
99782     },
99783
99784     // private override
99785     isDirty: function(){
99786         return Ext.Array.some(this.getBoxes(), function(cb) {
99787             return cb.isDirty();
99788         });
99789     },
99790
99791     // private override
99792     setReadOnly: function(readOnly) {
99793         this.eachBox(function(cb) {
99794             cb.setReadOnly(readOnly);
99795         });
99796         this.readOnly = readOnly;
99797     },
99798
99799     /**
99800      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
99801      * loaded values and clears any validation messages.
99802      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
99803      */
99804     reset: function() {
99805         var me = this,
99806             hadError = me.hasActiveError(),
99807             preventMark = me.preventMark;
99808         me.preventMark = true;
99809         me.batchChanges(function() {
99810             me.eachBox(function(cb) {
99811                 cb.reset();
99812             });
99813         });
99814         me.preventMark = preventMark;
99815         me.unsetActiveError();
99816         if (hadError) {
99817             me.doComponentLayout();
99818         }
99819     },
99820
99821     // private override
99822     resetOriginalValue: function() {
99823         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
99824         // the correct data from getValue()
99825         Ext.defer(function() {
99826             this.callParent();
99827         }, 1, this);
99828     },
99829
99830
99831     /**
99832      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
99833      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
99834      *
99835      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
99836      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
99837      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
99838      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
99839      *     checked and others will be unchecked.
99840      *
99841      * If a checkbox's name is not in the mapping at all, it will be unchecked.
99842      *
99843      * An example:
99844      *
99845      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
99846      *         columns: 3,
99847      *         items: [{
99848      *             name: 'cb1',
99849      *             boxLabel: 'Single 1'
99850      *         }, {
99851      *             name: 'cb2',
99852      *             boxLabel: 'Single 2'
99853      *         }, {
99854      *             name: 'cb3',
99855      *             boxLabel: 'Single 3'
99856      *         }, {
99857      *             name: 'cbGroup',
99858      *             boxLabel: 'Grouped 1'
99859      *             inputValue: 'value1'
99860      *         }, {
99861      *             name: 'cbGroup',
99862      *             boxLabel: 'Grouped 2'
99863      *             inputValue: 'value2'
99864      *         }, {
99865      *             name: 'cbGroup',
99866      *             boxLabel: 'Grouped 3'
99867      *             inputValue: 'value3'
99868      *         }]
99869      *     });
99870      *
99871      *     myCheckboxGroup.setValue({
99872      *         cb1: true,
99873      *         cb3: false,
99874      *         cbGroup: ['value1', 'value3']
99875      *     });
99876      *
99877      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
99878      * 'cbGroup'. The other three checkboxes will be unchecked.
99879      *
99880      * @param {Object} value The mapping of checkbox names to values.
99881      * @return {Ext.form.CheckboxGroup} this
99882      */
99883     setValue: function(value) {
99884         var me = this;
99885         me.batchChanges(function() {
99886             me.eachBox(function(cb) {
99887                 var name = cb.getName(),
99888                     cbValue = false;
99889                 if (value && name in value) {
99890                     if (Ext.isArray(value[name])) {
99891                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
99892                     } else {
99893                         // single value, let the checkbox's own setValue handle conversion
99894                         cbValue = value[name];
99895                     }
99896                 }
99897                 cb.setValue(cbValue);
99898             });
99899         });
99900         return me;
99901     },
99902
99903
99904     /**
99905      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
99906      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
99907      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
99908      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
99909      * will be an Array of the selected inputValues.
99910      *
99911      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
99912      *
99913      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
99914      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
99915      * Ext 4+, use the {@link #getChecked} method instead.
99916      */
99917     getValue: function() {
99918         var values = {};
99919         this.eachBox(function(cb) {
99920             var name = cb.getName(),
99921                 inputValue = cb.inputValue,
99922                 bucket;
99923             if (cb.getValue()) {
99924                 if (name in values) {
99925                     bucket = values[name];
99926                     if (!Ext.isArray(bucket)) {
99927                         bucket = values[name] = [bucket];
99928                     }
99929                     bucket.push(inputValue);
99930                 } else {
99931                     values[name] = inputValue;
99932                 }
99933             }
99934         });
99935         return values;
99936     },
99937
99938     /*
99939      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
99940      */
99941     getSubmitData: function() {
99942         return null;
99943     },
99944
99945     /*
99946      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
99947      */
99948     getModelData: function() {
99949         return null;
99950     },
99951
99952     validate: function() {
99953         var me = this,
99954             errors = me.getErrors(),
99955             isValid = Ext.isEmpty(errors),
99956             wasValid = !me.hasActiveError();
99957
99958         if (isValid) {
99959             me.unsetActiveError();
99960         } else {
99961             me.setActiveError(errors);
99962         }
99963         if (isValid !== wasValid) {
99964             me.fireEvent('validitychange', me, isValid);
99965             me.doComponentLayout();
99966         }
99967
99968         return isValid;
99969     }
99970
99971 }, function() {
99972
99973     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
99974
99975 });
99976
99977
99978 /**
99979  * @private
99980  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
99981  */
99982 Ext.define('Ext.form.CheckboxManager', {
99983     extend: 'Ext.util.MixedCollection',
99984     singleton: true,
99985
99986     getByName: function(name) {
99987         return this.filterBy(function(item) {
99988             return item.name == name;
99989         });
99990     },
99991
99992     getWithValue: function(name, value) {
99993         return this.filterBy(function(item) {
99994             return item.name == name && item.inputValue == value;
99995         });
99996     },
99997
99998     getChecked: function(name) {
99999         return this.filterBy(function(item) {
100000             return item.name == name && item.checked;
100001         });
100002     }
100003 });
100004
100005 /**
100006  * @docauthor Jason Johnston <jason@sencha.com>
100007  *
100008  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
100009  * config will be rendered as the fieldset's `legend`.
100010  *
100011  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
100012  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
100013  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
100014  * layout type.
100015  *
100016  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
100017  *
100018  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
100019  *    the {@link #title legend title}, or:
100020  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
100021  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
100022  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
100023  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
100024  *
100025  * # Example usage
100026  *
100027  *     @example
100028  *     Ext.create('Ext.form.Panel', {
100029  *         title: 'Simple Form with FieldSets',
100030  *         labelWidth: 75, // label settings here cascade unless overridden
100031  *         url: 'save-form.php',
100032  *         frame: true,
100033  *         bodyStyle: 'padding:5px 5px 0',
100034  *         width: 550,
100035  *         renderTo: Ext.getBody(),
100036  *         layout: 'column', // arrange fieldsets side by side
100037  *         defaults: {
100038  *             bodyPadding: 4
100039  *         },
100040  *         items: [{
100041  *             // Fieldset in Column 1 - collapsible via toggle button
100042  *             xtype:'fieldset',
100043  *             columnWidth: 0.5,
100044  *             title: 'Fieldset 1',
100045  *             collapsible: true,
100046  *             defaultType: 'textfield',
100047  *             defaults: {anchor: '100%'},
100048  *             layout: 'anchor',
100049  *             items :[{
100050  *                 fieldLabel: 'Field 1',
100051  *                 name: 'field1'
100052  *             }, {
100053  *                 fieldLabel: 'Field 2',
100054  *                 name: 'field2'
100055  *             }]
100056  *         }, {
100057  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
100058  *             xtype:'fieldset',
100059  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
100060  *             columnWidth: 0.5,
100061  *             checkboxToggle: true,
100062  *             collapsed: true, // fieldset initially collapsed
100063  *             layout:'anchor',
100064  *             items :[{
100065  *                 xtype: 'panel',
100066  *                 anchor: '100%',
100067  *                 title: 'Panel inside a fieldset',
100068  *                 frame: true,
100069  *                 height: 52
100070  *             }]
100071  *         }]
100072  *     });
100073  */
100074 Ext.define('Ext.form.FieldSet', {
100075     extend: 'Ext.container.Container',
100076     alias: 'widget.fieldset',
100077     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
100078
100079     /**
100080      * @cfg {String} title
100081      * A title to be displayed in the fieldset's legend. May contain HTML markup.
100082      */
100083
100084     /**
100085      * @cfg {Boolean} [checkboxToggle=false]
100086      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
100087      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
100088      * the {@link #checkboxName}.
100089      */
100090
100091     /**
100092      * @cfg {String} checkboxName
100093      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
100094      * (defaults to '[fieldset id]-checkbox').
100095      */
100096
100097     /**
100098      * @cfg {Boolean} [collapsible=false]
100099      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
100100      * into the legend element, false to keep the fieldset statically sized with no collapse button.
100101      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
100102      * fieldset by default.
100103      */
100104
100105     /**
100106      * @cfg {Boolean} collapsed
100107      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
100108      * will also be unchecked by default.
100109      */
100110     collapsed: false,
100111
100112     /**
100113      * @property {Ext.Component} legend
100114      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
100115      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
100116      */
100117
100118     /**
100119      * @cfg {String} [baseCls='x-fieldset']
100120      * The base CSS class applied to the fieldset.
100121      */
100122     baseCls: Ext.baseCSSPrefix + 'fieldset',
100123
100124     /**
100125      * @cfg {String} layout
100126      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
100127      */
100128     layout: 'anchor',
100129
100130     componentLayout: 'fieldset',
100131
100132     // No aria role necessary as fieldset has its own recognized semantics
100133     ariaRole: '',
100134
100135     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
100136
100137     maskOnDisable: false,
100138
100139     getElConfig: function(){
100140         return {tag: 'fieldset', id: this.id};
100141     },
100142
100143     initComponent: function() {
100144         var me = this,
100145             baseCls = me.baseCls;
100146
100147         me.callParent();
100148
100149         // Create the Legend component if needed
100150         me.initLegend();
100151
100152         // Add body el
100153         me.addChildEls('body');
100154
100155         if (me.collapsed) {
100156             me.addCls(baseCls + '-collapsed');
100157             me.collapse();
100158         }
100159     },
100160
100161     // private
100162     onRender: function(container, position) {
100163         this.callParent(arguments);
100164         // Make sure the legend is created and rendered
100165         this.initLegend();
100166     },
100167
100168     /**
100169      * @private
100170      * Initialize and render the legend component if necessary
100171      */
100172     initLegend: function() {
100173         var me = this,
100174             legendItems,
100175             legend = me.legend;
100176
100177         // Create the legend component if needed and it hasn't been already
100178         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
100179             legendItems = [];
100180
100181             // Checkbox
100182             if (me.checkboxToggle) {
100183                 legendItems.push(me.createCheckboxCmp());
100184             }
100185             // Toggle button
100186             else if (me.collapsible) {
100187                 legendItems.push(me.createToggleCmp());
100188             }
100189
100190             // Title
100191             legendItems.push(me.createTitleCmp());
100192
100193             legend = me.legend = Ext.create('Ext.container.Container', {
100194                 baseCls: me.baseCls + '-header',
100195                 ariaRole: '',
100196                 ownerCt: this,
100197                 getElConfig: function(){
100198                     var result = {
100199                         tag: 'legend',
100200                         cls: this.baseCls
100201                     };
100202
100203                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
100204                     // So here we change every <div> into <span>s. Therefore the following
100205                     // clearer is not needed and since div introduces a lot of subsequent
100206                     // problems, it is actually harmful.
100207                     if (!Ext.isGecko3) {
100208                         result.children = [{
100209                             cls: Ext.baseCSSPrefix + 'clear'
100210                         }];
100211                     }
100212                     return result;
100213                 },
100214                 items: legendItems
100215             });
100216         }
100217
100218         // Make sure legend is rendered if the fieldset is rendered
100219         if (legend && !legend.rendered && me.rendered) {
100220             me.legend.render(me.el, me.body); //insert before body element
100221         }
100222     },
100223
100224     /**
100225      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
100226      * customize the title component.
100227      * @return Ext.Component
100228      * @protected
100229      */
100230     createTitleCmp: function() {
100231         var me = this;
100232         me.titleCmp = Ext.create('Ext.Component', {
100233             html: me.title,
100234             getElConfig: function() {
100235                 return {
100236                     tag: Ext.isGecko3 ? 'span' : 'div',
100237                     cls: me.titleCmp.cls,
100238                     id: me.titleCmp.id
100239                 };
100240             },
100241             cls: me.baseCls + '-header-text'
100242         });
100243         return me.titleCmp;
100244     },
100245
100246     /**
100247      * @property {Ext.form.field.Checkbox} checkboxCmp
100248      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
100249      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
100250      */
100251
100252     /**
100253      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
100254      * customize the checkbox's configuration or even return an entirely different component type.
100255      * @return Ext.Component
100256      * @protected
100257      */
100258     createCheckboxCmp: function() {
100259         var me = this,
100260             suffix = '-checkbox';
100261
100262         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
100263             getElConfig: function() {
100264                 return {
100265                     tag: Ext.isGecko3 ? 'span' : 'div',
100266                     id: me.checkboxCmp.id,
100267                     cls: me.checkboxCmp.cls
100268                 };
100269             },
100270             name: me.checkboxName || me.id + suffix,
100271             cls: me.baseCls + '-header' + suffix,
100272             checked: !me.collapsed,
100273             listeners: {
100274                 change: me.onCheckChange,
100275                 scope: me
100276             }
100277         });
100278         return me.checkboxCmp;
100279     },
100280
100281     /**
100282      * @property {Ext.panel.Tool} toggleCmp
100283      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
100284      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
100285      */
100286
100287     /**
100288      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
100289      * customize the toggle component.
100290      * @return Ext.Component
100291      * @protected
100292      */
100293     createToggleCmp: function() {
100294         var me = this;
100295         me.toggleCmp = Ext.create('Ext.panel.Tool', {
100296             getElConfig: function() {
100297                 return {
100298                     tag: Ext.isGecko3 ? 'span' : 'div',
100299                     id: me.toggleCmp.id,
100300                     cls: me.toggleCmp.cls
100301                 };
100302             },
100303             type: 'toggle',
100304             handler: me.toggle,
100305             scope: me
100306         });
100307         return me.toggleCmp;
100308     },
100309
100310     /**
100311      * Sets the title of this fieldset
100312      * @param {String} title The new title
100313      * @return {Ext.form.FieldSet} this
100314      */
100315     setTitle: function(title) {
100316         var me = this;
100317         me.title = title;
100318         me.initLegend();
100319         me.titleCmp.update(title);
100320         return me;
100321     },
100322
100323     getTargetEl : function() {
100324         return this.body || this.frameBody || this.el;
100325     },
100326
100327     getContentTarget: function() {
100328         return this.body;
100329     },
100330
100331     /**
100332      * @private
100333      * Include the legend component in the items for ComponentQuery
100334      */
100335     getRefItems: function(deep) {
100336         var refItems = this.callParent(arguments),
100337             legend = this.legend;
100338
100339         // Prepend legend items to ensure correct order
100340         if (legend) {
100341             refItems.unshift(legend);
100342             if (deep) {
100343                 refItems.unshift.apply(refItems, legend.getRefItems(true));
100344             }
100345         }
100346         return refItems;
100347     },
100348
100349     /**
100350      * Expands the fieldset.
100351      * @return {Ext.form.FieldSet} this
100352      */
100353     expand : function(){
100354         return this.setExpanded(true);
100355     },
100356
100357     /**
100358      * Collapses the fieldset.
100359      * @return {Ext.form.FieldSet} this
100360      */
100361     collapse : function() {
100362         return this.setExpanded(false);
100363     },
100364
100365     /**
100366      * @private Collapse or expand the fieldset
100367      */
100368     setExpanded: function(expanded) {
100369         var me = this,
100370             checkboxCmp = me.checkboxCmp;
100371
100372         expanded = !!expanded;
100373
100374         if (checkboxCmp) {
100375             checkboxCmp.setValue(expanded);
100376         }
100377
100378         if (expanded) {
100379             me.removeCls(me.baseCls + '-collapsed');
100380         } else {
100381             me.addCls(me.baseCls + '-collapsed');
100382         }
100383         me.collapsed = !expanded;
100384         if (expanded) {
100385             // ensure subitems will get rendered and layed out when expanding
100386             me.getComponentLayout().childrenChanged = true;
100387         }
100388         me.doComponentLayout();
100389         return me;
100390     },
100391
100392     /**
100393      * Toggle the fieldset's collapsed state to the opposite of what it is currently
100394      */
100395     toggle: function() {
100396         this.setExpanded(!!this.collapsed);
100397     },
100398
100399     /**
100400      * @private
100401      * Handle changes in the checkbox checked state
100402      */
100403     onCheckChange: function(cmp, checked) {
100404         this.setExpanded(checked);
100405     },
100406
100407     beforeDestroy : function() {
100408         var legend = this.legend;
100409         if (legend) {
100410             legend.destroy();
100411         }
100412         this.callParent();
100413     }
100414 });
100415
100416 /**
100417  * @docauthor Jason Johnston <jason@sencha.com>
100418  *
100419  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
100420  * in that form using the {@link #forId} property.
100421  * 
100422  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
100423  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
100424  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
100425  * Ext.form.Label should only be used when your layout can not be achieved with the standard
100426  * {@link Ext.form.Labelable field layout}.
100427  * 
100428  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
100429  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
100430  * of that field.
100431  * 
100432  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
100433  * difference between the two is that the former will automatically escape HTML characters when rendering, while
100434  * the latter will not.
100435  *
100436  * # Example
100437  * 
100438  * This example creates a Label after its associated Text field, an arrangement that cannot currently
100439  * be achieved using the standard Field layout's labelAlign.
100440  * 
100441  *     @example
100442  *     Ext.create('Ext.form.Panel', {
100443  *         title: 'Field with Label',
100444  *         width: 400,
100445  *         bodyPadding: 10,
100446  *         renderTo: Ext.getBody(),
100447  *         layout: {
100448  *             type: 'hbox',
100449  *             align: 'middle'
100450  *         },
100451  *         items: [{
100452  *             xtype: 'textfield',
100453  *             hideLabel: true,
100454  *             flex: 1
100455  *         }, {
100456  *             xtype: 'label',
100457  *             forId: 'myFieldId',
100458  *             text: 'My Awesome Field',
100459  *             margins: '0 0 0 10'
100460  *         }]
100461  *     });
100462  */
100463 Ext.define('Ext.form.Label', {
100464     extend:'Ext.Component',
100465     alias: 'widget.label',
100466     requires: ['Ext.util.Format'],
100467
100468     /**
100469      * @cfg {String} [text='']
100470      * The plain text to display within the label. If you need to include HTML
100471      * tags within the label's innerHTML, use the {@link #html} config instead.
100472      */
100473     /**
100474      * @cfg {String} forId
100475      * The id of the input element to which this label will be bound via the standard HTML 'for'
100476      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
100477      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
100478      * the {@link Ext.form.field.Base#inputId inputId} of that field.
100479      */
100480     /**
100481      * @cfg {String} [html='']
100482      * An HTML fragment that will be used as the label's innerHTML.
100483      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
100484      */
100485     
100486     maskOnDisable: false,
100487     getElConfig: function(){
100488         var me = this;
100489         return {
100490             tag: 'label', 
100491             id: me.id, 
100492             htmlFor: me.forId || '',
100493             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
100494         };
100495     },
100496
100497     /**
100498      * Updates the label's innerHTML with the specified string.
100499      * @param {String} text The new label text
100500      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
100501      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
100502      * than rendering them as string literals per the default logic.
100503      * @return {Ext.form.Label} this
100504      */
100505     setText : function(text, encode){
100506         var me = this;
100507         
100508         encode = encode !== false;
100509         if(encode) {
100510             me.text = text;
100511             delete me.html;
100512         } else {
100513             me.html = text;
100514             delete me.text;
100515         }
100516         
100517         if(me.rendered){
100518             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
100519         }
100520         return this;
100521     }
100522 });
100523
100524
100525 /**
100526  * @docauthor Jason Johnston <jason@sencha.com>
100527  * 
100528  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
100529  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
100530  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
100531  * working with the BasicForm and the collection of Fields.
100532  * 
100533  * # Layout
100534  * 
100535  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
100536  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
100537  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
100538  * 
100539  * # BasicForm
100540  * 
100541  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
100542  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
100543  * the internal BasicForm when it is created.
100544  * 
100545  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
100546  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
100547  * configuration settings to `this` will *not* affect the BasicForm's configuration.
100548  * 
100549  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
100550  * listened for on the FormPanel itself:
100551  * 
100552  * - {@link Ext.form.Basic#beforeaction beforeaction}
100553  * - {@link Ext.form.Basic#actionfailed actionfailed}
100554  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
100555  * - {@link Ext.form.Basic#validitychange validitychange}
100556  * - {@link Ext.form.Basic#dirtychange dirtychange}
100557  * 
100558  * # Field Defaults
100559  * 
100560  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
100561  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
100562  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
100563  * for details of how the defaults are applied.
100564  * 
100565  * # Form Validation
100566  * 
100567  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
100568  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
100569  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
100570  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
100571  * 
100572  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
100573  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
100574  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
100575  * any component type.
100576  * 
100577  * For more information on form validation see the following:
100578  * 
100579  * - {@link Ext.form.field.Field#validateOnChange}
100580  * - {@link #pollForChanges} and {@link #pollInterval}
100581  * - {@link Ext.form.field.VTypes}
100582  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
100583  * 
100584  * # Form Submission
100585  * 
100586  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
100587  * {@link Ext.form.Basic} for details.
100588  *
100589  * # Example usage
100590  * 
100591  *     @example
100592  *     Ext.create('Ext.form.Panel', {
100593  *         title: 'Simple Form',
100594  *         bodyPadding: 5,
100595  *         width: 350,
100596  * 
100597  *         // The form will submit an AJAX request to this URL when submitted
100598  *         url: 'save-form.php',
100599  * 
100600  *         // Fields will be arranged vertically, stretched to full width
100601  *         layout: 'anchor',
100602  *         defaults: {
100603  *             anchor: '100%'
100604  *         },
100605  * 
100606  *         // The fields
100607  *         defaultType: 'textfield',
100608  *         items: [{
100609  *             fieldLabel: 'First Name',
100610  *             name: 'first',
100611  *             allowBlank: false
100612  *         },{
100613  *             fieldLabel: 'Last Name',
100614  *             name: 'last',
100615  *             allowBlank: false
100616  *         }],
100617  * 
100618  *         // Reset and Submit buttons
100619  *         buttons: [{
100620  *             text: 'Reset',
100621  *             handler: function() {
100622  *                 this.up('form').getForm().reset();
100623  *             }
100624  *         }, {
100625  *             text: 'Submit',
100626  *             formBind: true, //only enabled once the form is valid
100627  *             disabled: true,
100628  *             handler: function() {
100629  *                 var form = this.up('form').getForm();
100630  *                 if (form.isValid()) {
100631  *                     form.submit({
100632  *                         success: function(form, action) {
100633  *                            Ext.Msg.alert('Success', action.result.msg);
100634  *                         },
100635  *                         failure: function(form, action) {
100636  *                             Ext.Msg.alert('Failed', action.result.msg);
100637  *                         }
100638  *                     });
100639  *                 }
100640  *             }
100641  *         }],
100642  *         renderTo: Ext.getBody()
100643  *     });
100644  *
100645  */
100646 Ext.define('Ext.form.Panel', {
100647     extend:'Ext.panel.Panel',
100648     mixins: {
100649         fieldAncestor: 'Ext.form.FieldAncestor'
100650     },
100651     alias: 'widget.form',
100652     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
100653     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
100654
100655     /**
100656      * @cfg {Boolean} pollForChanges
100657      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
100658      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
100659      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
100660      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
100661      * do not fire native events. Defaults to `false`.
100662      */
100663
100664     /**
100665      * @cfg {Number} pollInterval
100666      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
100667      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
100668      */
100669
100670     /**
100671      * @cfg {String} layout
100672      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
100673      * Defaults to `'anchor'`.
100674      */
100675     layout: 'anchor',
100676
100677     ariaRole: 'form',
100678
100679     initComponent: function() {
100680         var me = this;
100681
100682         if (me.frame) {
100683             me.border = false;
100684         }
100685
100686         me.initFieldAncestor();
100687         me.callParent();
100688
100689         me.relayEvents(me.form, [
100690             'beforeaction',
100691             'actionfailed',
100692             'actioncomplete',
100693             'validitychange',
100694             'dirtychange'
100695         ]);
100696
100697         // Start polling if configured
100698         if (me.pollForChanges) {
100699             me.startPolling(me.pollInterval || 500);
100700         }
100701     },
100702
100703     initItems: function() {
100704         // Create the BasicForm
100705         var me = this;
100706
100707         me.form = me.createForm();
100708         me.callParent();
100709         me.form.initialize();
100710     },
100711
100712     /**
100713      * @private
100714      */
100715     createForm: function() {
100716         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
100717     },
100718
100719     /**
100720      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
100721      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
100722      */
100723     getForm: function() {
100724         return this.form;
100725     },
100726
100727     /**
100728      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
100729      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
100730      * @param {Ext.data.Model} record The record to load
100731      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
100732      */
100733     loadRecord: function(record) {
100734         return this.getForm().loadRecord(record);
100735     },
100736
100737     /**
100738      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
100739      * @return {Ext.data.Model} The loaded instance
100740      */
100741     getRecord: function() {
100742         return this.getForm().getRecord();
100743     },
100744
100745     /**
100746      * Convenience function for fetching the current value of each field in the form. This is the same as calling
100747      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
100748      * @return {Object} The current form field values, keyed by field name
100749      */
100750     getValues: function() {
100751         return this.getForm().getValues();
100752     },
100753
100754     beforeDestroy: function() {
100755         this.stopPolling();
100756         this.form.destroy();
100757         this.callParent();
100758     },
100759
100760     /**
100761      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
100762      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
100763      * {@link Ext.form.Basic#doAction} for details)
100764      */
100765     load: function(options) {
100766         this.form.load(options);
100767     },
100768
100769     /**
100770      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
100771      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
100772      * {@link Ext.form.Basic#doAction} for details)
100773      */
100774     submit: function(options) {
100775         this.form.submit(options);
100776     },
100777
100778     /*
100779      * Inherit docs, not using onDisable because it only gets fired
100780      * when the component is rendered.
100781      */
100782     disable: function(silent) {
100783         this.callParent(arguments);
100784         this.form.getFields().each(function(field) {
100785             field.disable();
100786         });
100787     },
100788
100789     /*
100790      * Inherit docs, not using onEnable because it only gets fired
100791      * when the component is rendered.
100792      */
100793     enable: function(silent) {
100794         this.callParent(arguments);
100795         this.form.getFields().each(function(field) {
100796             field.enable();
100797         });
100798     },
100799
100800     /**
100801      * Start an interval task to continuously poll all the fields in the form for changes in their
100802      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
100803      * @param {Number} interval The interval in milliseconds at which the check should run.
100804      */
100805     startPolling: function(interval) {
100806         this.stopPolling();
100807         var task = Ext.create('Ext.util.TaskRunner', interval);
100808         task.start({
100809             interval: 0,
100810             run: this.checkChange,
100811             scope: this
100812         });
100813         this.pollTask = task;
100814     },
100815
100816     /**
100817      * Stop a running interval task that was started by {@link #startPolling}.
100818      */
100819     stopPolling: function() {
100820         var task = this.pollTask;
100821         if (task) {
100822             task.stopAll();
100823             delete this.pollTask;
100824         }
100825     },
100826
100827     /**
100828      * Forces each field within the form panel to
100829      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
100830      */
100831     checkChange: function() {
100832         this.form.getFields().each(function(field) {
100833             field.checkChange();
100834         });
100835     }
100836 });
100837
100838 /**
100839  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
100840  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
100841  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
100842  * group of radio buttons as a whole.
100843  *
100844  * # Validation
100845  *
100846  * Individual radio buttons themselves have no default validation behavior, but
100847  * sometimes you want to require a user to select one of a group of radios. RadioGroup
100848  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
100849  * one of the radio buttons, the entire group will be highlighted as invalid and the
100850  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
100851  *
100852  * # Layout
100853  *
100854  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
100855  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
100856  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
100857  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
100858  * the Radio components at any depth will still be managed by the RadioGroup's validation.
100859  *
100860  * # Example usage
100861  *
100862  *     @example
100863  *     Ext.create('Ext.form.Panel', {
100864  *         title: 'RadioGroup Example',
100865  *         width: 300,
100866  *         height: 125,
100867  *         bodyPadding: 10,
100868  *         renderTo: Ext.getBody(),
100869  *         items:[{
100870  *             xtype: 'radiogroup',
100871  *             fieldLabel: 'Two Columns',
100872  *             // Arrange radio buttons into two columns, distributed vertically
100873  *             columns: 2,
100874  *             vertical: true,
100875  *             items: [
100876  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
100877  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
100878  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
100879  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
100880  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
100881  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
100882  *             ]
100883  *         }]
100884  *     });
100885  *
100886  */
100887 Ext.define('Ext.form.RadioGroup', {
100888     extend: 'Ext.form.CheckboxGroup',
100889     alias: 'widget.radiogroup',
100890
100891     /**
100892      * @cfg {Ext.form.field.Radio[]/Object[]} items
100893      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
100894      */
100895     /**
100896      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
100897      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
100898      * be used as the error text.
100899      */
100900     allowBlank : true,
100901     /**
100902      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
100903      */
100904     blankText : 'You must select one item in this group',
100905
100906     // private
100907     defaultType : 'radiofield',
100908
100909     // private
100910     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
100911
100912     getBoxes: function() {
100913         return this.query('[isRadio]');
100914     },
100915
100916     /**
100917      * Sets the value of the radio group. The radio with corresponding name and value will be set.
100918      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
100919      * for each name.
100920      * 
100921      * @param {Object} value The map from names to values to be set.
100922      * @return {Ext.form.CheckboxGroup} this
100923      */
100924     setValue: function(value) {
100925         var me = this;
100926         if (Ext.isObject(value)) {
100927             Ext.Object.each(value, function(name, cbValue) {
100928                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
100929                 radios.each(function(cb) {
100930                     cb.setValue(true);
100931                 });
100932             });
100933         }
100934         return me;
100935     }
100936 });
100937
100938 /**
100939  * @private
100940  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
100941  */
100942 Ext.define('Ext.form.RadioManager', {
100943     extend: 'Ext.util.MixedCollection',
100944     singleton: true,
100945
100946     getByName: function(name) {
100947         return this.filterBy(function(item) {
100948             return item.name == name;
100949         });
100950     },
100951
100952     getWithValue: function(name, value) {
100953         return this.filterBy(function(item) {
100954             return item.name == name && item.inputValue == value;
100955         });
100956     },
100957
100958     getChecked: function(name) {
100959         return this.findBy(function(item) {
100960             return item.name == name && item.checked;
100961         });
100962     }
100963 });
100964
100965 /**
100966  * @class Ext.form.action.DirectLoad
100967  * @extends Ext.form.action.Load
100968  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
100969  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
100970  * <pre><code>
100971 var myFormPanel = new Ext.form.Panel({
100972     // configs for FormPanel
100973     title: 'Basic Information',
100974     renderTo: document.body,
100975     width: 300, height: 160,
100976     padding: 10,
100977
100978     // configs apply to child items
100979     defaults: {anchor: '100%'},
100980     defaultType: 'textfield',
100981     items: [{
100982         fieldLabel: 'Name',
100983         name: 'name'
100984     },{
100985         fieldLabel: 'Email',
100986         name: 'email'
100987     },{
100988         fieldLabel: 'Company',
100989         name: 'company'
100990     }],
100991
100992     // configs for BasicForm
100993     api: {
100994         // The server-side method to call for load() requests
100995         load: Profile.getBasicInfo,
100996         // The server-side must mark the submit handler as a 'formHandler'
100997         submit: Profile.updateBasicInfo
100998     },
100999     // specify the order for the passed params
101000     paramOrder: ['uid', 'foo']
101001 });
101002
101003 // load the form
101004 myFormPanel.getForm().load({
101005     // pass 2 arguments to server side getBasicInfo method (len=2)
101006     params: {
101007         foo: 'bar',
101008         uid: 34
101009     }
101010 });
101011  * </code></pre>
101012  * The data packet sent to the server will resemble something like:
101013  * <pre><code>
101014 [
101015     {
101016         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101017         "data":[34,"bar"] // note the order of the params
101018     }
101019 ]
101020  * </code></pre>
101021  * The form will process a data packet returned by the server that is similar
101022  * to the following format:
101023  * <pre><code>
101024 [
101025     {
101026         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101027         "result":{
101028             "success":true,
101029             "data":{
101030                 "name":"Fred Flintstone",
101031                 "company":"Slate Rock and Gravel",
101032                 "email":"fred.flintstone@slaterg.com"
101033             }
101034         }
101035     }
101036 ]
101037  * </code></pre>
101038  */
101039 Ext.define('Ext.form.action.DirectLoad', {
101040     extend:'Ext.form.action.Load',
101041     requires: ['Ext.direct.Manager'],
101042     alternateClassName: 'Ext.form.Action.DirectLoad',
101043     alias: 'formaction.directload',
101044
101045     type: 'directload',
101046
101047     run: function() {
101048         this.form.api.load.apply(window, this.getArgs());
101049     },
101050
101051     /**
101052      * @private
101053      * Build the arguments to be sent to the Direct call.
101054      * @return Array
101055      */
101056     getArgs: function() {
101057         var me = this,
101058             args = [],
101059             form = me.form,
101060             paramOrder = form.paramOrder,
101061             params = me.getParams(),
101062             i, len;
101063
101064         // If a paramOrder was specified, add the params into the argument list in that order.
101065         if (paramOrder) {
101066             for (i = 0, len = paramOrder.length; i < len; i++) {
101067                 args.push(params[paramOrder[i]]);
101068             }
101069         }
101070         // If paramsAsHash was specified, add all the params as a single object argument.
101071         else if (form.paramsAsHash) {
101072             args.push(params);
101073         }
101074
101075         // Add the callback and scope to the end of the arguments list
101076         args.push(me.onSuccess, me);
101077
101078         return args;
101079     },
101080
101081     // Direct actions have already been processed and therefore
101082     // we can directly set the result; Direct Actions do not have
101083     // a this.response property.
101084     processResponse: function(result) {
101085         return (this.result = result);
101086     },
101087
101088     onSuccess: function(result, trans) {
101089         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
101090             result = {};
101091         }
101092         this.callParent([result]);
101093     }
101094 });
101095
101096
101097
101098 /**
101099  * @class Ext.form.action.DirectSubmit
101100  * @extends Ext.form.action.Submit
101101  * <p>Provides Ext.direct support for submitting form data.</p>
101102  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
101103  * <pre><code>
101104 var myFormPanel = new Ext.form.Panel({
101105     // configs for FormPanel
101106     title: 'Basic Information',
101107     renderTo: document.body,
101108     width: 300, height: 160,
101109     padding: 10,
101110     buttons:[{
101111         text: 'Submit',
101112         handler: function(){
101113             myFormPanel.getForm().submit({
101114                 params: {
101115                     foo: 'bar',
101116                     uid: 34
101117                 }
101118             });
101119         }
101120     }],
101121
101122     // configs apply to child items
101123     defaults: {anchor: '100%'},
101124     defaultType: 'textfield',
101125     items: [{
101126         fieldLabel: 'Name',
101127         name: 'name'
101128     },{
101129         fieldLabel: 'Email',
101130         name: 'email'
101131     },{
101132         fieldLabel: 'Company',
101133         name: 'company'
101134     }],
101135
101136     // configs for BasicForm
101137     api: {
101138         // The server-side method to call for load() requests
101139         load: Profile.getBasicInfo,
101140         // The server-side must mark the submit handler as a 'formHandler'
101141         submit: Profile.updateBasicInfo
101142     },
101143     // specify the order for the passed params
101144     paramOrder: ['uid', 'foo']
101145 });
101146  * </code></pre>
101147  * The data packet sent to the server will resemble something like:
101148  * <pre><code>
101149 {
101150     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101151     "result":{
101152         "success":true,
101153         "id":{
101154             "extAction":"Profile","extMethod":"updateBasicInfo",
101155             "extType":"rpc","extTID":"6","extUpload":"false",
101156             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
101157         }
101158     }
101159 }
101160  * </code></pre>
101161  * The form will process a data packet returned by the server that is similar
101162  * to the following:
101163  * <pre><code>
101164 // sample success packet (batched requests)
101165 [
101166     {
101167         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
101168         "result":{
101169             "success":true
101170         }
101171     }
101172 ]
101173
101174 // sample failure packet (one request)
101175 {
101176         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101177         "result":{
101178             "errors":{
101179                 "email":"already taken"
101180             },
101181             "success":false,
101182             "foo":"bar"
101183         }
101184 }
101185  * </code></pre>
101186  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
101187  */
101188 Ext.define('Ext.form.action.DirectSubmit', {
101189     extend:'Ext.form.action.Submit',
101190     requires: ['Ext.direct.Manager'],
101191     alternateClassName: 'Ext.form.Action.DirectSubmit',
101192     alias: 'formaction.directsubmit',
101193
101194     type: 'directsubmit',
101195
101196     doSubmit: function() {
101197         var me = this,
101198             callback = Ext.Function.bind(me.onSuccess, me),
101199             formEl = me.buildForm();
101200         me.form.api.submit(formEl, callback, me);
101201         Ext.removeNode(formEl);
101202     },
101203
101204     // Direct actions have already been processed and therefore
101205     // we can directly set the result; Direct Actions do not have
101206     // a this.response property.
101207     processResponse: function(result) {
101208         return (this.result = result);
101209     },
101210
101211     onSuccess: function(response, trans) {
101212         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
101213             response = {};
101214         }
101215         this.callParent([response]);
101216     }
101217 });
101218
101219 /**
101220  * @class Ext.form.action.StandardSubmit
101221  * @extends Ext.form.action.Submit
101222  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
101223  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
101224  * <p>If validation of the form fields fails, the Form's afterAction method
101225  * will be called. Otherwise, afterAction will not be called.</p>
101226  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
101227  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
101228  * config option is <tt>true</tt>.</p>
101229  */
101230 Ext.define('Ext.form.action.StandardSubmit', {
101231     extend:'Ext.form.action.Submit',
101232     alias: 'formaction.standardsubmit',
101233
101234     /**
101235      * @cfg {String} target
101236      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
101237      * the target will be the current window/frame.
101238      */
101239
101240     /**
101241      * @private
101242      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
101243      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
101244      * {@link Ext.form.Basic#baseParams baseParams}.
101245      */
101246     doSubmit: function() {
101247         var form = this.buildForm();
101248         form.submit();
101249         Ext.removeNode(form);
101250     }
101251
101252 });
101253
101254 /**
101255  * @docauthor Robert Dougan <rob@sencha.com>
101256  *
101257  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
101258  * parent class for {@link Ext.form.field.Radio radio buttons}.
101259  *
101260  * # Labeling
101261  *
101262  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
101263  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
101264  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
101265  *
101266  * # Values
101267  *
101268  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
101269  * The following values will check the checkbox:
101270  *
101271  * - `true`
101272  * - `'true'`
101273  * - `'1'`
101274  * - `'on'`
101275  *
101276  * Any other value will uncheck the checkbox.
101277  *
101278  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
101279  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
101280  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
101281  * will be used.
101282  *
101283  * # Example usage
101284  *
101285  *     @example
101286  *     Ext.create('Ext.form.Panel', {
101287  *         bodyPadding: 10,
101288  *         width: 300,
101289  *         title: 'Pizza Order',
101290  *         items: [
101291  *             {
101292  *                 xtype: 'fieldcontainer',
101293  *                 fieldLabel: 'Toppings',
101294  *                 defaultType: 'checkboxfield',
101295  *                 items: [
101296  *                     {
101297  *                         boxLabel  : 'Anchovies',
101298  *                         name      : 'topping',
101299  *                         inputValue: '1',
101300  *                         id        : 'checkbox1'
101301  *                     }, {
101302  *                         boxLabel  : 'Artichoke Hearts',
101303  *                         name      : 'topping',
101304  *                         inputValue: '2',
101305  *                         checked   : true,
101306  *                         id        : 'checkbox2'
101307  *                     }, {
101308  *                         boxLabel  : 'Bacon',
101309  *                         name      : 'topping',
101310  *                         inputValue: '3',
101311  *                         id        : 'checkbox3'
101312  *                     }
101313  *                 ]
101314  *             }
101315  *         ],
101316  *         bbar: [
101317  *             {
101318  *                 text: 'Select Bacon',
101319  *                 handler: function() {
101320  *                     Ext.getCmp('checkbox3').setValue(true);
101321  *                 }
101322  *             },
101323  *             '-',
101324  *             {
101325  *                 text: 'Select All',
101326  *                 handler: function() {
101327  *                     Ext.getCmp('checkbox1').setValue(true);
101328  *                     Ext.getCmp('checkbox2').setValue(true);
101329  *                     Ext.getCmp('checkbox3').setValue(true);
101330  *                 }
101331  *             },
101332  *             {
101333  *                 text: 'Deselect All',
101334  *                 handler: function() {
101335  *                     Ext.getCmp('checkbox1').setValue(false);
101336  *                     Ext.getCmp('checkbox2').setValue(false);
101337  *                     Ext.getCmp('checkbox3').setValue(false);
101338  *                 }
101339  *             }
101340  *         ],
101341  *         renderTo: Ext.getBody()
101342  *     });
101343  */
101344 Ext.define('Ext.form.field.Checkbox', {
101345     extend: 'Ext.form.field.Base',
101346     alias: ['widget.checkboxfield', 'widget.checkbox'],
101347     alternateClassName: 'Ext.form.Checkbox',
101348     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
101349
101350     // note: {id} here is really {inputId}, but {cmpId} is available
101351     fieldSubTpl: [
101352         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
101353             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101354         '</tpl>',
101355         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
101356         // styled with a custom checkbox image. This allows greater control and consistency in
101357         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
101358         '<input type="button" id="{id}" ',
101359             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
101360             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
101361         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
101362             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101363         '</tpl>',
101364         {
101365             disableFormats: true,
101366             compiled: true
101367         }
101368     ],
101369
101370     isCheckbox: true,
101371
101372     /**
101373      * @cfg {String} [focusCls='x-form-cb-focus']
101374      * The CSS class to use when the checkbox receives focus
101375      */
101376     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
101377
101378     /**
101379      * @cfg {String} [fieldCls='x-form-field']
101380      * The default CSS class for the checkbox
101381      */
101382
101383     /**
101384      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
101385      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
101386      * .
101387      */
101388     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
101389
101390     /**
101391      * @cfg {Boolean} checked
101392      * true if the checkbox should render initially checked
101393      */
101394     checked: false,
101395
101396     /**
101397      * @cfg {String} [checkedCls='x-form-cb-checked']
101398      * The CSS class added to the component's main element when it is in the checked state.
101399      */
101400     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
101401
101402     /**
101403      * @cfg {String} boxLabel
101404      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
101405      * determined by the {@link #boxLabelAlign} config.
101406      */
101407
101408     /**
101409      * @cfg {String} [boxLabelCls='x-form-cb-label']
101410      * The CSS class to be applied to the {@link #boxLabel} element
101411      */
101412     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
101413
101414     /**
101415      * @cfg {String} boxLabelAlign
101416      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
101417      * and 'after'.
101418      */
101419     boxLabelAlign: 'after',
101420
101421     /**
101422      * @cfg {String} inputValue
101423      * The value that should go into the generated input element's value attribute and should be used as the parameter
101424      * value when submitting as part of a form.
101425      */
101426     inputValue: 'on',
101427
101428     /**
101429      * @cfg {String} uncheckedValue
101430      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
101431      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
101432      * submitted (the default behavior of HTML checkboxes).
101433      */
101434
101435     /**
101436      * @cfg {Function} handler
101437      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
101438      * change event}).
101439      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
101440      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
101441      */
101442
101443     /**
101444      * @cfg {Object} scope
101445      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
101446      */
101447
101448     // private overrides
101449     checkChangeEvents: [],
101450     inputType: 'checkbox',
101451     ariaRole: 'checkbox',
101452
101453     // private
101454     onRe: /^on$/i,
101455
101456     initComponent: function(){
101457         this.callParent(arguments);
101458         this.getManager().add(this);
101459     },
101460
101461     initValue: function() {
101462         var me = this,
101463             checked = !!me.checked;
101464
101465         /**
101466          * @property {Object} originalValue
101467          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
101468          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
101469          */
101470         me.originalValue = me.lastValue = checked;
101471
101472         // Set the initial checked state
101473         me.setValue(checked);
101474     },
101475
101476     // private
101477     onRender : function(ct, position) {
101478         var me = this;
101479
101480         /**
101481          * @property {Ext.Element} boxLabelEl
101482          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
101483          * rendered and has a boxLabel configured.
101484          */
101485         me.addChildEls('boxLabelEl');
101486
101487         Ext.applyIf(me.subTplData, {
101488             boxLabel: me.boxLabel,
101489             boxLabelCls: me.boxLabelCls,
101490             boxLabelAlign: me.boxLabelAlign
101491         });
101492
101493         me.callParent(arguments);
101494     },
101495
101496     initEvents: function() {
101497         var me = this;
101498         me.callParent();
101499         me.mon(me.inputEl, 'click', me.onBoxClick, me);
101500     },
101501
101502     /**
101503      * @private Handle click on the checkbox button
101504      */
101505     onBoxClick: function(e) {
101506         var me = this;
101507         if (!me.disabled && !me.readOnly) {
101508             this.setValue(!this.checked);
101509         }
101510     },
101511
101512     /**
101513      * Returns the checked state of the checkbox.
101514      * @return {Boolean} True if checked, else false
101515      */
101516     getRawValue: function() {
101517         return this.checked;
101518     },
101519
101520     /**
101521      * Returns the checked state of the checkbox.
101522      * @return {Boolean} True if checked, else false
101523      */
101524     getValue: function() {
101525         return this.checked;
101526     },
101527
101528     /**
101529      * Returns the submit value for the checkbox which can be used when submitting forms.
101530      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
101531      */
101532     getSubmitValue: function() {
101533         var unchecked = this.uncheckedValue,
101534             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
101535         return this.checked ? this.inputValue : uncheckedVal;
101536     },
101537
101538     /**
101539      * Sets the checked state of the checkbox.
101540      *
101541      * @param {Boolean/String/Number} value The following values will check the checkbox:
101542      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
101543      * Any other value will uncheck the checkbox.
101544      * @return {Boolean} the new checked state of the checkbox
101545      */
101546     setRawValue: function(value) {
101547         var me = this,
101548             inputEl = me.inputEl,
101549             inputValue = me.inputValue,
101550             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
101551                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
101552
101553         if (inputEl) {
101554             inputEl.dom.setAttribute('aria-checked', checked);
101555             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
101556         }
101557
101558         me.checked = me.rawValue = checked;
101559         return checked;
101560     },
101561
101562     /**
101563      * Sets the checked state of the checkbox, and invokes change detection.
101564      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
101565      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
101566      * @return {Ext.form.field.Checkbox} this
101567      */
101568     setValue: function(checked) {
101569         var me = this;
101570
101571         // If an array of strings is passed, find all checkboxes in the group with the same name as this
101572         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
101573         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
101574         // don't want users depending on this behavior.
101575         if (Ext.isArray(checked)) {
101576             me.getManager().getByName(me.name).each(function(cb) {
101577                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
101578             });
101579         } else {
101580             me.callParent(arguments);
101581         }
101582
101583         return me;
101584     },
101585
101586     // private
101587     valueToRaw: function(value) {
101588         // No extra conversion for checkboxes
101589         return value;
101590     },
101591
101592     /**
101593      * @private
101594      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
101595      * function if specified.
101596      */
101597     onChange: function(newVal, oldVal) {
101598         var me = this,
101599             handler = me.handler;
101600         if (handler) {
101601             handler.call(me.scope || me, me, newVal);
101602         }
101603         me.callParent(arguments);
101604     },
101605
101606     // inherit docs
101607     beforeDestroy: function(){
101608         this.callParent();
101609         this.getManager().removeAtKey(this.id);
101610     },
101611
101612     // inherit docs
101613     getManager: function() {
101614         return Ext.form.CheckboxManager;
101615     },
101616
101617     onEnable: function() {
101618         var me = this,
101619             inputEl = me.inputEl;
101620         me.callParent();
101621         if (inputEl) {
101622             // Can still be disabled if the field is readOnly
101623             inputEl.dom.disabled = me.readOnly;
101624         }
101625     },
101626
101627     setReadOnly: function(readOnly) {
101628         var me = this,
101629             inputEl = me.inputEl;
101630         if (inputEl) {
101631             // Set the button to disabled when readonly
101632             inputEl.dom.disabled = readOnly || me.disabled;
101633         }
101634         me.readOnly = readOnly;
101635     },
101636
101637     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
101638     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
101639     getBodyNaturalWidth: function() {
101640         var me = this,
101641             bodyEl = me.bodyEl,
101642             ws = 'white-space',
101643             width;
101644         bodyEl.setStyle(ws, 'nowrap');
101645         width = bodyEl.getWidth();
101646         bodyEl.setStyle(ws, '');
101647         return width;
101648     }
101649
101650 });
101651
101652 /**
101653  * @private
101654  * @class Ext.layout.component.field.Trigger
101655  * @extends Ext.layout.component.field.Field
101656  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
101657  * the trigger button(s).
101658  * @private
101659  */
101660
101661 Ext.define('Ext.layout.component.field.Trigger', {
101662
101663     /* Begin Definitions */
101664
101665     alias: ['layout.triggerfield'],
101666
101667     extend: 'Ext.layout.component.field.Field',
101668
101669     /* End Definitions */
101670
101671     type: 'triggerfield',
101672
101673     sizeBodyContents: function(width, height) {
101674         var me = this,
101675             owner = me.owner,
101676             inputEl = owner.inputEl,
101677             triggerWrap = owner.triggerWrap,
101678             triggerWidth = owner.getTriggerWidth();
101679
101680         // If we or our ancestor is hidden, we can get a triggerWidth calculation
101681         // of 0.  We don't want to resize in this case.
101682         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
101683             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
101684             // are floated left in CSS so they'll stack up side by side.
101685             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
101686     
101687             // Explicitly set the triggerWrap's width, to prevent wrapping
101688             triggerWrap.setWidth(triggerWidth);
101689         }
101690     }
101691 });
101692 /**
101693  * A mechanism for displaying data using custom layout templates and formatting.
101694  *
101695  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
101696  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
101697  * to reflect the changes. The view also provides built-in behavior for many common events that can
101698  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
101699  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
101700  * be provided for the DataView to determine what nodes it will be working with.**
101701  *
101702  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
101703  *
101704  *     @example
101705  *     Ext.define('Image', {
101706  *         extend: 'Ext.data.Model',
101707  *         fields: [
101708  *             { name:'src', type:'string' },
101709  *             { name:'caption', type:'string' }
101710  *         ]
101711  *     });
101712  *
101713  *     Ext.create('Ext.data.Store', {
101714  *         id:'imagesStore',
101715  *         model: 'Image',
101716  *         data: [
101717  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
101718  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
101719  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
101720  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
101721  *         ]
101722  *     });
101723  *
101724  *     var imageTpl = new Ext.XTemplate(
101725  *         '<tpl for=".">',
101726  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
101727  *               '<img src="{src}" />',
101728  *               '<br/><span>{caption}</span>',
101729  *             '</div>',
101730  *         '</tpl>'
101731  *     );
101732  *
101733  *     Ext.create('Ext.view.View', {
101734  *         store: Ext.data.StoreManager.lookup('imagesStore'),
101735  *         tpl: imageTpl,
101736  *         itemSelector: 'div.thumb-wrap',
101737  *         emptyText: 'No images available',
101738  *         renderTo: Ext.getBody()
101739  *     });
101740  */
101741 Ext.define('Ext.view.View', {
101742     extend: 'Ext.view.AbstractView',
101743     alternateClassName: 'Ext.DataView',
101744     alias: 'widget.dataview',
101745
101746     inheritableStatics: {
101747         EventMap: {
101748             mousedown: 'MouseDown',
101749             mouseup: 'MouseUp',
101750             click: 'Click',
101751             dblclick: 'DblClick',
101752             contextmenu: 'ContextMenu',
101753             mouseover: 'MouseOver',
101754             mouseout: 'MouseOut',
101755             mouseenter: 'MouseEnter',
101756             mouseleave: 'MouseLeave',
101757             keydown: 'KeyDown',
101758             focus: 'Focus'
101759         }
101760     },
101761
101762     addCmpEvents: function() {
101763         this.addEvents(
101764             /**
101765              * @event beforeitemmousedown
101766              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
101767              * @param {Ext.view.View} this
101768              * @param {Ext.data.Model} record The record that belongs to the item
101769              * @param {HTMLElement} item The item's element
101770              * @param {Number} index The item's index
101771              * @param {Ext.EventObject} e The raw event object
101772              */
101773             'beforeitemmousedown',
101774             /**
101775              * @event beforeitemmouseup
101776              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
101777              * @param {Ext.view.View} this
101778              * @param {Ext.data.Model} record The record that belongs to the item
101779              * @param {HTMLElement} item The item's element
101780              * @param {Number} index The item's index
101781              * @param {Ext.EventObject} e The raw event object
101782              */
101783             'beforeitemmouseup',
101784             /**
101785              * @event beforeitemmouseenter
101786              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
101787              * @param {Ext.view.View} this
101788              * @param {Ext.data.Model} record The record that belongs to the item
101789              * @param {HTMLElement} item The item's element
101790              * @param {Number} index The item's index
101791              * @param {Ext.EventObject} e The raw event object
101792              */
101793             'beforeitemmouseenter',
101794             /**
101795              * @event beforeitemmouseleave
101796              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
101797              * @param {Ext.view.View} this
101798              * @param {Ext.data.Model} record The record that belongs to the item
101799              * @param {HTMLElement} item The item's element
101800              * @param {Number} index The item's index
101801              * @param {Ext.EventObject} e The raw event object
101802              */
101803             'beforeitemmouseleave',
101804             /**
101805              * @event beforeitemclick
101806              * Fires before the click event on an item is processed. Returns false to cancel the default action.
101807              * @param {Ext.view.View} this
101808              * @param {Ext.data.Model} record The record that belongs to the item
101809              * @param {HTMLElement} item The item's element
101810              * @param {Number} index The item's index
101811              * @param {Ext.EventObject} e The raw event object
101812              */
101813             'beforeitemclick',
101814             /**
101815              * @event beforeitemdblclick
101816              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
101817              * @param {Ext.view.View} this
101818              * @param {Ext.data.Model} record The record that belongs to the item
101819              * @param {HTMLElement} item The item's element
101820              * @param {Number} index The item's index
101821              * @param {Ext.EventObject} e The raw event object
101822              */
101823             'beforeitemdblclick',
101824             /**
101825              * @event beforeitemcontextmenu
101826              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
101827              * @param {Ext.view.View} this
101828              * @param {Ext.data.Model} record The record that belongs to the item
101829              * @param {HTMLElement} item The item's element
101830              * @param {Number} index The item's index
101831              * @param {Ext.EventObject} e The raw event object
101832              */
101833             'beforeitemcontextmenu',
101834             /**
101835              * @event beforeitemkeydown
101836              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
101837              * @param {Ext.view.View} this
101838              * @param {Ext.data.Model} record The record that belongs to the item
101839              * @param {HTMLElement} item The item's element
101840              * @param {Number} index The item's index
101841              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101842              */
101843             'beforeitemkeydown',
101844             /**
101845              * @event itemmousedown
101846              * Fires when there is a mouse down on an item
101847              * @param {Ext.view.View} this
101848              * @param {Ext.data.Model} record The record that belongs to the item
101849              * @param {HTMLElement} item The item's element
101850              * @param {Number} index The item's index
101851              * @param {Ext.EventObject} e The raw event object
101852              */
101853             'itemmousedown',
101854             /**
101855              * @event itemmouseup
101856              * Fires when there is a mouse up on an item
101857              * @param {Ext.view.View} this
101858              * @param {Ext.data.Model} record The record that belongs to the item
101859              * @param {HTMLElement} item The item's element
101860              * @param {Number} index The item's index
101861              * @param {Ext.EventObject} e The raw event object
101862              */
101863             'itemmouseup',
101864             /**
101865              * @event itemmouseenter
101866              * Fires when the mouse enters an item.
101867              * @param {Ext.view.View} this
101868              * @param {Ext.data.Model} record The record that belongs to the item
101869              * @param {HTMLElement} item The item's element
101870              * @param {Number} index The item's index
101871              * @param {Ext.EventObject} e The raw event object
101872              */
101873             'itemmouseenter',
101874             /**
101875              * @event itemmouseleave
101876              * Fires when the mouse leaves an item.
101877              * @param {Ext.view.View} this
101878              * @param {Ext.data.Model} record The record that belongs to the item
101879              * @param {HTMLElement} item The item's element
101880              * @param {Number} index The item's index
101881              * @param {Ext.EventObject} e The raw event object
101882              */
101883             'itemmouseleave',
101884             /**
101885              * @event itemclick
101886              * Fires when an item is clicked.
101887              * @param {Ext.view.View} this
101888              * @param {Ext.data.Model} record The record that belongs to the item
101889              * @param {HTMLElement} item The item's element
101890              * @param {Number} index The item's index
101891              * @param {Ext.EventObject} e The raw event object
101892              */
101893             'itemclick',
101894             /**
101895              * @event itemdblclick
101896              * Fires when an item is double clicked.
101897              * @param {Ext.view.View} this
101898              * @param {Ext.data.Model} record The record that belongs to the item
101899              * @param {HTMLElement} item The item's element
101900              * @param {Number} index The item's index
101901              * @param {Ext.EventObject} e The raw event object
101902              */
101903             'itemdblclick',
101904             /**
101905              * @event itemcontextmenu
101906              * Fires when an item is right clicked.
101907              * @param {Ext.view.View} this
101908              * @param {Ext.data.Model} record The record that belongs to the item
101909              * @param {HTMLElement} item The item's element
101910              * @param {Number} index The item's index
101911              * @param {Ext.EventObject} e The raw event object
101912              */
101913             'itemcontextmenu',
101914             /**
101915              * @event itemkeydown
101916              * Fires when a key is pressed while an item is currently selected.
101917              * @param {Ext.view.View} this
101918              * @param {Ext.data.Model} record The record that belongs to the item
101919              * @param {HTMLElement} item The item's element
101920              * @param {Number} index The item's index
101921              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101922              */
101923             'itemkeydown',
101924             /**
101925              * @event beforecontainermousedown
101926              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
101927              * @param {Ext.view.View} this
101928              * @param {Ext.EventObject} e The raw event object
101929              */
101930             'beforecontainermousedown',
101931             /**
101932              * @event beforecontainermouseup
101933              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
101934              * @param {Ext.view.View} this
101935              * @param {Ext.EventObject} e The raw event object
101936              */
101937             'beforecontainermouseup',
101938             /**
101939              * @event beforecontainermouseover
101940              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
101941              * @param {Ext.view.View} this
101942              * @param {Ext.EventObject} e The raw event object
101943              */
101944             'beforecontainermouseover',
101945             /**
101946              * @event beforecontainermouseout
101947              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
101948              * @param {Ext.view.View} this
101949              * @param {Ext.EventObject} e The raw event object
101950              */
101951             'beforecontainermouseout',
101952             /**
101953              * @event beforecontainerclick
101954              * Fires before the click event on the container is processed. Returns false to cancel the default action.
101955              * @param {Ext.view.View} this
101956              * @param {Ext.EventObject} e The raw event object
101957              */
101958             'beforecontainerclick',
101959             /**
101960              * @event beforecontainerdblclick
101961              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
101962              * @param {Ext.view.View} this
101963              * @param {Ext.EventObject} e The raw event object
101964              */
101965             'beforecontainerdblclick',
101966             /**
101967              * @event beforecontainercontextmenu
101968              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
101969              * @param {Ext.view.View} this
101970              * @param {Ext.EventObject} e The raw event object
101971              */
101972             'beforecontainercontextmenu',
101973             /**
101974              * @event beforecontainerkeydown
101975              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
101976              * @param {Ext.view.View} this
101977              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101978              */
101979             'beforecontainerkeydown',
101980             /**
101981              * @event containermouseup
101982              * Fires when there is a mouse up on the container
101983              * @param {Ext.view.View} this
101984              * @param {Ext.EventObject} e The raw event object
101985              */
101986             'containermouseup',
101987             /**
101988              * @event containermouseover
101989              * Fires when you move the mouse over the container.
101990              * @param {Ext.view.View} this
101991              * @param {Ext.EventObject} e The raw event object
101992              */
101993             'containermouseover',
101994             /**
101995              * @event containermouseout
101996              * Fires when you move the mouse out of the container.
101997              * @param {Ext.view.View} this
101998              * @param {Ext.EventObject} e The raw event object
101999              */
102000             'containermouseout',
102001             /**
102002              * @event containerclick
102003              * Fires when the container is clicked.
102004              * @param {Ext.view.View} this
102005              * @param {Ext.EventObject} e The raw event object
102006              */
102007             'containerclick',
102008             /**
102009              * @event containerdblclick
102010              * Fires when the container is double clicked.
102011              * @param {Ext.view.View} this
102012              * @param {Ext.EventObject} e The raw event object
102013              */
102014             'containerdblclick',
102015             /**
102016              * @event containercontextmenu
102017              * Fires when the container is right clicked.
102018              * @param {Ext.view.View} this
102019              * @param {Ext.EventObject} e The raw event object
102020              */
102021             'containercontextmenu',
102022             /**
102023              * @event containerkeydown
102024              * Fires when a key is pressed while the container is focused, and no item is currently selected.
102025              * @param {Ext.view.View} this
102026              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102027              */
102028             'containerkeydown',
102029
102030             /**
102031              * @event selectionchange
102032              * Fires when the selected nodes change. Relayed event from the underlying selection model.
102033              * @param {Ext.view.View} this
102034              * @param {HTMLElement[]} selections Array of the selected nodes
102035              */
102036             'selectionchange',
102037             /**
102038              * @event beforeselect
102039              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
102040              * @param {Ext.view.View} this
102041              * @param {HTMLElement} node The node to be selected
102042              * @param {HTMLElement[]} selections Array of currently selected nodes
102043              */
102044             'beforeselect'
102045         );
102046     },
102047     // private
102048     afterRender: function(){
102049         var me = this,
102050             listeners;
102051
102052         me.callParent();
102053
102054         listeners = {
102055             scope: me,
102056             /*
102057              * We need to make copies of this since some of the events fired here will end up triggering
102058              * a new event to be called and the shared event object will be mutated. In future we should
102059              * investigate if there are any issues with creating a new event object for each event that
102060              * is fired.
102061              */
102062             freezeEvent: true,
102063             click: me.handleEvent,
102064             mousedown: me.handleEvent,
102065             mouseup: me.handleEvent,
102066             dblclick: me.handleEvent,
102067             contextmenu: me.handleEvent,
102068             mouseover: me.handleEvent,
102069             mouseout: me.handleEvent,
102070             keydown: me.handleEvent
102071         };
102072
102073         me.mon(me.getTargetEl(), listeners);
102074
102075         if (me.store) {
102076             me.bindStore(me.store, true);
102077         }
102078     },
102079
102080     handleEvent: function(e) {
102081         if (this.processUIEvent(e) !== false) {
102082             this.processSpecialEvent(e);
102083         }
102084     },
102085
102086     // Private template method
102087     processItemEvent: Ext.emptyFn,
102088     processContainerEvent: Ext.emptyFn,
102089     processSpecialEvent: Ext.emptyFn,
102090
102091     /*
102092      * Returns true if this mouseover/out event is still over the overItem.
102093      */
102094     stillOverItem: function (event, overItem) {
102095         var nowOver;
102096
102097         // There is this weird bug when you hover over the border of a cell it is saying
102098         // the target is the table.
102099         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
102100         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
102101         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
102102         // this hard exception.
102103         if (overItem && typeof(overItem.offsetParent) === "object") {
102104             // mouseout : relatedTarget == nowOver, target == wasOver
102105             // mouseover: relatedTarget == wasOver, target == nowOver
102106             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
102107             return Ext.fly(overItem).contains(nowOver);
102108         }
102109
102110         return false;
102111     },
102112
102113     processUIEvent: function(e) {
102114         var me = this,
102115             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
102116             map = this.statics().EventMap,
102117             index, record,
102118             type = e.type,
102119             overItem = me.mouseOverItem,
102120             newType;
102121
102122         if (!item) {
102123             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
102124                 item = overItem;
102125             }
102126
102127             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
102128             if (type == 'keydown') {
102129                 record = me.getSelectionModel().getLastSelected();
102130                 if (record) {
102131                     item = me.getNode(record);
102132                 }
102133             }
102134         }
102135
102136         if (item) {
102137             index = me.indexOf(item);
102138             if (!record) {
102139                 record = me.getRecord(item);
102140             }
102141
102142             if (me.processItemEvent(record, item, index, e) === false) {
102143                 return false;
102144             }
102145
102146             newType = me.isNewItemEvent(item, e);
102147             if (newType === false) {
102148                 return false;
102149             }
102150
102151             if (
102152                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
102153                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
102154                 (me['onItem' + map[newType]](record, item, index, e) === false)
102155             ) {
102156                 return false;
102157             }
102158
102159             me.fireEvent('item' + newType, me, record, item, index, e);
102160         }
102161         else {
102162             if (
102163                 (me.processContainerEvent(e) === false) ||
102164                 (me['onBeforeContainer' + map[type]](e) === false) ||
102165                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
102166                 (me['onContainer' + map[type]](e) === false)
102167             ) {
102168                 return false;
102169             }
102170
102171             me.fireEvent('container' + type, me, e);
102172         }
102173
102174         return true;
102175     },
102176
102177     isNewItemEvent: function (item, e) {
102178         var me = this,
102179             overItem = me.mouseOverItem,
102180             type = e.type;
102181
102182         switch (type) {
102183             case 'mouseover':
102184                 if (item === overItem) {
102185                     return false;
102186                 }
102187                 me.mouseOverItem = item;
102188                 return 'mouseenter';
102189
102190             case 'mouseout':
102191                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
102192                 if (me.stillOverItem(e, overItem)) {
102193                     return false;
102194                 }
102195                 me.mouseOverItem = null;
102196                 return 'mouseleave';
102197         }
102198         return type;
102199     },
102200
102201     // private
102202     onItemMouseEnter: function(record, item, index, e) {
102203         if (this.trackOver) {
102204             this.highlightItem(item);
102205         }
102206     },
102207
102208     // private
102209     onItemMouseLeave : function(record, item, index, e) {
102210         if (this.trackOver) {
102211             this.clearHighlight();
102212         }
102213     },
102214
102215     // @private, template methods
102216     onItemMouseDown: Ext.emptyFn,
102217     onItemMouseUp: Ext.emptyFn,
102218     onItemFocus: Ext.emptyFn,
102219     onItemClick: Ext.emptyFn,
102220     onItemDblClick: Ext.emptyFn,
102221     onItemContextMenu: Ext.emptyFn,
102222     onItemKeyDown: Ext.emptyFn,
102223     onBeforeItemMouseDown: Ext.emptyFn,
102224     onBeforeItemMouseUp: Ext.emptyFn,
102225     onBeforeItemFocus: Ext.emptyFn,
102226     onBeforeItemMouseEnter: Ext.emptyFn,
102227     onBeforeItemMouseLeave: Ext.emptyFn,
102228     onBeforeItemClick: Ext.emptyFn,
102229     onBeforeItemDblClick: Ext.emptyFn,
102230     onBeforeItemContextMenu: Ext.emptyFn,
102231     onBeforeItemKeyDown: Ext.emptyFn,
102232
102233     // @private, template methods
102234     onContainerMouseDown: Ext.emptyFn,
102235     onContainerMouseUp: Ext.emptyFn,
102236     onContainerMouseOver: Ext.emptyFn,
102237     onContainerMouseOut: Ext.emptyFn,
102238     onContainerClick: Ext.emptyFn,
102239     onContainerDblClick: Ext.emptyFn,
102240     onContainerContextMenu: Ext.emptyFn,
102241     onContainerKeyDown: Ext.emptyFn,
102242     onBeforeContainerMouseDown: Ext.emptyFn,
102243     onBeforeContainerMouseUp: Ext.emptyFn,
102244     onBeforeContainerMouseOver: Ext.emptyFn,
102245     onBeforeContainerMouseOut: Ext.emptyFn,
102246     onBeforeContainerClick: Ext.emptyFn,
102247     onBeforeContainerDblClick: Ext.emptyFn,
102248     onBeforeContainerContextMenu: Ext.emptyFn,
102249     onBeforeContainerKeyDown: Ext.emptyFn,
102250
102251     /**
102252      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
102253      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
102254      * handle stepping through the list via keyboard navigation.
102255      * @param {HTMLElement} item The item to highlight
102256      */
102257     highlightItem: function(item) {
102258         var me = this;
102259         me.clearHighlight();
102260         me.highlightedItem = item;
102261         Ext.fly(item).addCls(me.overItemCls);
102262     },
102263
102264     /**
102265      * Un-highlights the currently highlighted item, if any.
102266      */
102267     clearHighlight: function() {
102268         var me = this,
102269             highlighted = me.highlightedItem;
102270
102271         if (highlighted) {
102272             Ext.fly(highlighted).removeCls(me.overItemCls);
102273             delete me.highlightedItem;
102274         }
102275     },
102276
102277     refresh: function() {
102278         var me = this;
102279         me.clearHighlight();
102280         me.callParent(arguments);
102281         if (!me.isFixedHeight()) {
102282             me.doComponentLayout();
102283         }
102284     }
102285 });
102286 /**
102287  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
102288  * @class Ext.layout.component.BoundList
102289  * @extends Ext.layout.component.Component
102290  * @private
102291  */
102292 Ext.define('Ext.layout.component.BoundList', {
102293     extend: 'Ext.layout.component.Component',
102294     alias: 'layout.boundlist',
102295
102296     type: 'component',
102297
102298     beforeLayout: function() {
102299         return this.callParent(arguments) || this.owner.refreshed > 0;
102300     },
102301
102302     onLayout : function(width, height) {
102303         var me = this,
102304             owner = me.owner,
102305             floating = owner.floating,
102306             el = owner.el,
102307             xy = el.getXY(),
102308             isNumber = Ext.isNumber,
102309             minWidth, maxWidth, minHeight, maxHeight,
102310             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
102311
102312         if (floating) {
102313             // Position offscreen so the natural width is not affected by the viewport's right edge
102314             el.setXY([-9999,-9999]);
102315         }
102316
102317         // Calculate initial layout
102318         me.setTargetSize(width, height);
102319
102320         // Handle min/maxWidth for auto-width
102321         if (!isNumber(width)) {
102322             minWidth = owner.minWidth;
102323             maxWidth = owner.maxWidth;
102324             if (isNumber(minWidth) || isNumber(maxWidth)) {
102325                 naturalWidth = el.getWidth();
102326                 if (naturalWidth < minWidth) {
102327                     constrainedWidth = minWidth;
102328                 }
102329                 else if (naturalWidth > maxWidth) {
102330                     constrainedWidth = maxWidth;
102331                 }
102332                 if (constrainedWidth) {
102333                     me.setTargetSize(constrainedWidth);
102334                 }
102335             }
102336         }
102337         // Handle min/maxHeight for auto-height
102338         if (!isNumber(height)) {
102339             minHeight = owner.minHeight;
102340             maxHeight = owner.maxHeight;
102341             if (isNumber(minHeight) || isNumber(maxHeight)) {
102342                 naturalHeight = el.getHeight();
102343                 if (naturalHeight < minHeight) {
102344                     constrainedHeight = minHeight;
102345                 }
102346                 else if (naturalHeight > maxHeight) {
102347                     constrainedHeight = maxHeight;
102348                 }
102349                 if (constrainedHeight) {
102350                     me.setTargetSize(undef, constrainedHeight);
102351                 }
102352             }
102353         }
102354
102355         if (floating) {
102356             // Restore position
102357             el.setXY(xy);
102358         }
102359     },
102360
102361     afterLayout: function() {
102362         var me = this,
102363             toolbar = me.owner.pagingToolbar;
102364         me.callParent();
102365         if (toolbar) {
102366             toolbar.doComponentLayout();
102367         }
102368     },
102369
102370     setTargetSize : function(width, height) {
102371         var me = this,
102372             owner = me.owner,
102373             listHeight = null,
102374             toolbar;
102375
102376         // Size the listEl
102377         if (Ext.isNumber(height)) {
102378             listHeight = height - owner.el.getFrameWidth('tb');
102379             toolbar = owner.pagingToolbar;
102380             if (toolbar) {
102381                 listHeight -= toolbar.getHeight();
102382             }
102383         }
102384         me.setElementSize(owner.listEl, null, listHeight);
102385
102386         me.callParent(arguments);
102387     }
102388
102389 });
102390
102391 /**
102392  * A simple class that renders text directly into a toolbar.
102393  *
102394  *     @example
102395  *     Ext.create('Ext.panel.Panel', {
102396  *         title: 'Panel with TextItem',
102397  *         width: 300,
102398  *         height: 200,
102399  *         tbar: [
102400  *             { xtype: 'tbtext', text: 'Sample Text Item' }
102401  *         ],
102402  *         renderTo: Ext.getBody()
102403  *     });
102404  *
102405  * @constructor
102406  * Creates a new TextItem
102407  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
102408  */
102409 Ext.define('Ext.toolbar.TextItem', {
102410     extend: 'Ext.toolbar.Item',
102411     requires: ['Ext.XTemplate'],
102412     alias: 'widget.tbtext',
102413     alternateClassName: 'Ext.Toolbar.TextItem',
102414
102415     /**
102416      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
102417      */
102418     text: '',
102419
102420     renderTpl: '{text}',
102421     //
102422     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
102423
102424     onRender : function() {
102425         Ext.apply(this.renderData, {
102426             text: this.text
102427         });
102428         this.callParent(arguments);
102429     },
102430
102431     /**
102432      * Updates this item's text, setting the text to be used as innerHTML.
102433      * @param {String} t The text to display (html accepted).
102434      */
102435     setText : function(t) {
102436         if (this.rendered) {
102437             this.el.update(t);
102438             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
102439         } else {
102440             this.text = t;
102441         }
102442     }
102443 });
102444 /**
102445  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
102446  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
102447  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
102448  * can provide a custom implementation.
102449  *
102450  * For example:
102451  *
102452  *     @example
102453  *     Ext.define('Ext.ux.CustomTrigger', {
102454  *         extend: 'Ext.form.field.Trigger',
102455  *         alias: 'widget.customtrigger',
102456  *
102457  *         // override onTriggerClick
102458  *         onTriggerClick: function() {
102459  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
102460  *         }
102461  *     });
102462  *
102463  *     Ext.create('Ext.form.FormPanel', {
102464  *         title: 'Form with TriggerField',
102465  *         bodyPadding: 5,
102466  *         width: 350,
102467  *         renderTo: Ext.getBody(),
102468  *         items:[{
102469  *             xtype: 'customtrigger',
102470  *             fieldLabel: 'Sample Trigger',
102471  *             emptyText: 'click the trigger',
102472  *         }]
102473  *     });
102474  *
102475  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
102476  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
102477  */
102478 Ext.define('Ext.form.field.Trigger', {
102479     extend:'Ext.form.field.Text',
102480     alias: ['widget.triggerfield', 'widget.trigger'],
102481     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
102482     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
102483
102484     // note: {id} here is really {inputId}, but {cmpId} is available
102485     fieldSubTpl: [
102486         '<input id="{id}" type="{type}" ',
102487             '<tpl if="name">name="{name}" </tpl>',
102488             '<tpl if="size">size="{size}" </tpl>',
102489             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
102490             'class="{fieldCls} {typeCls}" autocomplete="off" />',
102491         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
102492             '{triggerEl}',
102493             '<div class="{clearCls}" role="presentation"></div>',
102494         '</div>',
102495         {
102496             compiled: true,
102497             disableFormats: true
102498         }
102499     ],
102500
102501     /**
102502      * @cfg {String} triggerCls
102503      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
102504      * by default and triggerCls will be **appended** if specified.
102505      */
102506
102507     /**
102508      * @cfg {String} [triggerBaseCls='x-form-trigger']
102509      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
102510      * addition to this class.
102511      */
102512     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
102513
102514     /**
102515      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
102516      * The CSS class that is added to the div wrapping the trigger button(s).
102517      */
102518     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
102519
102520     /**
102521      * @cfg {Boolean} hideTrigger
102522      * true to hide the trigger element and display only the base text field
102523      */
102524     hideTrigger: false,
102525
102526     /**
102527      * @cfg {Boolean} editable
102528      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
102529      * action invoked by the trigger.
102530      */
102531     editable: true,
102532
102533     /**
102534      * @cfg {Boolean} readOnly
102535      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
102536      * options if the value is true.
102537      */
102538     readOnly: false,
102539
102540     /**
102541      * @cfg {Boolean} [selectOnFocus=false]
102542      * true to select any existing text in the field immediately on focus. Only applies when
102543      * {@link #editable editable} = true
102544      */
102545
102546     /**
102547      * @cfg {Boolean} repeatTriggerClick
102548      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
102549      */
102550     repeatTriggerClick: false,
102551
102552
102553     /**
102554      * @hide
102555      * @method autoSize
102556      */
102557     autoSize: Ext.emptyFn,
102558     // private
102559     monitorTab: true,
102560     // private
102561     mimicing: false,
102562     // private
102563     triggerIndexRe: /trigger-index-(\d+)/,
102564
102565     componentLayout: 'triggerfield',
102566
102567     initComponent: function() {
102568         this.wrapFocusCls = this.triggerWrapCls + '-focus';
102569         this.callParent(arguments);
102570     },
102571
102572     // private
102573     onRender: function(ct, position) {
102574         var me = this,
102575             triggerCls,
102576             triggerBaseCls = me.triggerBaseCls,
102577             triggerWrapCls = me.triggerWrapCls,
102578             triggerConfigs = [],
102579             i;
102580
102581         // triggerCls is a synonym for trigger1Cls, so copy it.
102582         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
102583         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
102584         // trigger config objects that hold cls, handler, etc.
102585         if (!me.trigger1Cls) {
102586             me.trigger1Cls = me.triggerCls;
102587         }
102588
102589         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
102590         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
102591             triggerConfigs.push({
102592                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
102593                 role: 'button'
102594             });
102595         }
102596         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
102597
102598         /**
102599          * @property {Ext.Element} triggerWrap
102600          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
102601          */
102602         me.addChildEls('triggerWrap');
102603
102604         Ext.applyIf(me.subTplData, {
102605             triggerWrapCls: triggerWrapCls,
102606             triggerEl: Ext.DomHelper.markup(triggerConfigs),
102607             clearCls: me.clearCls
102608         });
102609
102610         me.callParent(arguments);
102611
102612         /**
102613          * @property {Ext.CompositeElement} triggerEl
102614          * A composite of all the trigger button elements. Only set after the field has been rendered.
102615          */
102616         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
102617
102618         me.doc = Ext.getDoc();
102619         me.initTrigger();
102620     },
102621
102622     onEnable: function() {
102623         this.callParent();
102624         this.triggerWrap.unmask();
102625     },
102626     
102627     onDisable: function() {
102628         this.callParent();
102629         this.triggerWrap.mask();
102630     },
102631     
102632     afterRender: function() {
102633         this.callParent();
102634         this.updateEditState();
102635         this.triggerEl.unselectable();
102636     },
102637
102638     updateEditState: function() {
102639         var me = this,
102640             inputEl = me.inputEl,
102641             triggerWrap = me.triggerWrap,
102642             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
102643             displayed,
102644             readOnly;
102645
102646         if (me.rendered) {
102647             if (me.readOnly) {
102648                 inputEl.addCls(noeditCls);
102649                 readOnly = true;
102650                 displayed = false;
102651             } else {
102652                 if (me.editable) {
102653                     inputEl.removeCls(noeditCls);
102654                     readOnly = false;
102655                 } else {
102656                     inputEl.addCls(noeditCls);
102657                     readOnly = true;
102658                 }
102659                 displayed = !me.hideTrigger;
102660             }
102661
102662             triggerWrap.setDisplayed(displayed);
102663             inputEl.dom.readOnly = readOnly;
102664             me.doComponentLayout();
102665         }
102666     },
102667
102668     /**
102669      * Get the total width of the trigger button area. Only useful after the field has been rendered.
102670      * @return {Number} The trigger width
102671      */
102672     getTriggerWidth: function() {
102673         var me = this,
102674             triggerWrap = me.triggerWrap,
102675             totalTriggerWidth = 0;
102676         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
102677             me.triggerEl.each(function(trigger) {
102678                 totalTriggerWidth += trigger.getWidth();
102679             });
102680             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
102681         }
102682         return totalTriggerWidth;
102683     },
102684
102685     setHideTrigger: function(hideTrigger) {
102686         if (hideTrigger != this.hideTrigger) {
102687             this.hideTrigger = hideTrigger;
102688             this.updateEditState();
102689         }
102690     },
102691
102692     /**
102693      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
102694      * option at config time.
102695      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
102696      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
102697      * will call the trigger. 
102698      */
102699     setEditable: function(editable) {
102700         if (editable != this.editable) {
102701             this.editable = editable;
102702             this.updateEditState();
102703         }
102704     },
102705
102706     /**
102707      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
102708      * option at config time.
102709      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
102710      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
102711      * and hideTrigger.
102712      */
102713     setReadOnly: function(readOnly) {
102714         if (readOnly != this.readOnly) {
102715             this.readOnly = readOnly;
102716             this.updateEditState();
102717         }
102718     },
102719
102720     // private
102721     initTrigger: function() {
102722         var me = this,
102723             triggerWrap = me.triggerWrap,
102724             triggerEl = me.triggerEl;
102725
102726         if (me.repeatTriggerClick) {
102727             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
102728                 preventDefault: true,
102729                 handler: function(cr, e) {
102730                     me.onTriggerWrapClick(e);
102731                 }
102732             });
102733         } else {
102734             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
102735         }
102736
102737         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
102738         triggerEl.each(function(el, c, i) {
102739             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
102740         });
102741         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
102742         triggerEl.each(function(el, c, i) {
102743             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
102744         });
102745     },
102746
102747     // private
102748     onDestroy: function() {
102749         var me = this;
102750         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
102751         delete me.doc;
102752         me.callParent();
102753     },
102754
102755     // private
102756     onFocus: function() {
102757         var me = this;
102758         me.callParent();
102759         if (!me.mimicing) {
102760             me.bodyEl.addCls(me.wrapFocusCls);
102761             me.mimicing = true;
102762             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
102763                 delay: 10
102764             });
102765             if (me.monitorTab) {
102766                 me.on('specialkey', me.checkTab, me);
102767             }
102768         }
102769     },
102770
102771     // private
102772     checkTab: function(me, e) {
102773         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
102774             this.triggerBlur();
102775         }
102776     },
102777
102778     // private
102779     onBlur: Ext.emptyFn,
102780
102781     // private
102782     mimicBlur: function(e) {
102783         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
102784             this.triggerBlur();
102785         }
102786     },
102787
102788     // private
102789     triggerBlur: function() {
102790         var me = this;
102791         me.mimicing = false;
102792         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
102793         if (me.monitorTab && me.inputEl) {
102794             me.un('specialkey', me.checkTab, me);
102795         }
102796         Ext.form.field.Trigger.superclass.onBlur.call(me);
102797         if (me.bodyEl) {
102798             me.bodyEl.removeCls(me.wrapFocusCls);
102799         }
102800     },
102801
102802     beforeBlur: Ext.emptyFn,
102803
102804     // private
102805     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
102806     validateBlur: function(e) {
102807         return true;
102808     },
102809
102810     // private
102811     // process clicks upon triggers.
102812     // determine which trigger index, and dispatch to the appropriate click handler
102813     onTriggerWrapClick: function(e) {
102814         var me = this,
102815             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
102816             match = t && t.className.match(me.triggerIndexRe),
102817             idx,
102818             triggerClickMethod;
102819
102820         if (match && !me.readOnly) {
102821             idx = parseInt(match[1], 10);
102822             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
102823             if (triggerClickMethod) {
102824                 triggerClickMethod.call(me, e);
102825             }
102826         }
102827     },
102828
102829     /**
102830      * @method onTriggerClick
102831      * @protected
102832      * The function that should handle the trigger's click event. This method does nothing by default until overridden
102833      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
102834      * @param {Ext.EventObject} e
102835      */
102836     onTriggerClick: Ext.emptyFn
102837
102838     /**
102839      * @cfg {Boolean} grow @hide
102840      */
102841     /**
102842      * @cfg {Number} growMin @hide
102843      */
102844     /**
102845      * @cfg {Number} growMax @hide
102846      */
102847 });
102848
102849 /**
102850  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
102851  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
102852  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
102853  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
102854  * respectively.
102855  *
102856  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
102857  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
102858  * for the field.
102859  */
102860 Ext.define('Ext.form.field.Picker', {
102861     extend: 'Ext.form.field.Trigger',
102862     alias: 'widget.pickerfield',
102863     alternateClassName: 'Ext.form.Picker',
102864     requires: ['Ext.util.KeyNav'],
102865
102866     /**
102867      * @cfg {Boolean} matchFieldWidth
102868      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
102869      */
102870     matchFieldWidth: true,
102871
102872     /**
102873      * @cfg {String} pickerAlign
102874      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
102875      */
102876     pickerAlign: 'tl-bl?',
102877
102878     /**
102879      * @cfg {Number[]} pickerOffset
102880      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
102881      * Defaults to undefined.
102882      */
102883
102884     /**
102885      * @cfg {String} openCls
102886      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
102887      * Defaults to 'x-pickerfield-open'.
102888      */
102889     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
102890
102891     /**
102892      * @property {Boolean} isExpanded
102893      * True if the picker is currently expanded, false if not.
102894      */
102895
102896     /**
102897      * @cfg {Boolean} editable
102898      * False to prevent the user from typing text directly into the field; the field can only have its value set via
102899      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
102900      * field itself.
102901      */
102902     editable: true,
102903
102904
102905     initComponent: function() {
102906         this.callParent();
102907
102908         // Custom events
102909         this.addEvents(
102910             /**
102911              * @event expand
102912              * Fires when the field's picker is expanded.
102913              * @param {Ext.form.field.Picker} field This field instance
102914              */
102915             'expand',
102916             /**
102917              * @event collapse
102918              * Fires when the field's picker is collapsed.
102919              * @param {Ext.form.field.Picker} field This field instance
102920              */
102921             'collapse',
102922             /**
102923              * @event select
102924              * Fires when a value is selected via the picker.
102925              * @param {Ext.form.field.Picker} field This field instance
102926              * @param {Object} value The value that was selected. The exact type of this value is dependent on
102927              * the individual field and picker implementations.
102928              */
102929             'select'
102930         );
102931     },
102932
102933
102934     initEvents: function() {
102935         var me = this;
102936         me.callParent();
102937
102938         // Add handlers for keys to expand/collapse the picker
102939         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
102940             down: function() {
102941                 if (!me.isExpanded) {
102942                     // Don't call expand() directly as there may be additional processing involved before
102943                     // expanding, e.g. in the case of a ComboBox query.
102944                     me.onTriggerClick();
102945                 }
102946             },
102947             esc: me.collapse,
102948             scope: me,
102949             forceKeyDown: true
102950         });
102951
102952         // Non-editable allows opening the picker by clicking the field
102953         if (!me.editable) {
102954             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
102955         }
102956
102957         // Disable native browser autocomplete
102958         if (Ext.isGecko) {
102959             me.inputEl.dom.setAttribute('autocomplete', 'off');
102960         }
102961     },
102962
102963
102964     /**
102965      * Expands this field's picker dropdown.
102966      */
102967     expand: function() {
102968         var me = this,
102969             bodyEl, picker, collapseIf;
102970
102971         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
102972             bodyEl = me.bodyEl;
102973             picker = me.getPicker();
102974             collapseIf = me.collapseIf;
102975
102976             // show the picker and set isExpanded flag
102977             picker.show();
102978             me.isExpanded = true;
102979             me.alignPicker();
102980             bodyEl.addCls(me.openCls);
102981
102982             // monitor clicking and mousewheel
102983             me.mon(Ext.getDoc(), {
102984                 mousewheel: collapseIf,
102985                 mousedown: collapseIf,
102986                 scope: me
102987             });
102988             Ext.EventManager.onWindowResize(me.alignPicker, me);
102989             me.fireEvent('expand', me);
102990             me.onExpand();
102991         }
102992     },
102993
102994     onExpand: Ext.emptyFn,
102995
102996     /**
102997      * Aligns the picker to the input element
102998      * @protected
102999      */
103000     alignPicker: function() {
103001         var me = this,
103002             picker;
103003
103004         if (me.isExpanded) {
103005             picker = me.getPicker();
103006             if (me.matchFieldWidth) {
103007                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
103008                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
103009             }
103010             if (picker.isFloating()) {
103011                 me.doAlign();
103012             }
103013         }
103014     },
103015
103016     /**
103017      * Performs the alignment on the picker using the class defaults
103018      * @private
103019      */
103020     doAlign: function(){
103021         var me = this,
103022             picker = me.picker,
103023             aboveSfx = '-above',
103024             isAbove;
103025
103026         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
103027         // add the {openCls}-above class if the picker was aligned above
103028         // the field due to hitting the bottom of the viewport
103029         isAbove = picker.el.getY() < me.inputEl.getY();
103030         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
103031         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
103032     },
103033
103034     /**
103035      * Collapses this field's picker dropdown.
103036      */
103037     collapse: function() {
103038         if (this.isExpanded && !this.isDestroyed) {
103039             var me = this,
103040                 openCls = me.openCls,
103041                 picker = me.picker,
103042                 doc = Ext.getDoc(),
103043                 collapseIf = me.collapseIf,
103044                 aboveSfx = '-above';
103045
103046             // hide the picker and set isExpanded flag
103047             picker.hide();
103048             me.isExpanded = false;
103049
103050             // remove the openCls
103051             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
103052             picker.el.removeCls(picker.baseCls + aboveSfx);
103053
103054             // remove event listeners
103055             doc.un('mousewheel', collapseIf, me);
103056             doc.un('mousedown', collapseIf, me);
103057             Ext.EventManager.removeResizeListener(me.alignPicker, me);
103058             me.fireEvent('collapse', me);
103059             me.onCollapse();
103060         }
103061     },
103062
103063     onCollapse: Ext.emptyFn,
103064
103065
103066     /**
103067      * @private
103068      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
103069      */
103070     collapseIf: function(e) {
103071         var me = this;
103072         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
103073             me.collapse();
103074         }
103075     },
103076
103077     /**
103078      * Returns a reference to the picker component for this field, creating it if necessary by
103079      * calling {@link #createPicker}.
103080      * @return {Ext.Component} The picker component
103081      */
103082     getPicker: function() {
103083         var me = this;
103084         return me.picker || (me.picker = me.createPicker());
103085     },
103086
103087     /**
103088      * @method
103089      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
103090      * The current field should also be passed as a configuration option to the picker component as the pickerField
103091      * property.
103092      */
103093     createPicker: Ext.emptyFn,
103094
103095     /**
103096      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
103097      * @protected
103098      */
103099     onTriggerClick: function() {
103100         var me = this;
103101         if (!me.readOnly && !me.disabled) {
103102             if (me.isExpanded) {
103103                 me.collapse();
103104             } else {
103105                 me.expand();
103106             }
103107             me.inputEl.focus();
103108         }
103109     },
103110
103111     mimicBlur: function(e) {
103112         var me = this,
103113             picker = me.picker;
103114         // ignore mousedown events within the picker element
103115         if (!picker || !e.within(picker.el, false, true)) {
103116             me.callParent(arguments);
103117         }
103118     },
103119
103120     onDestroy : function(){
103121         var me = this,
103122             picker = me.picker;
103123
103124         Ext.EventManager.removeResizeListener(me.alignPicker, me);
103125         Ext.destroy(me.keyNav);
103126         if (picker) {
103127             delete picker.pickerField;
103128             picker.destroy();
103129         }
103130         me.callParent();
103131     }
103132
103133 });
103134
103135
103136 /**
103137  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
103138  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
103139  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
103140  * field which uses the spinner to increment and decrement the field's value by its
103141  * {@link Ext.form.field.Number#step step} config value.
103142  *
103143  * For example:
103144  *
103145  *     @example
103146  *     Ext.define('Ext.ux.CustomSpinner', {
103147  *         extend: 'Ext.form.field.Spinner',
103148  *         alias: 'widget.customspinner',
103149  *
103150  *         // override onSpinUp (using step isn't neccessary)
103151  *         onSpinUp: function() {
103152  *             var me = this;
103153  *             if (!me.readOnly) {
103154  *                 var val = me.step; // set the default value to the step value
103155  *                 if(me.getValue() !== '') {
103156  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103157  *                 }
103158  *                 me.setValue((val + me.step) + ' Pack');
103159  *             }
103160  *         },
103161  *
103162  *         // override onSpinDown
103163  *         onSpinDown: function() {
103164  *             var val, me = this;
103165  *             if (!me.readOnly) {
103166  *                 if(me.getValue() !== '') {
103167  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103168  *                 }
103169  *                 me.setValue((val - me.step) + ' Pack');
103170  *             }
103171  *         }
103172  *     });
103173  *
103174  *     Ext.create('Ext.form.FormPanel', {
103175  *         title: 'Form with SpinnerField',
103176  *         bodyPadding: 5,
103177  *         width: 350,
103178  *         renderTo: Ext.getBody(),
103179  *         items:[{
103180  *             xtype: 'customspinner',
103181  *             fieldLabel: 'How Much Beer?',
103182  *             step: 6
103183  *         }]
103184  *     });
103185  *
103186  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
103187  * to prevent this, set `{@link #keyNavEnabled} = false`.
103188  */
103189 Ext.define('Ext.form.field.Spinner', {
103190     extend: 'Ext.form.field.Trigger',
103191     alias: 'widget.spinnerfield',
103192     alternateClassName: 'Ext.form.Spinner',
103193     requires: ['Ext.util.KeyNav'],
103194
103195     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
103196     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
103197
103198     /**
103199      * @cfg {Boolean} spinUpEnabled
103200      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
103201      * created, use the {@link #setSpinUpEnabled} method.
103202      */
103203     spinUpEnabled: true,
103204
103205     /**
103206      * @cfg {Boolean} spinDownEnabled
103207      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
103208      * created, use the {@link #setSpinDownEnabled} method.
103209      */
103210     spinDownEnabled: true,
103211
103212     /**
103213      * @cfg {Boolean} keyNavEnabled
103214      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
103215      */
103216     keyNavEnabled: true,
103217
103218     /**
103219      * @cfg {Boolean} mouseWheelEnabled
103220      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
103221      * Defaults to true.
103222      */
103223     mouseWheelEnabled: true,
103224
103225     /**
103226      * @cfg {Boolean} repeatTriggerClick
103227      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
103228      * Defaults to true.
103229      */
103230     repeatTriggerClick: true,
103231
103232     /**
103233      * @method
103234      * @protected
103235      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
103236      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103237      */
103238     onSpinUp: Ext.emptyFn,
103239
103240     /**
103241      * @method
103242      * @protected
103243      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
103244      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103245      */
103246     onSpinDown: Ext.emptyFn,
103247
103248     initComponent: function() {
103249         this.callParent();
103250
103251         this.addEvents(
103252             /**
103253              * @event spin
103254              * Fires when the spinner is made to spin up or down.
103255              * @param {Ext.form.field.Spinner} this
103256              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
103257              */
103258             'spin',
103259
103260             /**
103261              * @event spinup
103262              * Fires when the spinner is made to spin up.
103263              * @param {Ext.form.field.Spinner} this
103264              */
103265             'spinup',
103266
103267             /**
103268              * @event spindown
103269              * Fires when the spinner is made to spin down.
103270              * @param {Ext.form.field.Spinner} this
103271              */
103272             'spindown'
103273         );
103274     },
103275
103276     /**
103277      * @private
103278      * Override.
103279      */
103280     onRender: function() {
103281         var me = this,
103282             triggers;
103283
103284         me.callParent(arguments);
103285         triggers = me.triggerEl;
103286
103287         /**
103288          * @property {Ext.Element} spinUpEl
103289          * The spinner up button element
103290          */
103291         me.spinUpEl = triggers.item(0);
103292         /**
103293          * @property {Ext.Element} spinDownEl
103294          * The spinner down button element
103295          */
103296         me.spinDownEl = triggers.item(1);
103297
103298         // Set initial enabled/disabled states
103299         me.setSpinUpEnabled(me.spinUpEnabled);
103300         me.setSpinDownEnabled(me.spinDownEnabled);
103301
103302         // Init up/down arrow keys
103303         if (me.keyNavEnabled) {
103304             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
103305                 scope: me,
103306                 up: me.spinUp,
103307                 down: me.spinDown
103308             });
103309         }
103310
103311         // Init mouse wheel
103312         if (me.mouseWheelEnabled) {
103313             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
103314         }
103315     },
103316
103317     /**
103318      * @private
103319      * Override. Since the triggers are stacked, only measure the width of one of them.
103320      */
103321     getTriggerWidth: function() {
103322         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
103323     },
103324
103325     /**
103326      * @private
103327      * Handles the spinner up button clicks.
103328      */
103329     onTrigger1Click: function() {
103330         this.spinUp();
103331     },
103332
103333     /**
103334      * @private
103335      * Handles the spinner down button clicks.
103336      */
103337     onTrigger2Click: function() {
103338         this.spinDown();
103339     },
103340
103341     /**
103342      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
103343      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
103344      * is false.
103345      */
103346     spinUp: function() {
103347         var me = this;
103348         if (me.spinUpEnabled && !me.disabled) {
103349             me.fireEvent('spin', me, 'up');
103350             me.fireEvent('spinup', me);
103351             me.onSpinUp();
103352         }
103353     },
103354
103355     /**
103356      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
103357      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
103358      * is false.
103359      */
103360     spinDown: function() {
103361         var me = this;
103362         if (me.spinDownEnabled && !me.disabled) {
103363             me.fireEvent('spin', me, 'down');
103364             me.fireEvent('spindown', me);
103365             me.onSpinDown();
103366         }
103367     },
103368
103369     /**
103370      * Sets whether the spinner up button is enabled.
103371      * @param {Boolean} enabled true to enable the button, false to disable it.
103372      */
103373     setSpinUpEnabled: function(enabled) {
103374         var me = this,
103375             wasEnabled = me.spinUpEnabled;
103376         me.spinUpEnabled = enabled;
103377         if (wasEnabled !== enabled && me.rendered) {
103378             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
103379         }
103380     },
103381
103382     /**
103383      * Sets whether the spinner down button is enabled.
103384      * @param {Boolean} enabled true to enable the button, false to disable it.
103385      */
103386     setSpinDownEnabled: function(enabled) {
103387         var me = this,
103388             wasEnabled = me.spinDownEnabled;
103389         me.spinDownEnabled = enabled;
103390         if (wasEnabled !== enabled && me.rendered) {
103391             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
103392         }
103393     },
103394
103395     /**
103396      * @private
103397      * Handles mousewheel events on the field
103398      */
103399     onMouseWheel: function(e) {
103400         var me = this,
103401             delta;
103402         if (me.hasFocus) {
103403             delta = e.getWheelDelta();
103404             if (delta > 0) {
103405                 me.spinUp();
103406             }
103407             else if (delta < 0) {
103408                 me.spinDown();
103409             }
103410             e.stopEvent();
103411         }
103412     },
103413
103414     onDestroy: function() {
103415         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
103416         this.callParent();
103417     }
103418
103419 });
103420 /**
103421  * @docauthor Jason Johnston <jason@sencha.com>
103422  *
103423  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
103424  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
103425  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
103426  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
103427  *
103428  * By default, the number field is also rendered with a set of up/down spinner buttons and has
103429  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
103430  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
103431  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
103432  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
103433  *
103434  * # Example usage
103435  *
103436  *     @example
103437  *     Ext.create('Ext.form.Panel', {
103438  *         title: 'On The Wall',
103439  *         width: 300,
103440  *         bodyPadding: 10,
103441  *         renderTo: Ext.getBody(),
103442  *         items: [{
103443  *             xtype: 'numberfield',
103444  *             anchor: '100%',
103445  *             name: 'bottles',
103446  *             fieldLabel: 'Bottles of Beer',
103447  *             value: 99,
103448  *             maxValue: 99,
103449  *             minValue: 0
103450  *         }],
103451  *         buttons: [{
103452  *             text: 'Take one down, pass it around',
103453  *             handler: function() {
103454  *                 this.up('form').down('[name=bottles]').spinDown();
103455  *             }
103456  *         }]
103457  *     });
103458  *
103459  * # Removing UI Enhancements
103460  *
103461  *     @example
103462  *     Ext.create('Ext.form.Panel', {
103463  *         title: 'Personal Info',
103464  *         width: 300,
103465  *         bodyPadding: 10,
103466  *         renderTo: Ext.getBody(),
103467  *         items: [{
103468  *             xtype: 'numberfield',
103469  *             anchor: '100%',
103470  *             name: 'age',
103471  *             fieldLabel: 'Age',
103472  *             minValue: 0, //prevents negative numbers
103473  *
103474  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
103475  *             hideTrigger: true,
103476  *             keyNavEnabled: false,
103477  *             mouseWheelEnabled: false
103478  *         }]
103479  *     });
103480  *
103481  * # Using Step
103482  *
103483  *     @example
103484  *     Ext.create('Ext.form.Panel', {
103485  *         renderTo: Ext.getBody(),
103486  *         title: 'Step',
103487  *         width: 300,
103488  *         bodyPadding: 10,
103489  *         items: [{
103490  *             xtype: 'numberfield',
103491  *             anchor: '100%',
103492  *             name: 'evens',
103493  *             fieldLabel: 'Even Numbers',
103494  *
103495  *             // Set step so it skips every other number
103496  *             step: 2,
103497  *             value: 0,
103498  *
103499  *             // Add change handler to force user-entered numbers to evens
103500  *             listeners: {
103501  *                 change: function(field, value) {
103502  *                     value = parseInt(value, 10);
103503  *                     field.setValue(value + value % 2);
103504  *                 }
103505  *             }
103506  *         }]
103507  *     });
103508  */
103509 Ext.define('Ext.form.field.Number', {
103510     extend:'Ext.form.field.Spinner',
103511     alias: 'widget.numberfield',
103512     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
103513
103514     /**
103515      * @cfg {RegExp} stripCharsRe @hide
103516      */
103517     /**
103518      * @cfg {RegExp} maskRe @hide
103519      */
103520
103521     /**
103522      * @cfg {Boolean} allowDecimals
103523      * False to disallow decimal values
103524      */
103525     allowDecimals : true,
103526
103527     /**
103528      * @cfg {String} decimalSeparator
103529      * Character(s) to allow as the decimal separator
103530      */
103531     decimalSeparator : '.',
103532
103533     /**
103534      * @cfg {Number} decimalPrecision
103535      * The maximum precision to display after the decimal separator
103536      */
103537     decimalPrecision : 2,
103538
103539     /**
103540      * @cfg {Number} minValue
103541      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
103542      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
103543      */
103544     minValue: Number.NEGATIVE_INFINITY,
103545
103546     /**
103547      * @cfg {Number} maxValue
103548      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
103549      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
103550      */
103551     maxValue: Number.MAX_VALUE,
103552
103553     /**
103554      * @cfg {Number} step
103555      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
103556      * the spinner.
103557      */
103558     step: 1,
103559
103560     /**
103561      * @cfg {String} minText
103562      * Error text to display if the minimum value validation fails.
103563      */
103564     minText : 'The minimum value for this field is {0}',
103565
103566     /**
103567      * @cfg {String} maxText
103568      * Error text to display if the maximum value validation fails.
103569      */
103570     maxText : 'The maximum value for this field is {0}',
103571
103572     /**
103573      * @cfg {String} nanText
103574      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
103575      * '.' or '-' is left in the field with no number.
103576      */
103577     nanText : '{0} is not a valid number',
103578
103579     /**
103580      * @cfg {String} negativeText
103581      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
103582      * {@link #minText} in that circumstance only.
103583      */
103584     negativeText : 'The value cannot be negative',
103585
103586     /**
103587      * @cfg {String} baseChars
103588      * The base set of characters to evaluate as valid numbers.
103589      */
103590     baseChars : '0123456789',
103591
103592     /**
103593      * @cfg {Boolean} autoStripChars
103594      * True to automatically strip not allowed characters from the field.
103595      */
103596     autoStripChars: false,
103597
103598     initComponent: function() {
103599         var me = this,
103600             allowed;
103601
103602         me.callParent();
103603
103604         me.setMinValue(me.minValue);
103605         me.setMaxValue(me.maxValue);
103606
103607         // Build regexes for masking and stripping based on the configured options
103608         if (me.disableKeyFilter !== true) {
103609             allowed = me.baseChars + '';
103610             if (me.allowDecimals) {
103611                 allowed += me.decimalSeparator;
103612             }
103613             if (me.minValue < 0) {
103614                 allowed += '-';
103615             }
103616             allowed = Ext.String.escapeRegex(allowed);
103617             me.maskRe = new RegExp('[' + allowed + ']');
103618             if (me.autoStripChars) {
103619                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
103620             }
103621         }
103622     },
103623
103624     /**
103625      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
103626      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
103627      * that the value is a number, and that it is within the configured min and max values.
103628      * @param {Object} [value] The value to get errors for (defaults to the current field value)
103629      * @return {String[]} All validation errors for this field
103630      */
103631     getErrors: function(value) {
103632         var me = this,
103633             errors = me.callParent(arguments),
103634             format = Ext.String.format,
103635             num;
103636
103637         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
103638
103639         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
103640              return errors;
103641         }
103642
103643         value = String(value).replace(me.decimalSeparator, '.');
103644
103645         if(isNaN(value)){
103646             errors.push(format(me.nanText, value));
103647         }
103648
103649         num = me.parseValue(value);
103650
103651         if (me.minValue === 0 && num < 0) {
103652             errors.push(this.negativeText);
103653         }
103654         else if (num < me.minValue) {
103655             errors.push(format(me.minText, me.minValue));
103656         }
103657
103658         if (num > me.maxValue) {
103659             errors.push(format(me.maxText, me.maxValue));
103660         }
103661
103662
103663         return errors;
103664     },
103665
103666     rawToValue: function(rawValue) {
103667         var value = this.fixPrecision(this.parseValue(rawValue));
103668         if (value === null) {
103669             value = rawValue || null;
103670         }
103671         return  value;
103672     },
103673
103674     valueToRaw: function(value) {
103675         var me = this,
103676             decimalSeparator = me.decimalSeparator;
103677         value = me.parseValue(value);
103678         value = me.fixPrecision(value);
103679         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
103680         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
103681         return value;
103682     },
103683
103684     onChange: function() {
103685         var me = this,
103686             value = me.getValue(),
103687             valueIsNull = value === null;
103688
103689         me.callParent(arguments);
103690
103691         // Update the spinner buttons
103692         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
103693         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
103694     },
103695
103696     /**
103697      * Replaces any existing {@link #minValue} with the new value.
103698      * @param {Number} value The minimum value
103699      */
103700     setMinValue : function(value) {
103701         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
103702     },
103703
103704     /**
103705      * Replaces any existing {@link #maxValue} with the new value.
103706      * @param {Number} value The maximum value
103707      */
103708     setMaxValue: function(value) {
103709         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
103710     },
103711
103712     // private
103713     parseValue : function(value) {
103714         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
103715         return isNaN(value) ? null : value;
103716     },
103717
103718     /**
103719      * @private
103720      */
103721     fixPrecision : function(value) {
103722         var me = this,
103723             nan = isNaN(value),
103724             precision = me.decimalPrecision;
103725
103726         if (nan || !value) {
103727             return nan ? '' : value;
103728         } else if (!me.allowDecimals || precision <= 0) {
103729             precision = 0;
103730         }
103731
103732         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
103733     },
103734
103735     beforeBlur : function() {
103736         var me = this,
103737             v = me.parseValue(me.getRawValue());
103738
103739         if (!Ext.isEmpty(v)) {
103740             me.setValue(v);
103741         }
103742     },
103743
103744     onSpinUp: function() {
103745         var me = this;
103746         if (!me.readOnly) {
103747             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
103748         }
103749     },
103750
103751     onSpinDown: function() {
103752         var me = this;
103753         if (!me.readOnly) {
103754             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
103755         }
103756     }
103757 });
103758
103759 /**
103760  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
103761  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
103762  * available screen area, vertical scrollbars will be added.
103763  *
103764  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
103765  * which the server needs to interpret and then respond with the appropriate data.
103766  *
103767  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
103768  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
103769  * parameters used for paging criteria.
103770  *
103771  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
103772  *
103773  * Paging Toolbar is typically used as one of the Grid's toolbars:
103774  *
103775  *     @example
103776  *     var itemsPerPage = 2;   // set the number of items you want per page
103777  *
103778  *     var store = Ext.create('Ext.data.Store', {
103779  *         id:'simpsonsStore',
103780  *         autoLoad: false,
103781  *         fields:['name', 'email', 'phone'],
103782  *         pageSize: itemsPerPage, // items per page
103783  *         proxy: {
103784  *             type: 'ajax',
103785  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
103786  *             reader: {
103787  *                 type: 'json',
103788  *                 root: 'items',
103789  *                 totalProperty: 'total'
103790  *             }
103791  *         }
103792  *     });
103793  *
103794  *     // specify segment of data you want to load using params
103795  *     store.load({
103796  *         params:{
103797  *             start:0,
103798  *             limit: itemsPerPage
103799  *         }
103800  *     });
103801  *
103802  *     Ext.create('Ext.grid.Panel', {
103803  *         title: 'Simpsons',
103804  *         store: store,
103805  *         columns: [
103806  *             { header: 'Name',  dataIndex: 'name' },
103807  *             { header: 'Email', dataIndex: 'email', flex: 1 },
103808  *             { header: 'Phone', dataIndex: 'phone' }
103809  *         ],
103810  *         width: 400,
103811  *         height: 125,
103812  *         dockedItems: [{
103813  *             xtype: 'pagingtoolbar',
103814  *             store: store,   // same store GridPanel is using
103815  *             dock: 'bottom',
103816  *             displayInfo: true
103817  *         }],
103818  *         renderTo: Ext.getBody()
103819  *     });
103820  *
103821  * To use paging, pass the paging requirements to the server when the store is first loaded.
103822  *
103823  *     store.load({
103824  *         params: {
103825  *             // specify params for the first page load if using paging
103826  *             start: 0,
103827  *             limit: myPageSize,
103828  *             // other params
103829  *             foo:   'bar'
103830  *         }
103831  *     });
103832  *
103833  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
103834  *
103835  *     var myStore = Ext.create('Ext.data.Store', {
103836  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
103837  *         ...
103838  *     });
103839  *
103840  * The packet sent back from the server would have this form:
103841  *
103842  *     {
103843  *         "success": true,
103844  *         "results": 2000,
103845  *         "rows": [ // ***Note:** this must be an Array
103846  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
103847  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
103848  *             ...
103849  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
103850  *         ]
103851  *     }
103852  *
103853  * ## Paging with Local Data
103854  *
103855  * Paging can also be accomplished with local data using extensions:
103856  *
103857  *   - [Ext.ux.data.PagingStore][1]
103858  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
103859  *
103860  *    [1]: http://sencha.com/forum/showthread.php?t=71532
103861  */
103862 Ext.define('Ext.toolbar.Paging', {
103863     extend: 'Ext.toolbar.Toolbar',
103864     alias: 'widget.pagingtoolbar',
103865     alternateClassName: 'Ext.PagingToolbar',
103866     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
103867     /**
103868      * @cfg {Ext.data.Store} store (required)
103869      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
103870      */
103871
103872     /**
103873      * @cfg {Boolean} displayInfo
103874      * true to display the displayMsg
103875      */
103876     displayInfo: false,
103877
103878     /**
103879      * @cfg {Boolean} prependButtons
103880      * true to insert any configured items _before_ the paging buttons.
103881      */
103882     prependButtons: false,
103883
103884     /**
103885      * @cfg {String} displayMsg
103886      * The paging status message to display. Note that this string is
103887      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
103888      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
103889      */
103890     displayMsg : 'Displaying {0} - {1} of {2}',
103891
103892     /**
103893      * @cfg {String} emptyMsg
103894      * The message to display when no records are found.
103895      */
103896     emptyMsg : 'No data to display',
103897
103898     /**
103899      * @cfg {String} beforePageText
103900      * The text displayed before the input item.
103901      */
103902     beforePageText : 'Page',
103903
103904     /**
103905      * @cfg {String} afterPageText
103906      * Customizable piece of the default paging text. Note that this string is formatted using
103907      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
103908      * string if showing the total page count is desired.
103909      */
103910     afterPageText : 'of {0}',
103911
103912     /**
103913      * @cfg {String} firstText
103914      * The quicktip text displayed for the first page button.
103915      * **Note**: quick tips must be initialized for the quicktip to show.
103916      */
103917     firstText : 'First Page',
103918
103919     /**
103920      * @cfg {String} prevText
103921      * The quicktip text displayed for the previous page button.
103922      * **Note**: quick tips must be initialized for the quicktip to show.
103923      */
103924     prevText : 'Previous Page',
103925
103926     /**
103927      * @cfg {String} nextText
103928      * The quicktip text displayed for the next page button.
103929      * **Note**: quick tips must be initialized for the quicktip to show.
103930      */
103931     nextText : 'Next Page',
103932
103933     /**
103934      * @cfg {String} lastText
103935      * The quicktip text displayed for the last page button.
103936      * **Note**: quick tips must be initialized for the quicktip to show.
103937      */
103938     lastText : 'Last Page',
103939
103940     /**
103941      * @cfg {String} refreshText
103942      * The quicktip text displayed for the Refresh button.
103943      * **Note**: quick tips must be initialized for the quicktip to show.
103944      */
103945     refreshText : 'Refresh',
103946
103947     /**
103948      * @cfg {Number} inputItemWidth
103949      * The width in pixels of the input field used to display and change the current page number.
103950      */
103951     inputItemWidth : 30,
103952
103953     /**
103954      * Gets the standard paging items in the toolbar
103955      * @private
103956      */
103957     getPagingItems: function() {
103958         var me = this;
103959
103960         return [{
103961             itemId: 'first',
103962             tooltip: me.firstText,
103963             overflowText: me.firstText,
103964             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
103965             disabled: true,
103966             handler: me.moveFirst,
103967             scope: me
103968         },{
103969             itemId: 'prev',
103970             tooltip: me.prevText,
103971             overflowText: me.prevText,
103972             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
103973             disabled: true,
103974             handler: me.movePrevious,
103975             scope: me
103976         },
103977         '-',
103978         me.beforePageText,
103979         {
103980             xtype: 'numberfield',
103981             itemId: 'inputItem',
103982             name: 'inputItem',
103983             cls: Ext.baseCSSPrefix + 'tbar-page-number',
103984             allowDecimals: false,
103985             minValue: 1,
103986             hideTrigger: true,
103987             enableKeyEvents: true,
103988             selectOnFocus: true,
103989             submitValue: false,
103990             width: me.inputItemWidth,
103991             margins: '-1 2 3 2',
103992             listeners: {
103993                 scope: me,
103994                 keydown: me.onPagingKeyDown,
103995                 blur: me.onPagingBlur
103996             }
103997         },{
103998             xtype: 'tbtext',
103999             itemId: 'afterTextItem',
104000             text: Ext.String.format(me.afterPageText, 1)
104001         },
104002         '-',
104003         {
104004             itemId: 'next',
104005             tooltip: me.nextText,
104006             overflowText: me.nextText,
104007             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
104008             disabled: true,
104009             handler: me.moveNext,
104010             scope: me
104011         },{
104012             itemId: 'last',
104013             tooltip: me.lastText,
104014             overflowText: me.lastText,
104015             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
104016             disabled: true,
104017             handler: me.moveLast,
104018             scope: me
104019         },
104020         '-',
104021         {
104022             itemId: 'refresh',
104023             tooltip: me.refreshText,
104024             overflowText: me.refreshText,
104025             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
104026             handler: me.doRefresh,
104027             scope: me
104028         }];
104029     },
104030
104031     initComponent : function(){
104032         var me = this,
104033             pagingItems = me.getPagingItems(),
104034             userItems   = me.items || me.buttons || [];
104035
104036         if (me.prependButtons) {
104037             me.items = userItems.concat(pagingItems);
104038         } else {
104039             me.items = pagingItems.concat(userItems);
104040         }
104041         delete me.buttons;
104042
104043         if (me.displayInfo) {
104044             me.items.push('->');
104045             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
104046         }
104047
104048         me.callParent();
104049
104050         me.addEvents(
104051             /**
104052              * @event change
104053              * Fires after the active page has been changed.
104054              * @param {Ext.toolbar.Paging} this
104055              * @param {Object} pageData An object that has these properties:
104056              *
104057              * - `total` : Number
104058              *
104059              *   The total number of records in the dataset as returned by the server
104060              *
104061              * - `currentPage` : Number
104062              *
104063              *   The current page number
104064              *
104065              * - `pageCount` : Number
104066              *
104067              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
104068              *   server and the current {@link Ext.data.Store#pageSize pageSize})
104069              *
104070              * - `toRecord` : Number
104071              *
104072              *   The starting record index for the current page
104073              *
104074              * - `fromRecord` : Number
104075              *
104076              *   The ending record index for the current page
104077              */
104078             'change',
104079
104080             /**
104081              * @event beforechange
104082              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
104083              * @param {Ext.toolbar.Paging} this
104084              * @param {Number} page The page number that will be loaded on change
104085              */
104086             'beforechange'
104087         );
104088         me.on('afterlayout', me.onLoad, me, {single: true});
104089
104090         me.bindStore(me.store || 'ext-empty-store', true);
104091     },
104092     // private
104093     updateInfo : function(){
104094         var me = this,
104095             displayItem = me.child('#displayItem'),
104096             store = me.store,
104097             pageData = me.getPageData(),
104098             count, msg;
104099
104100         if (displayItem) {
104101             count = store.getCount();
104102             if (count === 0) {
104103                 msg = me.emptyMsg;
104104             } else {
104105                 msg = Ext.String.format(
104106                     me.displayMsg,
104107                     pageData.fromRecord,
104108                     pageData.toRecord,
104109                     pageData.total
104110                 );
104111             }
104112             displayItem.setText(msg);
104113             me.doComponentLayout();
104114         }
104115     },
104116
104117     // private
104118     onLoad : function(){
104119         var me = this,
104120             pageData,
104121             currPage,
104122             pageCount,
104123             afterText;
104124
104125         if (!me.rendered) {
104126             return;
104127         }
104128
104129         pageData = me.getPageData();
104130         currPage = pageData.currentPage;
104131         pageCount = pageData.pageCount;
104132         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
104133
104134         me.child('#afterTextItem').setText(afterText);
104135         me.child('#inputItem').setValue(currPage);
104136         me.child('#first').setDisabled(currPage === 1);
104137         me.child('#prev').setDisabled(currPage === 1);
104138         me.child('#next').setDisabled(currPage === pageCount);
104139         me.child('#last').setDisabled(currPage === pageCount);
104140         me.child('#refresh').enable();
104141         me.updateInfo();
104142         me.fireEvent('change', me, pageData);
104143     },
104144
104145     // private
104146     getPageData : function(){
104147         var store = this.store,
104148             totalCount = store.getTotalCount();
104149
104150         return {
104151             total : totalCount,
104152             currentPage : store.currentPage,
104153             pageCount: Math.ceil(totalCount / store.pageSize),
104154             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104155             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
104156
104157         };
104158     },
104159
104160     // private
104161     onLoadError : function(){
104162         if (!this.rendered) {
104163             return;
104164         }
104165         this.child('#refresh').enable();
104166     },
104167
104168     // private
104169     readPageFromInput : function(pageData){
104170         var v = this.child('#inputItem').getValue(),
104171             pageNum = parseInt(v, 10);
104172
104173         if (!v || isNaN(pageNum)) {
104174             this.child('#inputItem').setValue(pageData.currentPage);
104175             return false;
104176         }
104177         return pageNum;
104178     },
104179
104180     onPagingFocus : function(){
104181         this.child('#inputItem').select();
104182     },
104183
104184     //private
104185     onPagingBlur : function(e){
104186         var curPage = this.getPageData().currentPage;
104187         this.child('#inputItem').setValue(curPage);
104188     },
104189
104190     // private
104191     onPagingKeyDown : function(field, e){
104192         var me = this,
104193             k = e.getKey(),
104194             pageData = me.getPageData(),
104195             increment = e.shiftKey ? 10 : 1,
104196             pageNum;
104197
104198         if (k == e.RETURN) {
104199             e.stopEvent();
104200             pageNum = me.readPageFromInput(pageData);
104201             if (pageNum !== false) {
104202                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
104203                 if(me.fireEvent('beforechange', me, pageNum) !== false){
104204                     me.store.loadPage(pageNum);
104205                 }
104206             }
104207         } else if (k == e.HOME || k == e.END) {
104208             e.stopEvent();
104209             pageNum = k == e.HOME ? 1 : pageData.pageCount;
104210             field.setValue(pageNum);
104211         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
104212             e.stopEvent();
104213             pageNum = me.readPageFromInput(pageData);
104214             if (pageNum) {
104215                 if (k == e.DOWN || k == e.PAGEDOWN) {
104216                     increment *= -1;
104217                 }
104218                 pageNum += increment;
104219                 if (pageNum >= 1 && pageNum <= pageData.pages) {
104220                     field.setValue(pageNum);
104221                 }
104222             }
104223         }
104224     },
104225
104226     // private
104227     beforeLoad : function(){
104228         if(this.rendered && this.refresh){
104229             this.refresh.disable();
104230         }
104231     },
104232
104233     // private
104234     doLoad : function(start){
104235         if(this.fireEvent('beforechange', this, o) !== false){
104236             this.store.load();
104237         }
104238     },
104239
104240     /**
104241      * Move to the first page, has the same effect as clicking the 'first' button.
104242      */
104243     moveFirst : function(){
104244         if (this.fireEvent('beforechange', this, 1) !== false){
104245             this.store.loadPage(1);
104246         }
104247     },
104248
104249     /**
104250      * Move to the previous page, has the same effect as clicking the 'previous' button.
104251      */
104252     movePrevious : function(){
104253         var me = this,
104254             prev = me.store.currentPage - 1;
104255
104256         if (prev > 0) {
104257             if (me.fireEvent('beforechange', me, prev) !== false) {
104258                 me.store.previousPage();
104259             }
104260         }
104261     },
104262
104263     /**
104264      * Move to the next page, has the same effect as clicking the 'next' button.
104265      */
104266     moveNext : function(){
104267         var me = this,
104268             total = me.getPageData().pageCount,
104269             next = me.store.currentPage + 1;
104270
104271         if (next <= total) {
104272             if (me.fireEvent('beforechange', me, next) !== false) {
104273                 me.store.nextPage();
104274             }
104275         }
104276     },
104277
104278     /**
104279      * Move to the last page, has the same effect as clicking the 'last' button.
104280      */
104281     moveLast : function(){
104282         var me = this,
104283             last = me.getPageData().pageCount;
104284
104285         if (me.fireEvent('beforechange', me, last) !== false) {
104286             me.store.loadPage(last);
104287         }
104288     },
104289
104290     /**
104291      * Refresh the current page, has the same effect as clicking the 'refresh' button.
104292      */
104293     doRefresh : function(){
104294         var me = this,
104295             current = me.store.currentPage;
104296
104297         if (me.fireEvent('beforechange', me, current) !== false) {
104298             me.store.loadPage(current);
104299         }
104300     },
104301
104302     /**
104303      * Binds the paging toolbar to the specified {@link Ext.data.Store}
104304      * @param {Ext.data.Store} store The store to bind to this toolbar
104305      * @param {Boolean} initial (Optional) true to not remove listeners
104306      */
104307     bindStore : function(store, initial){
104308         var me = this;
104309
104310         if (!initial && me.store) {
104311             if(store !== me.store && me.store.autoDestroy){
104312                 me.store.destroyStore();
104313             }else{
104314                 me.store.un('beforeload', me.beforeLoad, me);
104315                 me.store.un('load', me.onLoad, me);
104316                 me.store.un('exception', me.onLoadError, me);
104317             }
104318             if(!store){
104319                 me.store = null;
104320             }
104321         }
104322         if (store) {
104323             store = Ext.data.StoreManager.lookup(store);
104324             store.on({
104325                 scope: me,
104326                 beforeload: me.beforeLoad,
104327                 load: me.onLoad,
104328                 exception: me.onLoadError
104329             });
104330         }
104331         me.store = store;
104332     },
104333
104334     /**
104335      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
104336      * @param {Ext.data.Store} store The data store to unbind
104337      */
104338     unbind : function(store){
104339         this.bindStore(null);
104340     },
104341
104342     /**
104343      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
104344      * @param {Ext.data.Store} store The data store to bind
104345      */
104346     bind : function(store){
104347         this.bindStore(store);
104348     },
104349
104350     // private
104351     onDestroy : function(){
104352         this.bindStore(null);
104353         this.callParent();
104354     }
104355 });
104356
104357 /**
104358  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
104359  */
104360 Ext.define('Ext.view.BoundList', {
104361     extend: 'Ext.view.View',
104362     alias: 'widget.boundlist',
104363     alternateClassName: 'Ext.BoundList',
104364     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
104365
104366     /**
104367      * @cfg {Number} pageSize
104368      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
104369      * queries will execute with page {@link Ext.data.Operation#start start} and
104370      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
104371      */
104372     pageSize: 0,
104373
104374     /**
104375      * @property {Ext.toolbar.Paging} pagingToolbar
104376      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
104377      * than zero and the BoundList has been rendered.
104378      */
104379
104380     // private overrides
104381     autoScroll: true,
104382     baseCls: Ext.baseCSSPrefix + 'boundlist',
104383     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
104384     listItemCls: '',
104385     shadow: false,
104386     trackOver: true,
104387     refreshed: 0,
104388
104389     ariaRole: 'listbox',
104390
104391     componentLayout: 'boundlist',
104392
104393     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
104394
104395     initComponent: function() {
104396         var me = this,
104397             baseCls = me.baseCls,
104398             itemCls = me.itemCls;
104399             
104400         me.selectedItemCls = baseCls + '-selected';
104401         me.overItemCls = baseCls + '-item-over';
104402         me.itemSelector = "." + itemCls;
104403
104404         if (me.floating) {
104405             me.addCls(baseCls + '-floating');
104406         }
104407
104408         if (!me.tpl) {
104409             // should be setting aria-posinset based on entire set of data
104410             // not filtered set
104411             me.tpl = Ext.create('Ext.XTemplate',
104412                 '<ul><tpl for=".">',
104413                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
104414                 '</tpl></ul>'
104415             );
104416         } else if (Ext.isString(me.tpl)) {
104417             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
104418         }
104419
104420         if (me.pageSize) {
104421             me.pagingToolbar = me.createPagingToolbar();
104422         }
104423
104424         me.callParent();
104425
104426         me.addChildEls('listEl');
104427     },
104428
104429     createPagingToolbar: function() {
104430         return Ext.widget('pagingtoolbar', {
104431             pageSize: this.pageSize,
104432             store: this.store,
104433             border: false
104434         });
104435     },
104436
104437     onRender: function() {
104438         var me = this,
104439             toolbar = me.pagingToolbar;
104440         me.callParent(arguments);
104441         if (toolbar) {
104442             toolbar.render(me.el);
104443         }
104444     },
104445
104446     bindStore : function(store, initial) {
104447         var me = this,
104448             toolbar = me.pagingToolbar;
104449         me.callParent(arguments);
104450         if (toolbar) {
104451             toolbar.bindStore(store, initial);
104452         }
104453     },
104454
104455     getTargetEl: function() {
104456         return this.listEl || this.el;
104457     },
104458
104459     getInnerTpl: function(displayField) {
104460         return '{' + displayField + '}';
104461     },
104462
104463     refresh: function() {
104464         var me = this;
104465         me.callParent();
104466         if (me.isVisible()) {
104467             me.refreshed++;
104468             me.doComponentLayout();
104469             me.refreshed--;
104470         }
104471     },
104472
104473     initAria: function() {
104474         this.callParent();
104475
104476         var selModel = this.getSelectionModel(),
104477             mode     = selModel.getSelectionMode(),
104478             actionEl = this.getActionEl();
104479
104480         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
104481         if (mode !== 'SINGLE') {
104482             actionEl.dom.setAttribute('aria-multiselectable', true);
104483         }
104484     },
104485
104486     onDestroy: function() {
104487         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
104488         this.callParent();
104489     }
104490 });
104491
104492 /**
104493  * @class Ext.view.BoundListKeyNav
104494  * @extends Ext.util.KeyNav
104495  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
104496  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
104497  * through the list. The enter key invokes the selection model's select action using the highlighted item.
104498  */
104499 Ext.define('Ext.view.BoundListKeyNav', {
104500     extend: 'Ext.util.KeyNav',
104501     requires: 'Ext.view.BoundList',
104502
104503     /**
104504      * @cfg {Ext.view.BoundList} boundList (required)
104505      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
104506      */
104507
104508     constructor: function(el, config) {
104509         var me = this;
104510         me.boundList = config.boundList;
104511         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
104512     },
104513
104514     defaultHandlers: {
104515         up: function() {
104516             var me = this,
104517                 boundList = me.boundList,
104518                 allItems = boundList.all,
104519                 oldItem = boundList.highlightedItem,
104520                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104521                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
104522             me.highlightAt(newItemIdx);
104523         },
104524
104525         down: function() {
104526             var me = this,
104527                 boundList = me.boundList,
104528                 allItems = boundList.all,
104529                 oldItem = boundList.highlightedItem,
104530                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104531                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
104532             me.highlightAt(newItemIdx);
104533         },
104534
104535         pageup: function() {
104536             //TODO
104537         },
104538
104539         pagedown: function() {
104540             //TODO
104541         },
104542
104543         home: function() {
104544             this.highlightAt(0);
104545         },
104546
104547         end: function() {
104548             var me = this;
104549             me.highlightAt(me.boundList.all.getCount() - 1);
104550         },
104551
104552         enter: function(e) {
104553             this.selectHighlighted(e);
104554         }
104555     },
104556
104557     /**
104558      * Highlights the item at the given index.
104559      * @param {Number} index
104560      */
104561     highlightAt: function(index) {
104562         var boundList = this.boundList,
104563             item = boundList.all.item(index);
104564         if (item) {
104565             item = item.dom;
104566             boundList.highlightItem(item);
104567             boundList.getTargetEl().scrollChildIntoView(item, false);
104568         }
104569     },
104570
104571     /**
104572      * Triggers selection of the currently highlighted item according to the behavior of
104573      * the configured SelectionModel.
104574      */
104575     selectHighlighted: function(e) {
104576         var me = this,
104577             boundList = me.boundList,
104578             highlighted = boundList.highlightedItem,
104579             selModel = boundList.getSelectionModel();
104580         if (highlighted) {
104581             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
104582         }
104583     }
104584
104585 });
104586 /**
104587  * @docauthor Jason Johnston <jason@sencha.com>
104588  *
104589  * A combobox control with support for autocomplete, remote loading, and many other features.
104590  *
104591  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
104592  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
104593  * list. The user can input any value by default, even if it does not appear in the selection list;
104594  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
104595  *
104596  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
104597  * stores. The data items in the store are mapped to each option's displayed text and backing value via
104598  * the {@link #valueField} and {@link #displayField} configurations, respectively.
104599  *
104600  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
104601  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
104602  *
104603  * # Example usage:
104604  *
104605  *     @example
104606  *     // The data store containing the list of states
104607  *     var states = Ext.create('Ext.data.Store', {
104608  *         fields: ['abbr', 'name'],
104609  *         data : [
104610  *             {"abbr":"AL", "name":"Alabama"},
104611  *             {"abbr":"AK", "name":"Alaska"},
104612  *             {"abbr":"AZ", "name":"Arizona"}
104613  *             //...
104614  *         ]
104615  *     });
104616  *
104617  *     // Create the combo box, attached to the states data store
104618  *     Ext.create('Ext.form.ComboBox', {
104619  *         fieldLabel: 'Choose State',
104620  *         store: states,
104621  *         queryMode: 'local',
104622  *         displayField: 'name',
104623  *         valueField: 'abbr',
104624  *         renderTo: Ext.getBody()
104625  *     });
104626  *
104627  * # Events
104628  *
104629  * To do something when something in ComboBox is selected, configure the select event:
104630  *
104631  *     var cb = Ext.create('Ext.form.ComboBox', {
104632  *         // all of your config options
104633  *         listeners:{
104634  *              scope: yourScope,
104635  *              'select': yourFunction
104636  *         }
104637  *     });
104638  *
104639  *     // Alternatively, you can assign events after the object is created:
104640  *     var cb = new Ext.form.field.ComboBox(yourOptions);
104641  *     cb.on('select', yourFunction, yourScope);
104642  *
104643  * # Multiple Selection
104644  *
104645  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
104646  * {@link #multiSelect} config to `true`.
104647  */
104648 Ext.define('Ext.form.field.ComboBox', {
104649     extend:'Ext.form.field.Picker',
104650     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
104651     alternateClassName: 'Ext.form.ComboBox',
104652     alias: ['widget.combobox', 'widget.combo'],
104653
104654     /**
104655      * @cfg {String} [triggerCls='x-form-arrow-trigger']
104656      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
104657      * by default and `triggerCls` will be **appended** if specified.
104658      */
104659     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
104660
104661     /**
104662      * @private
104663      * @cfg {String}
104664      * CSS class used to find the {@link #hiddenDataEl}
104665      */
104666     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
104667
104668     /**
104669      * @override
104670      */
104671     fieldSubTpl: [
104672         '<div class="{hiddenDataCls}" role="presentation"></div>',
104673         '<input id="{id}" type="{type}" ',
104674             '<tpl if="size">size="{size}" </tpl>',
104675             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
104676             'class="{fieldCls} {typeCls}" autocomplete="off" />',
104677         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
104678             '{triggerEl}',
104679             '<div class="{clearCls}" role="presentation"></div>',
104680         '</div>',
104681         {
104682             compiled: true,
104683             disableFormats: true
104684         }
104685     ],
104686
104687     getSubTplData: function(){
104688         var me = this;
104689         Ext.applyIf(me.subTplData, {
104690             hiddenDataCls: me.hiddenDataCls
104691         });
104692         return me.callParent(arguments);
104693     },
104694
104695     afterRender: function(){
104696         var me = this;
104697         me.callParent(arguments);
104698         me.setHiddenValue(me.value);
104699     },
104700
104701     /**
104702      * @cfg {Ext.data.Store/Array} store
104703      * The data source to which this combo is bound. Acceptable values for this property are:
104704      *
104705      *   - **any {@link Ext.data.Store Store} subclass**
104706      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
104707      *     {@link Ext.data.Field#name field names} to work with all data components.
104708      *
104709      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
104710      *
104711      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
104712      *       {@link #valueField} and {@link #displayField})
104713      *
104714      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
104715      *
104716      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
104717      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
104718      *
104719      * See also {@link #queryMode}.
104720      */
104721
104722     /**
104723      * @cfg {Boolean} multiSelect
104724      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
104725      * items from the dropdown list. The combo's text field will show all selected values separated by the
104726      * {@link #delimiter}.
104727      */
104728     multiSelect: false,
104729
104730     /**
104731      * @cfg {String} delimiter
104732      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
104733      * `{@link #multiSelect} = true`.
104734      */
104735     delimiter: ', ',
104736
104737     /**
104738      * @cfg {String} displayField
104739      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
104740      *
104741      * See also `{@link #valueField}`.
104742      */
104743     displayField: 'text',
104744
104745     /**
104746      * @cfg {String} valueField (required)
104747      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
104748      * the value of the {@link #displayField} config).
104749      *
104750      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
104751      * `{@link #displayField}`.
104752      */
104753
104754     /**
104755      * @cfg {String} triggerAction
104756      * The action to execute when the trigger is clicked.
104757      *
104758      *   - **`'all'`** :
104759      *
104760      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
104761      *
104762      *   - **`'query'`** :
104763      *
104764      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
104765      *
104766      * See also `{@link #queryParam}`.
104767      */
104768     triggerAction: 'all',
104769
104770     /**
104771      * @cfg {String} allQuery
104772      * The text query to send to the server to return all records for the list with no filtering
104773      */
104774     allQuery: '',
104775
104776     /**
104777      * @cfg {String} queryParam
104778      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
104779      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
104780      */
104781     queryParam: 'query',
104782
104783     /**
104784      * @cfg {String} queryMode
104785      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
104786      *
104787      *   - **`'remote'`** :
104788      *
104789      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
104790      *
104791      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
104792      *     Ext.data.Store#load load}ed.
104793      *
104794      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
104795      *     string is `query`, but this can be configured using the {@link #queryParam} config.
104796      *
104797      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
104798      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
104799      *     request which allows the server to further refine the returned dataset.
104800      *
104801      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
104802      *     autocomplete.
104803      *
104804      *   - **`'local'`** :
104805      *
104806      *     ComboBox loads local data
104807      *
104808      *         var combo = new Ext.form.field.ComboBox({
104809      *             renderTo: document.body,
104810      *             queryMode: 'local',
104811      *             store: new Ext.data.ArrayStore({
104812      *                 id: 0,
104813      *                 fields: [
104814      *                     'myId',  // numeric value is the key
104815      *                     'displayText'
104816      *                 ],
104817      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
104818      *             }),
104819      *             valueField: 'myId',
104820      *             displayField: 'displayText',
104821      *             triggerAction: 'all'
104822      *         });
104823      */
104824     queryMode: 'remote',
104825
104826     queryCaching: true,
104827
104828     /**
104829      * @cfg {Number} pageSize
104830      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
104831      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
104832      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
104833      */
104834     pageSize: 0,
104835
104836     /**
104837      * @cfg {Number} queryDelay
104838      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
104839      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
104840      */
104841
104842     /**
104843      * @cfg {Number} minChars
104844      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
104845      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
104846      * `{@link Ext.form.field.Trigger#editable editable} = false`).
104847      */
104848
104849     /**
104850      * @cfg {Boolean} autoSelect
104851      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
104852      * opened. A false value would cause nothing in the list to be highlighted automatically, so
104853      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
104854      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
104855      */
104856     autoSelect: true,
104857
104858     /**
104859      * @cfg {Boolean} typeAhead
104860      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
104861      * ({@link #typeAheadDelay}) if it matches a known value.
104862      */
104863     typeAhead: false,
104864
104865     /**
104866      * @cfg {Number} typeAheadDelay
104867      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
104868      */
104869     typeAheadDelay: 250,
104870
104871     /**
104872      * @cfg {Boolean} selectOnTab
104873      * Whether the Tab key should select the currently highlighted item.
104874      */
104875     selectOnTab: true,
104876
104877     /**
104878      * @cfg {Boolean} forceSelection
104879      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
104880      * arbitrary text into the field.
104881      */
104882     forceSelection: false,
104883
104884     /**
104885      * @cfg {String} valueNotFoundText
104886      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
104887      * be displayed as the field text if defined. If this default text is used, it means there
104888      * is no value set and no validation will occur on this field.
104889      */
104890
104891     /**
104892      * @property {String} lastQuery
104893      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
104894      *
104895      *     var combo = new Ext.form.field.ComboBox({
104896      *         ...
104897      *         queryMode: 'remote',
104898      *         listeners: {
104899      *             // delete the previous query in the beforequery event or set
104900      *             // combo.lastQuery = null (this will reload the store the next time it expands)
104901      *             beforequery: function(qe){
104902      *                 delete qe.combo.lastQuery;
104903      *             }
104904      *         }
104905      *     });
104906      *
104907      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
104908      * combo with `lastQuery=''`. Example use:
104909      *
104910      *     var combo = new Ext.form.field.ComboBox({
104911      *         ...
104912      *         queryMode: 'local',
104913      *         triggerAction: 'all',
104914      *         lastQuery: ''
104915      *     });
104916      */
104917
104918     /**
104919      * @cfg {Object} defaultListConfig
104920      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
104921      */
104922     defaultListConfig: {
104923         emptyText: '',
104924         loadingText: 'Loading...',
104925         loadingHeight: 70,
104926         minWidth: 70,
104927         maxHeight: 300,
104928         shadow: 'sides'
104929     },
104930
104931     /**
104932      * @cfg {String/HTMLElement/Ext.Element} transform
104933      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
104934      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
104935      * will take precedence over this.
104936      */
104937
104938     /**
104939      * @cfg {Object} listConfig
104940      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
104941      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
104942      *
104943      *   - {@link Ext.view.BoundList#cls} - defaults to empty
104944      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
104945      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
104946      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
104947      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
104948      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
104949      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
104950      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
104951      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
104952      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
104953      *     field if {@link #matchFieldWidth} is true)
104954      */
104955
104956     //private
104957     ignoreSelection: 0,
104958
104959     initComponent: function() {
104960         var me = this,
104961             isDefined = Ext.isDefined,
104962             store = me.store,
104963             transform = me.transform,
104964             transformSelect, isLocalMode;
104965
104966         Ext.applyIf(me.renderSelectors, {
104967             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
104968         });
104969         
104970
104971         this.addEvents(
104972             /**
104973              * @event beforequery
104974              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
104975              * property to true.
104976              *
104977              * @param {Object} queryEvent An object that has these properties:
104978              *
104979              *   - `combo` : Ext.form.field.ComboBox
104980              *
104981              *     This combo box
104982              *
104983              *   - `query` : String
104984              *
104985              *     The query string
104986              *
104987              *   - `forceAll` : Boolean
104988              *
104989              *     True to force "all" query
104990              *
104991              *   - `cancel` : Boolean
104992              *
104993              *     Set to true to cancel the query
104994              */
104995             'beforequery',
104996
104997             /**
104998              * @event select
104999              * Fires when at least one list item is selected.
105000              * @param {Ext.form.field.ComboBox} combo This combo box
105001              * @param {Array} records The selected records
105002              */
105003             'select',
105004
105005             /**
105006              * @event beforeselect
105007              * Fires before the selected item is added to the collection
105008              * @param {Ext.form.field.ComboBox} combo This combo box
105009              * @param {Ext.data.Record} record The selected record
105010              * @param {Number} index The index of the selected record
105011              */
105012             'beforeselect',
105013
105014             /**
105015              * @event beforedeselect
105016              * Fires before the deselected item is removed from the collection
105017              * @param {Ext.form.field.ComboBox} combo This combo box
105018              * @param {Ext.data.Record} record The deselected record
105019              * @param {Number} index The index of the deselected record
105020              */
105021             'beforedeselect'
105022         );
105023
105024         // Build store from 'transform' HTML select element's options
105025         if (transform) {
105026             transformSelect = Ext.getDom(transform);
105027             if (transformSelect) {
105028                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
105029                     return [option.value, option.text];
105030                 });
105031                 if (!me.name) {
105032                     me.name = transformSelect.name;
105033                 }
105034                 if (!('value' in me)) {
105035                     me.value = transformSelect.value;
105036                 }
105037             }
105038         }
105039
105040         me.bindStore(store || 'ext-empty-store', true);
105041         store = me.store;
105042         if (store.autoCreated) {
105043             me.queryMode = 'local';
105044             me.valueField = me.displayField = 'field1';
105045             if (!store.expanded) {
105046                 me.displayField = 'field2';
105047             }
105048         }
105049
105050
105051         if (!isDefined(me.valueField)) {
105052             me.valueField = me.displayField;
105053         }
105054
105055         isLocalMode = me.queryMode === 'local';
105056         if (!isDefined(me.queryDelay)) {
105057             me.queryDelay = isLocalMode ? 10 : 500;
105058         }
105059         if (!isDefined(me.minChars)) {
105060             me.minChars = isLocalMode ? 0 : 4;
105061         }
105062
105063         if (!me.displayTpl) {
105064             me.displayTpl = Ext.create('Ext.XTemplate',
105065                 '<tpl for=".">' +
105066                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
105067                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
105068                 '</tpl>'
105069             );
105070         } else if (Ext.isString(me.displayTpl)) {
105071             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
105072         }
105073
105074         me.callParent();
105075
105076         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
105077
105078         // store has already been loaded, setValue
105079         if (me.store.getCount() > 0) {
105080             me.setValue(me.value);
105081         }
105082
105083         // render in place of 'transform' select
105084         if (transformSelect) {
105085             me.render(transformSelect.parentNode, transformSelect);
105086             Ext.removeNode(transformSelect);
105087             delete me.renderTo;
105088         }
105089     },
105090
105091     /**
105092      * Returns the store associated with this ComboBox.
105093      * @return {Ext.data.Store} The store
105094      */
105095     getStore : function(){
105096         return this.store;
105097     },
105098
105099     beforeBlur: function() {
105100         this.doQueryTask.cancel();
105101         this.assertValue();
105102     },
105103
105104     // private
105105     assertValue: function() {
105106         var me = this,
105107             value = me.getRawValue(),
105108             rec;
105109
105110         if (me.forceSelection) {
105111             if (me.multiSelect) {
105112                 // For multiselect, check that the current displayed value matches the current
105113                 // selection, if it does not then revert to the most recent selection.
105114                 if (value !== me.getDisplayValue()) {
105115                     me.setValue(me.lastSelection);
105116                 }
105117             } else {
105118                 // For single-select, match the displayed value to a record and select it,
105119                 // if it does not match a record then revert to the most recent selection.
105120                 rec = me.findRecordByDisplay(value);
105121                 if (rec) {
105122                     me.select(rec);
105123                 } else {
105124                     me.setValue(me.lastSelection);
105125                 }
105126             }
105127         }
105128         me.collapse();
105129     },
105130
105131     onTypeAhead: function() {
105132         var me = this,
105133             displayField = me.displayField,
105134             record = me.store.findRecord(displayField, me.getRawValue()),
105135             boundList = me.getPicker(),
105136             newValue, len, selStart;
105137
105138         if (record) {
105139             newValue = record.get(displayField);
105140             len = newValue.length;
105141             selStart = me.getRawValue().length;
105142
105143             boundList.highlightItem(boundList.getNode(record));
105144
105145             if (selStart !== 0 && selStart !== len) {
105146                 me.setRawValue(newValue);
105147                 me.selectText(selStart, newValue.length);
105148             }
105149         }
105150     },
105151
105152     // invoked when a different store is bound to this combo
105153     // than the original
105154     resetToDefault: function() {
105155
105156     },
105157
105158     bindStore: function(store, initial) {
105159         var me = this,
105160             oldStore = me.store;
105161
105162         // this code directly accesses this.picker, bc invoking getPicker
105163         // would create it when we may be preping to destroy it
105164         if (oldStore && !initial) {
105165             if (oldStore !== store && oldStore.autoDestroy) {
105166                 oldStore.destroyStore();
105167             } else {
105168                 oldStore.un({
105169                     scope: me,
105170                     load: me.onLoad,
105171                     exception: me.collapse
105172                 });
105173             }
105174             if (!store) {
105175                 me.store = null;
105176                 if (me.picker) {
105177                     me.picker.bindStore(null);
105178                 }
105179             }
105180         }
105181         if (store) {
105182             if (!initial) {
105183                 me.resetToDefault();
105184             }
105185
105186             me.store = Ext.data.StoreManager.lookup(store);
105187             me.store.on({
105188                 scope: me,
105189                 load: me.onLoad,
105190                 exception: me.collapse
105191             });
105192
105193             if (me.picker) {
105194                 me.picker.bindStore(store);
105195             }
105196         }
105197     },
105198
105199     onLoad: function() {
105200         var me = this,
105201             value = me.value;
105202
105203         // If performing a remote query upon the raw value...
105204         if (me.rawQuery) {
105205             me.rawQuery = false;
105206             me.syncSelection();
105207             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
105208                 me.doAutoSelect();
105209             }
105210         }
105211         // If store initial load or triggerAction: 'all' trigger click.
105212         else {
105213             // Set the value on load
105214             if (me.value) {
105215                 me.setValue(me.value);
105216             } else {
105217                 // There's no value.
105218                 // Highlight the first item in the list if autoSelect: true
105219                 if (me.store.getCount()) {
105220                     me.doAutoSelect();
105221                 } else {
105222                     me.setValue('');
105223                 }
105224             }
105225         }
105226     },
105227
105228     /**
105229      * @private
105230      * Execute the query with the raw contents within the textfield.
105231      */
105232     doRawQuery: function() {
105233         this.doQuery(this.getRawValue(), false, true);
105234     },
105235
105236     /**
105237      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
105238      * allowing the query action to be canceled if needed.
105239      *
105240      * @param {String} queryString The SQL query to execute
105241      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
105242      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
105243      * previously saved in the current store.
105244      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
105245      * resulting store load to leave the raw value undisturbed.
105246      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
105247      * handler.
105248      */
105249     doQuery: function(queryString, forceAll, rawQuery) {
105250         queryString = queryString || '';
105251
105252         // store in object and pass by reference in 'beforequery'
105253         // so that client code can modify values.
105254         var me = this,
105255             qe = {
105256                 query: queryString,
105257                 forceAll: forceAll,
105258                 combo: me,
105259                 cancel: false
105260             },
105261             store = me.store,
105262             isLocalMode = me.queryMode === 'local';
105263
105264         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
105265             return false;
105266         }
105267
105268         // get back out possibly modified values
105269         queryString = qe.query;
105270         forceAll = qe.forceAll;
105271
105272         // query permitted to run
105273         if (forceAll || (queryString.length >= me.minChars)) {
105274             // expand before starting query so LoadMask can position itself correctly
105275             me.expand();
105276
105277             // make sure they aren't querying the same thing
105278             if (!me.queryCaching || me.lastQuery !== queryString) {
105279                 me.lastQuery = queryString;
105280
105281                 if (isLocalMode) {
105282                     // forceAll means no filtering - show whole dataset.
105283                     if (forceAll) {
105284                         store.clearFilter();
105285                     } else {
105286                         // Clear filter, but supress event so that the BoundList is not immediately updated.
105287                         store.clearFilter(true);
105288                         store.filter(me.displayField, queryString);
105289                     }
105290                 } else {
105291                     // Set flag for onLoad handling to know how the Store was loaded
105292                     me.rawQuery = rawQuery;
105293
105294                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
105295                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
105296                     if (me.pageSize) {
105297                         // if we're paging, we've changed the query so start at page 1.
105298                         me.loadPage(1);
105299                     } else {
105300                         store.load({
105301                             params: me.getParams(queryString)
105302                         });
105303                     }
105304                 }
105305             }
105306
105307             // Clear current selection if it does not match the current value in the field
105308             if (me.getRawValue() !== me.getDisplayValue()) {
105309                 me.ignoreSelection++;
105310                 me.picker.getSelectionModel().deselectAll();
105311                 me.ignoreSelection--;
105312             }
105313
105314             if (isLocalMode) {
105315                 me.doAutoSelect();
105316             }
105317             if (me.typeAhead) {
105318                 me.doTypeAhead();
105319             }
105320         }
105321         return true;
105322     },
105323
105324     loadPage: function(pageNum){
105325         this.store.loadPage(pageNum, {
105326             params: this.getParams(this.lastQuery)
105327         });
105328     },
105329
105330     onPageChange: function(toolbar, newPage){
105331         /*
105332          * Return false here so we can call load ourselves and inject the query param.
105333          * We don't want to do this for every store load since the developer may load
105334          * the store through some other means so we won't add the query param.
105335          */
105336         this.loadPage(newPage);
105337         return false;
105338     },
105339
105340     // private
105341     getParams: function(queryString) {
105342         var params = {},
105343             param = this.queryParam;
105344
105345         if (param) {
105346             params[param] = queryString;
105347         }
105348         return params;
105349     },
105350
105351     /**
105352      * @private
105353      * If the autoSelect config is true, and the picker is open, highlights the first item.
105354      */
105355     doAutoSelect: function() {
105356         var me = this,
105357             picker = me.picker,
105358             lastSelected, itemNode;
105359         if (picker && me.autoSelect && me.store.getCount() > 0) {
105360             // Highlight the last selected item and scroll it into view
105361             lastSelected = picker.getSelectionModel().lastSelected;
105362             itemNode = picker.getNode(lastSelected || 0);
105363             if (itemNode) {
105364                 picker.highlightItem(itemNode);
105365                 picker.listEl.scrollChildIntoView(itemNode, false);
105366             }
105367         }
105368     },
105369
105370     doTypeAhead: function() {
105371         if (!this.typeAheadTask) {
105372             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
105373         }
105374         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
105375             this.typeAheadTask.delay(this.typeAheadDelay);
105376         }
105377     },
105378
105379     onTriggerClick: function() {
105380         var me = this;
105381         if (!me.readOnly && !me.disabled) {
105382             if (me.isExpanded) {
105383                 me.collapse();
105384             } else {
105385                 me.onFocus({});
105386                 if (me.triggerAction === 'all') {
105387                     me.doQuery(me.allQuery, true);
105388                 } else {
105389                     me.doQuery(me.getRawValue(), false, true);
105390                 }
105391             }
105392             me.inputEl.focus();
105393         }
105394     },
105395
105396
105397     // store the last key and doQuery if relevant
105398     onKeyUp: function(e, t) {
105399         var me = this,
105400             key = e.getKey();
105401
105402         if (!me.readOnly && !me.disabled && me.editable) {
105403             me.lastKey = key;
105404             // we put this in a task so that we can cancel it if a user is
105405             // in and out before the queryDelay elapses
105406
105407             // perform query w/ any normal key or backspace or delete
105408             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
105409                 me.doQueryTask.delay(me.queryDelay);
105410             }
105411         }
105412
105413         if (me.enableKeyEvents) {
105414             me.callParent(arguments);
105415         }
105416     },
105417
105418     initEvents: function() {
105419         var me = this;
105420         me.callParent();
105421
105422         /*
105423          * Setup keyboard handling. If enableKeyEvents is true, we already have
105424          * a listener on the inputEl for keyup, so don't create a second.
105425          */
105426         if (!me.enableKeyEvents) {
105427             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
105428         }
105429     },
105430     
105431     onDestroy: function(){
105432         this.bindStore(null);
105433         this.callParent();    
105434     },
105435
105436     createPicker: function() {
105437         var me = this,
105438             picker,
105439             menuCls = Ext.baseCSSPrefix + 'menu',
105440             opts = Ext.apply({
105441                 pickerField: me,
105442                 selModel: {
105443                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
105444                 },
105445                 floating: true,
105446                 hidden: true,
105447                 ownerCt: me.ownerCt,
105448                 cls: me.el.up('.' + menuCls) ? menuCls : '',
105449                 store: me.store,
105450                 displayField: me.displayField,
105451                 focusOnToFront: false,
105452                 pageSize: me.pageSize,
105453                 tpl: me.tpl
105454             }, me.listConfig, me.defaultListConfig);
105455
105456         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
105457         if (me.pageSize) {
105458             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
105459         }
105460
105461         me.mon(picker, {
105462             itemclick: me.onItemClick,
105463             refresh: me.onListRefresh,
105464             scope: me
105465         });
105466
105467         me.mon(picker.getSelectionModel(), {
105468             'beforeselect': me.onBeforeSelect,
105469             'beforedeselect': me.onBeforeDeselect,
105470             'selectionchange': me.onListSelectionChange,
105471             scope: me
105472         });
105473
105474         return picker;
105475     },
105476
105477     alignPicker: function(){
105478         var me = this,
105479             picker = me.picker,
105480             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
105481             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
105482             space = Math.max(heightAbove, heightBelow);
105483
105484         me.callParent();
105485         if (picker.getHeight() > space) {
105486             picker.setHeight(space - 5); // have some leeway so we aren't flush against
105487             me.doAlign();
105488         }
105489     },
105490
105491     onListRefresh: function() {
105492         this.alignPicker();
105493         this.syncSelection();
105494     },
105495
105496     onItemClick: function(picker, record){
105497         /*
105498          * If we're doing single selection, the selection change events won't fire when
105499          * clicking on the selected element. Detect it here.
105500          */
105501         var me = this,
105502             lastSelection = me.lastSelection,
105503             valueField = me.valueField,
105504             selected;
105505
105506         if (!me.multiSelect && lastSelection) {
105507             selected = lastSelection[0];
105508             if (selected && (record.get(valueField) === selected.get(valueField))) {
105509                 // Make sure we also update the display value if it's only partial
105510                 me.displayTplData = [record.data];
105511                 me.setRawValue(me.getDisplayValue());
105512                 me.collapse();
105513             }
105514         }
105515     },
105516
105517     onBeforeSelect: function(list, record) {
105518         return this.fireEvent('beforeselect', this, record, record.index);
105519     },
105520
105521     onBeforeDeselect: function(list, record) {
105522         return this.fireEvent('beforedeselect', this, record, record.index);
105523     },
105524
105525     onListSelectionChange: function(list, selectedRecords) {
105526         var me = this,
105527             isMulti = me.multiSelect,
105528             hasRecords = selectedRecords.length > 0;
105529         // Only react to selection if it is not called from setValue, and if our list is
105530         // expanded (ignores changes to the selection model triggered elsewhere)
105531         if (!me.ignoreSelection && me.isExpanded) {
105532             if (!isMulti) {
105533                 Ext.defer(me.collapse, 1, me);
105534             }
105535             /*
105536              * Only set the value here if we're in multi selection mode or we have
105537              * a selection. Otherwise setValue will be called with an empty value
105538              * which will cause the change event to fire twice.
105539              */
105540             if (isMulti || hasRecords) {
105541                 me.setValue(selectedRecords, false);
105542             }
105543             if (hasRecords) {
105544                 me.fireEvent('select', me, selectedRecords);
105545             }
105546             me.inputEl.focus();
105547         }
105548     },
105549
105550     /**
105551      * @private
105552      * Enables the key nav for the BoundList when it is expanded.
105553      */
105554     onExpand: function() {
105555         var me = this,
105556             keyNav = me.listKeyNav,
105557             selectOnTab = me.selectOnTab,
105558             picker = me.getPicker();
105559
105560         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
105561         if (keyNav) {
105562             keyNav.enable();
105563         } else {
105564             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
105565                 boundList: picker,
105566                 forceKeyDown: true,
105567                 tab: function(e) {
105568                     if (selectOnTab) {
105569                         this.selectHighlighted(e);
105570                         me.triggerBlur();
105571                     }
105572                     // Tab key event is allowed to propagate to field
105573                     return true;
105574                 }
105575             });
105576         }
105577
105578         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
105579         if (selectOnTab) {
105580             me.ignoreMonitorTab = true;
105581         }
105582
105583         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
105584         me.inputEl.focus();
105585     },
105586
105587     /**
105588      * @private
105589      * Disables the key nav for the BoundList when it is collapsed.
105590      */
105591     onCollapse: function() {
105592         var me = this,
105593             keyNav = me.listKeyNav;
105594         if (keyNav) {
105595             keyNav.disable();
105596             me.ignoreMonitorTab = false;
105597         }
105598     },
105599
105600     /**
105601      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
105602      * @param {Object} r
105603      */
105604     select: function(r) {
105605         this.setValue(r, true);
105606     },
105607
105608     /**
105609      * Finds the record by searching for a specific field/value combination.
105610      * @param {String} field The name of the field to test.
105611      * @param {Object} value The value to match the field against.
105612      * @return {Ext.data.Model} The matched record or false.
105613      */
105614     findRecord: function(field, value) {
105615         var ds = this.store,
105616             idx = ds.findExact(field, value);
105617         return idx !== -1 ? ds.getAt(idx) : false;
105618     },
105619
105620     /**
105621      * Finds the record by searching values in the {@link #valueField}.
105622      * @param {Object} value The value to match the field against.
105623      * @return {Ext.data.Model} The matched record or false.
105624      */
105625     findRecordByValue: function(value) {
105626         return this.findRecord(this.valueField, value);
105627     },
105628
105629     /**
105630      * Finds the record by searching values in the {@link #displayField}.
105631      * @param {Object} value The value to match the field against.
105632      * @return {Ext.data.Model} The matched record or false.
105633      */
105634     findRecordByDisplay: function(value) {
105635         return this.findRecord(this.displayField, value);
105636     },
105637
105638     /**
105639      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
105640      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
105641      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
105642      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
105643      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
105644      * or an Array of Strings or Models.
105645      * @return {Ext.form.field.Field} this
105646      */
105647     setValue: function(value, doSelect) {
105648         var me = this,
105649             valueNotFoundText = me.valueNotFoundText,
105650             inputEl = me.inputEl,
105651             i, len, record,
105652             models = [],
105653             displayTplData = [],
105654             processedValue = [];
105655
105656         if (me.store.loading) {
105657             // Called while the Store is loading. Ensure it is processed by the onLoad method.
105658             me.value = value;
105659             me.setHiddenValue(me.value);
105660             return me;
105661         }
105662
105663         // This method processes multi-values, so ensure value is an array.
105664         value = Ext.Array.from(value);
105665
105666         // Loop through values
105667         for (i = 0, len = value.length; i < len; i++) {
105668             record = value[i];
105669             if (!record || !record.isModel) {
105670                 record = me.findRecordByValue(record);
105671             }
105672             // record found, select it.
105673             if (record) {
105674                 models.push(record);
105675                 displayTplData.push(record.data);
105676                 processedValue.push(record.get(me.valueField));
105677             }
105678             // record was not found, this could happen because
105679             // store is not loaded or they set a value not in the store
105680             else {
105681                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
105682                 if (!me.forceSelection) {
105683                     displayTplData.push(value[i]);
105684                     processedValue.push(value[i]);
105685                 }
105686                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
105687                 else if (Ext.isDefined(valueNotFoundText)) {
105688                     displayTplData.push(valueNotFoundText);
105689                 }
105690             }
105691         }
105692
105693         // Set the value of this field. If we are multiselecting, then that is an array.
105694         me.setHiddenValue(processedValue);
105695         me.value = me.multiSelect ? processedValue : processedValue[0];
105696         if (!Ext.isDefined(me.value)) {
105697             me.value = null;
105698         }
105699         me.displayTplData = displayTplData; //store for getDisplayValue method
105700         me.lastSelection = me.valueModels = models;
105701
105702         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
105703             inputEl.removeCls(me.emptyCls);
105704         }
105705
105706         // Calculate raw value from the collection of Model data
105707         me.setRawValue(me.getDisplayValue());
105708         me.checkChange();
105709
105710         if (doSelect !== false) {
105711             me.syncSelection();
105712         }
105713         me.applyEmptyText();
105714
105715         return me;
105716     },
105717
105718     /**
105719      * @private
105720      * Set the value of {@link #hiddenDataEl}
105721      * Dynamically adds and removes input[type=hidden] elements
105722      */
105723     setHiddenValue: function(values){
105724         var me = this, i;
105725         if (!me.hiddenDataEl) {
105726             return;
105727         }
105728         values = Ext.Array.from(values);
105729         var dom = me.hiddenDataEl.dom,
105730             childNodes = dom.childNodes,
105731             input = childNodes[0],
105732             valueCount = values.length,
105733             childrenCount = childNodes.length;
105734         
105735         if (!input && valueCount > 0) {
105736             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
105737             childrenCount = 1;
105738             input = dom.firstChild;
105739         }
105740         while (childrenCount > valueCount) {
105741             dom.removeChild(childNodes[0]);
105742             -- childrenCount;
105743         }
105744         while (childrenCount < valueCount) {
105745             dom.appendChild(input.cloneNode(true));
105746             ++ childrenCount;
105747         }
105748         for (i = 0; i < valueCount; i++) {
105749             childNodes[i].value = values[i];
105750         }
105751     },
105752
105753     /**
105754      * @private Generates the string value to be displayed in the text field for the currently stored value
105755      */
105756     getDisplayValue: function() {
105757         return this.displayTpl.apply(this.displayTplData);
105758     },
105759
105760     getValue: function() {
105761         // If the user has not changed the raw field value since a value was selected from the list,
105762         // then return the structured value from the selection. If the raw field value is different
105763         // than what would be displayed due to selection, return that raw value.
105764         var me = this,
105765             picker = me.picker,
105766             rawValue = me.getRawValue(), //current value of text field
105767             value = me.value; //stored value from last selection or setValue() call
105768
105769         if (me.getDisplayValue() !== rawValue) {
105770             value = rawValue;
105771             me.value = me.displayTplData = me.valueModels = null;
105772             if (picker) {
105773                 me.ignoreSelection++;
105774                 picker.getSelectionModel().deselectAll();
105775                 me.ignoreSelection--;
105776             }
105777         }
105778
105779         return value;
105780     },
105781
105782     getSubmitValue: function() {
105783         return this.getValue();
105784     },
105785
105786     isEqual: function(v1, v2) {
105787         var fromArray = Ext.Array.from,
105788             i, len;
105789
105790         v1 = fromArray(v1);
105791         v2 = fromArray(v2);
105792         len = v1.length;
105793
105794         if (len !== v2.length) {
105795             return false;
105796         }
105797
105798         for(i = 0; i < len; i++) {
105799             if (v2[i] !== v1[i]) {
105800                 return false;
105801             }
105802         }
105803
105804         return true;
105805     },
105806
105807     /**
105808      * Clears any value currently set in the ComboBox.
105809      */
105810     clearValue: function() {
105811         this.setValue([]);
105812     },
105813
105814     /**
105815      * @private Synchronizes the selection in the picker to match the current value of the combobox.
105816      */
105817     syncSelection: function() {
105818         var me = this,
105819             ExtArray = Ext.Array,
105820             picker = me.picker,
105821             selection, selModel;
105822         if (picker) {
105823             // From the value, find the Models that are in the store's current data
105824             selection = [];
105825             ExtArray.forEach(me.valueModels || [], function(value) {
105826                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
105827                     selection.push(value);
105828                 }
105829             });
105830
105831             // Update the selection to match
105832             me.ignoreSelection++;
105833             selModel = picker.getSelectionModel();
105834             selModel.deselectAll();
105835             if (selection.length) {
105836                 selModel.select(selection);
105837             }
105838             me.ignoreSelection--;
105839         }
105840     }
105841 });
105842
105843 /**
105844  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
105845  * to allow browsing and selection of year/months combinations.
105846  */
105847 Ext.define('Ext.picker.Month', {
105848     extend: 'Ext.Component',
105849     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
105850     alias: 'widget.monthpicker',
105851     alternateClassName: 'Ext.MonthPicker',
105852
105853     renderTpl: [
105854         '<div id="{id}-bodyEl" class="{baseCls}-body">',
105855           '<div class="{baseCls}-months">',
105856               '<tpl for="months">',
105857                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
105858               '</tpl>',
105859           '</div>',
105860           '<div class="{baseCls}-years">',
105861               '<div class="{baseCls}-yearnav">',
105862                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
105863                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
105864               '</div>',
105865               '<tpl for="years">',
105866                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
105867               '</tpl>',
105868           '</div>',
105869           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
105870         '</div>',
105871         '<tpl if="showButtons">',
105872           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
105873         '</tpl>'
105874     ],
105875
105876     /**
105877      * @cfg {String} okText The text to display on the ok button.
105878      */
105879     okText: 'OK',
105880
105881     /**
105882      * @cfg {String} cancelText The text to display on the cancel button.
105883      */
105884     cancelText: 'Cancel',
105885
105886     /**
105887      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
105888      */
105889     baseCls: Ext.baseCSSPrefix + 'monthpicker',
105890
105891     /**
105892      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
105893      */
105894     showButtons: true,
105895
105896     /**
105897      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
105898      * <tt>'x-monthpicker-selected'</tt>
105899      */
105900
105901     /**
105902      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
105903      */
105904     width: 178,
105905
105906     // used when attached to date picker which isnt showing buttons
105907     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
105908
105909     // private
105910     totalYears: 10,
105911     yearOffset: 5, // 10 years in total, 2 per row
105912     monthOffset: 6, // 12 months, 2 per row
105913
105914     // private, inherit docs
105915     initComponent: function(){
105916         var me = this;
105917
105918         me.selectedCls = me.baseCls + '-selected';
105919         me.addEvents(
105920             /**
105921              * @event cancelclick
105922              * Fires when the cancel button is pressed.
105923              * @param {Ext.picker.Month} this
105924              */
105925             'cancelclick',
105926
105927             /**
105928              * @event monthclick
105929              * Fires when a month is clicked.
105930              * @param {Ext.picker.Month} this
105931              * @param {Array} value The current value
105932              */
105933             'monthclick',
105934
105935             /**
105936              * @event monthdblclick
105937              * Fires when a month is clicked.
105938              * @param {Ext.picker.Month} this
105939              * @param {Array} value The current value
105940              */
105941             'monthdblclick',
105942
105943             /**
105944              * @event okclick
105945              * Fires when the ok button is pressed.
105946              * @param {Ext.picker.Month} this
105947              * @param {Array} value The current value
105948              */
105949             'okclick',
105950
105951             /**
105952              * @event select
105953              * Fires when a month/year is selected.
105954              * @param {Ext.picker.Month} this
105955              * @param {Array} value The current value
105956              */
105957             'select',
105958
105959             /**
105960              * @event yearclick
105961              * Fires when a year is clicked.
105962              * @param {Ext.picker.Month} this
105963              * @param {Array} value The current value
105964              */
105965             'yearclick',
105966
105967             /**
105968              * @event yeardblclick
105969              * Fires when a year is clicked.
105970              * @param {Ext.picker.Month} this
105971              * @param {Array} value The current value
105972              */
105973             'yeardblclick'
105974         );
105975         if (me.small) {
105976             me.addCls(me.smallCls);
105977         }
105978         me.setValue(me.value);
105979         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
105980         this.callParent();
105981     },
105982
105983     // private, inherit docs
105984     onRender: function(ct, position){
105985         var me = this,
105986             i = 0,
105987             months = [],
105988             shortName = Ext.Date.getShortMonthName,
105989             monthLen = me.monthOffset;
105990
105991         for (; i < monthLen; ++i) {
105992             months.push(shortName(i), shortName(i + monthLen));
105993         }
105994
105995         Ext.apply(me.renderData, {
105996             months: months,
105997             years: me.getYears(),
105998             showButtons: me.showButtons
105999         });
106000
106001         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
106002
106003         me.callParent(arguments);
106004     },
106005
106006     // private, inherit docs
106007     afterRender: function(){
106008         var me = this,
106009             body = me.bodyEl,
106010             buttonsEl = me.buttonsEl;
106011
106012         me.callParent();
106013
106014         me.mon(body, 'click', me.onBodyClick, me);
106015         me.mon(body, 'dblclick', me.onBodyClick, me);
106016
106017         // keep a reference to the year/month elements since we'll be re-using them
106018         me.years = body.select('.' + me.baseCls + '-year a');
106019         me.months = body.select('.' + me.baseCls + '-month a');
106020
106021         if (me.showButtons) {
106022             me.okBtn = Ext.create('Ext.button.Button', {
106023                 text: me.okText,
106024                 renderTo: buttonsEl,
106025                 handler: me.onOkClick,
106026                 scope: me
106027             });
106028             me.cancelBtn = Ext.create('Ext.button.Button', {
106029                 text: me.cancelText,
106030                 renderTo: buttonsEl,
106031                 handler: me.onCancelClick,
106032                 scope: me
106033             });
106034         }
106035
106036         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106037             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
106038         });
106039
106040         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
106041         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106042             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
106043         });
106044         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
106045         me.updateBody();
106046     },
106047
106048     /**
106049      * Set the value for the picker.
106050      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
106051      * it can be an array, with the month as the first index and the year as the second.
106052      * @return {Ext.picker.Month} this
106053      */
106054     setValue: function(value){
106055         var me = this,
106056             active = me.activeYear,
106057             offset = me.monthOffset,
106058             year,
106059             index;
106060
106061         if (!value) {
106062             me.value = [null, null];
106063         } else if (Ext.isDate(value)) {
106064             me.value = [value.getMonth(), value.getFullYear()];
106065         } else {
106066             me.value = [value[0], value[1]];
106067         }
106068
106069         if (me.rendered) {
106070             year = me.value[1];
106071             if (year !== null) {
106072                 if ((year < active || year > active + me.yearOffset)) {
106073                     me.activeYear = year - me.yearOffset + 1;
106074                 }
106075             }
106076             me.updateBody();
106077         }
106078
106079         return me;
106080     },
106081
106082     /**
106083      * Gets the selected value. It is returned as an array [month, year]. It may
106084      * be a partial value, for example [null, 2010]. The month is returned as
106085      * 0 based.
106086      * @return {Number[]} The selected value
106087      */
106088     getValue: function(){
106089         return this.value;
106090     },
106091
106092     /**
106093      * Checks whether the picker has a selection
106094      * @return {Boolean} Returns true if both a month and year have been selected
106095      */
106096     hasSelection: function(){
106097         var value = this.value;
106098         return value[0] !== null && value[1] !== null;
106099     },
106100
106101     /**
106102      * Get an array of years to be pushed in the template. It is not in strict
106103      * numerical order because we want to show them in columns.
106104      * @private
106105      * @return {Number[]} An array of years
106106      */
106107     getYears: function(){
106108         var me = this,
106109             offset = me.yearOffset,
106110             start = me.activeYear, // put the "active" year on the left
106111             end = start + offset,
106112             i = start,
106113             years = [];
106114
106115         for (; i < end; ++i) {
106116             years.push(i, i + offset);
106117         }
106118
106119         return years;
106120     },
106121
106122     /**
106123      * Update the years in the body based on any change
106124      * @private
106125      */
106126     updateBody: function(){
106127         var me = this,
106128             years = me.years,
106129             months = me.months,
106130             yearNumbers = me.getYears(),
106131             cls = me.selectedCls,
106132             value = me.getYear(null),
106133             month = me.value[0],
106134             monthOffset = me.monthOffset,
106135             year;
106136
106137         if (me.rendered) {
106138             years.removeCls(cls);
106139             months.removeCls(cls);
106140             years.each(function(el, all, index){
106141                 year = yearNumbers[index];
106142                 el.dom.innerHTML = year;
106143                 if (year == value) {
106144                     el.dom.className = cls;
106145                 }
106146             });
106147             if (month !== null) {
106148                 if (month < monthOffset) {
106149                     month = month * 2;
106150                 } else {
106151                     month = (month - monthOffset) * 2 + 1;
106152                 }
106153                 months.item(month).addCls(cls);
106154             }
106155         }
106156     },
106157
106158     /**
106159      * Gets the current year value, or the default.
106160      * @private
106161      * @param {Number} defaultValue The default value to use if the year is not defined.
106162      * @param {Number} offset A number to offset the value by
106163      * @return {Number} The year value
106164      */
106165     getYear: function(defaultValue, offset) {
106166         var year = this.value[1];
106167         offset = offset || 0;
106168         return year === null ? defaultValue : year + offset;
106169     },
106170
106171     /**
106172      * React to clicks on the body
106173      * @private
106174      */
106175     onBodyClick: function(e, t) {
106176         var me = this,
106177             isDouble = e.type == 'dblclick';
106178
106179         if (e.getTarget('.' + me.baseCls + '-month')) {
106180             e.stopEvent();
106181             me.onMonthClick(t, isDouble);
106182         } else if (e.getTarget('.' + me.baseCls + '-year')) {
106183             e.stopEvent();
106184             me.onYearClick(t, isDouble);
106185         }
106186     },
106187
106188     /**
106189      * Modify the year display by passing an offset.
106190      * @param {Number} [offset=10] The offset to move by.
106191      */
106192     adjustYear: function(offset){
106193         if (typeof offset != 'number') {
106194             offset = this.totalYears;
106195         }
106196         this.activeYear += offset;
106197         this.updateBody();
106198     },
106199
106200     /**
106201      * React to the ok button being pressed
106202      * @private
106203      */
106204     onOkClick: function(){
106205         this.fireEvent('okclick', this, this.value);
106206     },
106207
106208     /**
106209      * React to the cancel button being pressed
106210      * @private
106211      */
106212     onCancelClick: function(){
106213         this.fireEvent('cancelclick', this);
106214     },
106215
106216     /**
106217      * React to a month being clicked
106218      * @private
106219      * @param {HTMLElement} target The element that was clicked
106220      * @param {Boolean} isDouble True if the event was a doubleclick
106221      */
106222     onMonthClick: function(target, isDouble){
106223         var me = this;
106224         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
106225         me.updateBody();
106226         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106227         me.fireEvent('select', me, me.value);
106228     },
106229
106230     /**
106231      * React to a year being clicked
106232      * @private
106233      * @param {HTMLElement} target The element that was clicked
106234      * @param {Boolean} isDouble True if the event was a doubleclick
106235      */
106236     onYearClick: function(target, isDouble){
106237         var me = this;
106238         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
106239         me.updateBody();
106240         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106241         me.fireEvent('select', me, me.value);
106242
106243     },
106244
106245     /**
106246      * Returns an offsetted number based on the position in the collection. Since our collections aren't
106247      * numerically ordered, this function helps to normalize those differences.
106248      * @private
106249      * @param {Object} index
106250      * @param {Object} offset
106251      * @return {Number} The correctly offsetted number
106252      */
106253     resolveOffset: function(index, offset){
106254         if (index % 2 === 0) {
106255             return (index / 2);
106256         } else {
106257             return offset + Math.floor(index / 2);
106258         }
106259     },
106260
106261     // private, inherit docs
106262     beforeDestroy: function(){
106263         var me = this;
106264         me.years = me.months = null;
106265         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
106266         me.callParent();
106267     }
106268 });
106269
106270 /**
106271  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
106272  * dates in a popup next to the field, but may also be used with other components.
106273  *
106274  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
106275  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
106276  *
106277  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
106278  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
106279  *
106280  * All the string values documented below may be overridden by including an Ext locale file in your page.
106281  *
106282  *     @example
106283  *     Ext.create('Ext.panel.Panel', {
106284  *         title: 'Choose a future date:',
106285  *         width: 200,
106286  *         bodyPadding: 10,
106287  *         renderTo: Ext.getBody(),
106288  *         items: [{
106289  *             xtype: 'datepicker',
106290  *             minDate: new Date(),
106291  *             handler: function(picker, date) {
106292  *                 // do something with the selected date
106293  *             }
106294  *         }]
106295  *     });
106296  */
106297 Ext.define('Ext.picker.Date', {
106298     extend: 'Ext.Component',
106299     requires: [
106300         'Ext.XTemplate',
106301         'Ext.button.Button',
106302         'Ext.button.Split',
106303         'Ext.util.ClickRepeater',
106304         'Ext.util.KeyNav',
106305         'Ext.EventObject',
106306         'Ext.fx.Manager',
106307         'Ext.picker.Month'
106308     ],
106309     alias: 'widget.datepicker',
106310     alternateClassName: 'Ext.DatePicker',
106311
106312     renderTpl: [
106313         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
106314             '<div role="presentation" class="{baseCls}-header">',
106315                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
106316                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
106317                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
106318             '</div>',
106319             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
106320                 '<thead role="presentation"><tr role="presentation">',
106321                     '<tpl for="dayNames">',
106322                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
106323                     '</tpl>',
106324                 '</tr></thead>',
106325                 '<tbody role="presentation"><tr role="presentation">',
106326                     '<tpl for="days">',
106327                         '{#:this.isEndOfWeek}',
106328                         '<td role="gridcell" id="{[Ext.id()]}">',
106329                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
106330                                 '<em role="presentation"><span role="presentation"></span></em>',
106331                             '</a>',
106332                         '</td>',
106333                     '</tpl>',
106334                 '</tr></tbody>',
106335             '</table>',
106336             '<tpl if="showToday">',
106337                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
106338             '</tpl>',
106339         '</div>',
106340         {
106341             firstInitial: function(value) {
106342                 return value.substr(0,1);
106343             },
106344             isEndOfWeek: function(value) {
106345                 // convert from 1 based index to 0 based
106346                 // by decrementing value once.
106347                 value--;
106348                 var end = value % 7 === 0 && value !== 0;
106349                 return end ? '</tr><tr role="row">' : '';
106350             },
106351             longDay: function(value){
106352                 return Ext.Date.format(value, this.longDayFormat);
106353             }
106354         }
106355     ],
106356
106357     ariaTitle: 'Date Picker',
106358
106359     /**
106360      * @cfg {String} todayText
106361      * The text to display on the button that selects the current date
106362      */
106363     todayText : 'Today',
106364
106365     /**
106366      * @cfg {Function} handler
106367      * Optional. A function that will handle the select event of this picker. The handler is passed the following
106368      * parameters:
106369      *
106370      *   - `picker` : Ext.picker.Date
106371      *
106372      * This Date picker.
106373      *
106374      *   - `date` : Date
106375      *
106376      * The selected date.
106377      */
106378
106379     /**
106380      * @cfg {Object} scope
106381      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
106382      * DatePicker instance.
106383      */
106384
106385     /**
106386      * @cfg {String} todayTip
106387      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
106388      * The `{0}` token in string is replaced by today's date.
106389      */
106390     todayTip : '{0} (Spacebar)',
106391
106392     /**
106393      * @cfg {String} minText
106394      * The error text to display if the minDate validation fails.
106395      */
106396     minText : 'This date is before the minimum date',
106397
106398     /**
106399      * @cfg {String} maxText
106400      * The error text to display if the maxDate validation fails.
106401      */
106402     maxText : 'This date is after the maximum date',
106403
106404     /**
106405      * @cfg {String} format
106406      * The default date format string which can be overriden for localization support. The format must be valid
106407      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
106408      */
106409
106410     /**
106411      * @cfg {String} disabledDaysText
106412      * The tooltip to display when the date falls on a disabled day.
106413      */
106414     disabledDaysText : 'Disabled',
106415
106416     /**
106417      * @cfg {String} disabledDatesText
106418      * The tooltip text to display when the date falls on a disabled date.
106419      */
106420     disabledDatesText : 'Disabled',
106421
106422     /**
106423      * @cfg {String[]} monthNames
106424      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
106425      */
106426
106427     /**
106428      * @cfg {String[]} dayNames
106429      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
106430      */
106431
106432     /**
106433      * @cfg {String} nextText
106434      * The next month navigation button tooltip
106435      */
106436     nextText : 'Next Month (Control+Right)',
106437
106438     /**
106439      * @cfg {String} prevText
106440      * The previous month navigation button tooltip
106441      */
106442     prevText : 'Previous Month (Control+Left)',
106443
106444     /**
106445      * @cfg {String} monthYearText
106446      * The header month selector tooltip
106447      */
106448     monthYearText : 'Choose a month (Control+Up/Down to move years)',
106449
106450     /**
106451      * @cfg {Number} startDay
106452      * Day index at which the week should begin, 0-based (defaults to Sunday)
106453      */
106454     startDay : 0,
106455
106456     /**
106457      * @cfg {Boolean} showToday
106458      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
106459      * selects the current date.
106460      */
106461     showToday : true,
106462
106463     /**
106464      * @cfg {Date} [minDate=null]
106465      * Minimum allowable date (JavaScript date object)
106466      */
106467
106468     /**
106469      * @cfg {Date} [maxDate=null]
106470      * Maximum allowable date (JavaScript date object)
106471      */
106472
106473     /**
106474      * @cfg {Number[]} [disabledDays=null]
106475      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
106476      */
106477
106478     /**
106479      * @cfg {RegExp} [disabledDatesRE=null]
106480      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
106481      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
106482      * disabledDates value.
106483      */
106484
106485     /**
106486      * @cfg {String[]} disabledDates
106487      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
106488      * they are very powerful. Some examples:
106489      *
106490      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
106491      *   - ['03/08', '09/16'] would disable those days for every year
106492      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
106493      *   - ['03/../2006'] would disable every day in March 2006
106494      *   - ['^03'] would disable every day in every March
106495      *
106496      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
106497      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
106498      * dot when restricting dates. For example: ['03\\.08\\.03'].
106499      */
106500
106501     /**
106502      * @cfg {Boolean} disableAnim
106503      * True to disable animations when showing the month picker.
106504      */
106505     disableAnim: false,
106506
106507     /**
106508      * @cfg {String} [baseCls='x-datepicker']
106509      * The base CSS class to apply to this components element.
106510      */
106511     baseCls: Ext.baseCSSPrefix + 'datepicker',
106512
106513     /**
106514      * @cfg {String} [selectedCls='x-datepicker-selected']
106515      * The class to apply to the selected cell.
106516      */
106517
106518     /**
106519      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
106520      * The class to apply to disabled cells.
106521      */
106522
106523     /**
106524      * @cfg {String} longDayFormat
106525      * The format for displaying a date in a longer format.
106526      */
106527     longDayFormat: 'F d, Y',
106528
106529     /**
106530      * @cfg {Object} keyNavConfig
106531      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
106532      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
106533      * object will replace default handlers of the same name.
106534      */
106535
106536     /**
106537      * @cfg {Boolean} focusOnShow
106538      * True to automatically focus the picker on show.
106539      */
106540     focusOnShow: false,
106541
106542     // private
106543     // Set by other components to stop the picker focus being updated when the value changes.
106544     focusOnSelect: true,
106545
106546     width: 178,
106547
106548     // default value used to initialise each date in the DatePicker
106549     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
106550     initHour: 12, // 24-hour format
106551
106552     numDays: 42,
106553
106554     // private, inherit docs
106555     initComponent : function() {
106556         var me = this,
106557             clearTime = Ext.Date.clearTime;
106558
106559         me.selectedCls = me.baseCls + '-selected';
106560         me.disabledCellCls = me.baseCls + '-disabled';
106561         me.prevCls = me.baseCls + '-prevday';
106562         me.activeCls = me.baseCls + '-active';
106563         me.nextCls = me.baseCls + '-prevday';
106564         me.todayCls = me.baseCls + '-today';
106565         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
106566         this.callParent();
106567
106568         me.value = me.value ?
106569                  clearTime(me.value, true) : clearTime(new Date());
106570
106571         me.addEvents(
106572             /**
106573              * @event select
106574              * Fires when a date is selected
106575              * @param {Ext.picker.Date} this DatePicker
106576              * @param {Date} date The selected date
106577              */
106578             'select'
106579         );
106580
106581         me.initDisabledDays();
106582     },
106583
106584     // private, inherit docs
106585     onRender : function(container, position){
106586         /*
106587          * days array for looping through 6 full weeks (6 weeks * 7 days)
106588          * Note that we explicitly force the size here so the template creates
106589          * all the appropriate cells.
106590          */
106591
106592         var me = this,
106593             days = new Array(me.numDays),
106594             today = Ext.Date.format(new Date(), me.format);
106595
106596         Ext.applyIf(me, {
106597             renderData: {}
106598         });
106599
106600         Ext.apply(me.renderData, {
106601             dayNames: me.dayNames,
106602             ariaTitle: me.ariaTitle,
106603             value: me.value,
106604             showToday: me.showToday,
106605             prevText: me.prevText,
106606             nextText: me.nextText,
106607             days: days
106608         });
106609         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
106610
106611         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
106612
106613         this.callParent(arguments);
106614         me.el.unselectable();
106615
106616         me.cells = me.eventEl.select('tbody td');
106617         me.textNodes = me.eventEl.query('tbody td span');
106618
106619         me.monthBtn = Ext.create('Ext.button.Split', {
106620             text: '',
106621             tooltip: me.monthYearText,
106622             renderTo: me.middleBtnEl
106623         });
106624         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
106625
106626
106627         me.todayBtn = Ext.create('Ext.button.Button', {
106628             renderTo: me.footerEl,
106629             text: Ext.String.format(me.todayText, today),
106630             tooltip: Ext.String.format(me.todayTip, today),
106631             handler: me.selectToday,
106632             scope: me
106633         });
106634     },
106635
106636     // private, inherit docs
106637     initEvents: function(){
106638         var me = this,
106639             eDate = Ext.Date,
106640             day = eDate.DAY;
106641
106642         this.callParent();
106643
106644         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106645             handler: me.showPrevMonth,
106646             scope: me,
106647             preventDefault: true,
106648             stopDefault: true
106649         });
106650
106651         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106652             handler: me.showNextMonth,
106653             scope: me,
106654             preventDefault:true,
106655             stopDefault:true
106656         });
106657
106658         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
106659             scope: me,
106660             'left' : function(e){
106661                 if(e.ctrlKey){
106662                     me.showPrevMonth();
106663                 }else{
106664                     me.update(eDate.add(me.activeDate, day, -1));
106665                 }
106666             },
106667
106668             'right' : function(e){
106669                 if(e.ctrlKey){
106670                     me.showNextMonth();
106671                 }else{
106672                     me.update(eDate.add(me.activeDate, day, 1));
106673                 }
106674             },
106675
106676             'up' : function(e){
106677                 if(e.ctrlKey){
106678                     me.showNextYear();
106679                 }else{
106680                     me.update(eDate.add(me.activeDate, day, -7));
106681                 }
106682             },
106683
106684             'down' : function(e){
106685                 if(e.ctrlKey){
106686                     me.showPrevYear();
106687                 }else{
106688                     me.update(eDate.add(me.activeDate, day, 7));
106689                 }
106690             },
106691             'pageUp' : me.showNextMonth,
106692             'pageDown' : me.showPrevMonth,
106693             'enter' : function(e){
106694                 e.stopPropagation();
106695                 return true;
106696             }
106697         }, me.keyNavConfig));
106698
106699         if(me.showToday){
106700             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
106701         }
106702         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
106703         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
106704         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
106705         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
106706         me.update(me.value);
106707     },
106708
106709     /**
106710      * Setup the disabled dates regex based on config options
106711      * @private
106712      */
106713     initDisabledDays : function(){
106714         var me = this,
106715             dd = me.disabledDates,
106716             re = '(?:',
106717             len;
106718
106719         if(!me.disabledDatesRE && dd){
106720                 len = dd.length - 1;
106721
106722             Ext.each(dd, function(d, i){
106723                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
106724                 if(i != len){
106725                     re += '|';
106726                 }
106727             }, me);
106728             me.disabledDatesRE = new RegExp(re + ')');
106729         }
106730     },
106731
106732     /**
106733      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
106734      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
106735      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
106736      * @return {Ext.picker.Date} this
106737      */
106738     setDisabledDates : function(dd){
106739         var me = this;
106740
106741         if(Ext.isArray(dd)){
106742             me.disabledDates = dd;
106743             me.disabledDatesRE = null;
106744         }else{
106745             me.disabledDatesRE = dd;
106746         }
106747         me.initDisabledDays();
106748         me.update(me.value, true);
106749         return me;
106750     },
106751
106752     /**
106753      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
106754      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
106755      * on supported values.
106756      * @return {Ext.picker.Date} this
106757      */
106758     setDisabledDays : function(dd){
106759         this.disabledDays = dd;
106760         return this.update(this.value, true);
106761     },
106762
106763     /**
106764      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
106765      * @param {Date} value The minimum date that can be selected
106766      * @return {Ext.picker.Date} this
106767      */
106768     setMinDate : function(dt){
106769         this.minDate = dt;
106770         return this.update(this.value, true);
106771     },
106772
106773     /**
106774      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
106775      * @param {Date} value The maximum date that can be selected
106776      * @return {Ext.picker.Date} this
106777      */
106778     setMaxDate : function(dt){
106779         this.maxDate = dt;
106780         return this.update(this.value, true);
106781     },
106782
106783     /**
106784      * Sets the value of the date field
106785      * @param {Date} value The date to set
106786      * @return {Ext.picker.Date} this
106787      */
106788     setValue : function(value){
106789         this.value = Ext.Date.clearTime(value, true);
106790         return this.update(this.value);
106791     },
106792
106793     /**
106794      * Gets the current selected value of the date field
106795      * @return {Date} The selected date
106796      */
106797     getValue : function(){
106798         return this.value;
106799     },
106800
106801     // private
106802     focus : function(){
106803         this.update(this.activeDate);
106804     },
106805
106806     // private, inherit docs
106807     onEnable: function(){
106808         this.callParent();
106809         this.setDisabledStatus(false);
106810         this.update(this.activeDate);
106811
106812     },
106813
106814     // private, inherit docs
106815     onDisable : function(){
106816         this.callParent();
106817         this.setDisabledStatus(true);
106818     },
106819
106820     /**
106821      * Set the disabled state of various internal components
106822      * @private
106823      * @param {Boolean} disabled
106824      */
106825     setDisabledStatus : function(disabled){
106826         var me = this;
106827
106828         me.keyNav.setDisabled(disabled);
106829         me.prevRepeater.setDisabled(disabled);
106830         me.nextRepeater.setDisabled(disabled);
106831         if (me.showToday) {
106832             me.todayKeyListener.setDisabled(disabled);
106833             me.todayBtn.setDisabled(disabled);
106834         }
106835     },
106836
106837     /**
106838      * Get the current active date.
106839      * @private
106840      * @return {Date} The active date
106841      */
106842     getActive: function(){
106843         return this.activeDate || this.value;
106844     },
106845
106846     /**
106847      * Run any animation required to hide/show the month picker.
106848      * @private
106849      * @param {Boolean} isHide True if it's a hide operation
106850      */
106851     runAnimation: function(isHide){
106852         var picker = this.monthPicker,
106853             options = {
106854                 duration: 200,
106855                 callback: function(){
106856                     if (isHide) {
106857                         picker.hide();
106858                     } else {
106859                         picker.show();
106860                     }
106861                 }
106862             };
106863
106864         if (isHide) {
106865             picker.el.slideOut('t', options);
106866         } else {
106867             picker.el.slideIn('t', options);
106868         }
106869     },
106870
106871     /**
106872      * Hides the month picker, if it's visible.
106873      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106874      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106875      * whether to animate or not.
106876      * @return {Ext.picker.Date} this
106877      */
106878     hideMonthPicker : function(animate){
106879         var me = this,
106880             picker = me.monthPicker;
106881
106882         if (picker) {
106883             if (me.shouldAnimate(animate)) {
106884                 me.runAnimation(true);
106885             } else {
106886                 picker.hide();
106887             }
106888         }
106889         return me;
106890     },
106891
106892     /**
106893      * Show the month picker
106894      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106895      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106896      * whether to animate or not.
106897      * @return {Ext.picker.Date} this
106898      */
106899     showMonthPicker : function(animate){
106900         var me = this,
106901             picker;
106902         
106903         if (me.rendered && !me.disabled) {
106904             picker = me.createMonthPicker();
106905             picker.setValue(me.getActive());
106906             picker.setSize(me.getSize());
106907             picker.setPosition(-1, -1);
106908             if (me.shouldAnimate(animate)) {
106909                 me.runAnimation(false);
106910             } else {
106911                 picker.show();
106912             }
106913         }
106914         return me;
106915     },
106916     
106917     /**
106918      * Checks whether a hide/show action should animate
106919      * @private
106920      * @param {Boolean} [animate] A possible animation value
106921      * @return {Boolean} Whether to animate the action
106922      */
106923     shouldAnimate: function(animate){
106924         return Ext.isDefined(animate) ? animate : !this.disableAnim;
106925     },
106926
106927     /**
106928      * Create the month picker instance
106929      * @private
106930      * @return {Ext.picker.Month} picker
106931      */
106932     createMonthPicker: function(){
106933         var me = this,
106934             picker = me.monthPicker;
106935
106936         if (!picker) {
106937             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
106938                 renderTo: me.el,
106939                 floating: true,
106940                 shadow: false,
106941                 small: me.showToday === false,
106942                 listeners: {
106943                     scope: me,
106944                     cancelclick: me.onCancelClick,
106945                     okclick: me.onOkClick,
106946                     yeardblclick: me.onOkClick,
106947                     monthdblclick: me.onOkClick
106948                 }
106949             });
106950             if (!me.disableAnim) {
106951                 // hide the element if we're animating to prevent an initial flicker
106952                 picker.el.setStyle('display', 'none');
106953             }
106954             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
106955         }
106956         return picker;
106957     },
106958
106959     /**
106960      * Respond to an ok click on the month picker
106961      * @private
106962      */
106963     onOkClick: function(picker, value){
106964         var me = this,
106965             month = value[0],
106966             year = value[1],
106967             date = new Date(year, month, me.getActive().getDate());
106968
106969         if (date.getMonth() !== month) {
106970             // 'fix' the JS rolling date conversion if needed
106971             date = new Date(year, month, 1).getLastDateOfMonth();
106972         }
106973         me.update(date);
106974         me.hideMonthPicker();
106975     },
106976
106977     /**
106978      * Respond to a cancel click on the month picker
106979      * @private
106980      */
106981     onCancelClick: function(){
106982         this.hideMonthPicker();
106983     },
106984
106985     /**
106986      * Show the previous month.
106987      * @param {Object} e
106988      * @return {Ext.picker.Date} this
106989      */
106990     showPrevMonth : function(e){
106991         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
106992     },
106993
106994     /**
106995      * Show the next month.
106996      * @param {Object} e
106997      * @return {Ext.picker.Date} this
106998      */
106999     showNextMonth : function(e){
107000         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
107001     },
107002
107003     /**
107004      * Show the previous year.
107005      * @return {Ext.picker.Date} this
107006      */
107007     showPrevYear : function(){
107008         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
107009     },
107010
107011     /**
107012      * Show the next year.
107013      * @return {Ext.picker.Date} this
107014      */
107015     showNextYear : function(){
107016         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
107017     },
107018
107019     /**
107020      * Respond to the mouse wheel event
107021      * @private
107022      * @param {Ext.EventObject} e
107023      */
107024     handleMouseWheel : function(e){
107025         e.stopEvent();
107026         if(!this.disabled){
107027             var delta = e.getWheelDelta();
107028             if(delta > 0){
107029                 this.showPrevMonth();
107030             } else if(delta < 0){
107031                 this.showNextMonth();
107032             }
107033         }
107034     },
107035
107036     /**
107037      * Respond to a date being clicked in the picker
107038      * @private
107039      * @param {Ext.EventObject} e
107040      * @param {HTMLElement} t
107041      */
107042     handleDateClick : function(e, t){
107043         var me = this,
107044             handler = me.handler;
107045
107046         e.stopEvent();
107047         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
107048             me.cancelFocus = me.focusOnSelect === false;
107049             me.setValue(new Date(t.dateValue));
107050             delete me.cancelFocus;
107051             me.fireEvent('select', me, me.value);
107052             if (handler) {
107053                 handler.call(me.scope || me, me, me.value);
107054             }
107055             // event handling is turned off on hide
107056             // when we are using the picker in a field
107057             // therefore onSelect comes AFTER the select
107058             // event.
107059             me.onSelect();
107060         }
107061     },
107062
107063     /**
107064      * Perform any post-select actions
107065      * @private
107066      */
107067     onSelect: function() {
107068         if (this.hideOnSelect) {
107069              this.hide();
107070          }
107071     },
107072
107073     /**
107074      * Sets the current value to today.
107075      * @return {Ext.picker.Date} this
107076      */
107077     selectToday : function(){
107078         var me = this,
107079             btn = me.todayBtn,
107080             handler = me.handler;
107081
107082         if(btn && !btn.disabled){
107083             me.setValue(Ext.Date.clearTime(new Date()));
107084             me.fireEvent('select', me, me.value);
107085             if (handler) {
107086                 handler.call(me.scope || me, me, me.value);
107087             }
107088             me.onSelect();
107089         }
107090         return me;
107091     },
107092
107093     /**
107094      * Update the selected cell
107095      * @private
107096      * @param {Date} date The new date
107097      * @param {Date} active The active date
107098      */
107099     selectedUpdate: function(date, active){
107100         var me = this,
107101             t = date.getTime(),
107102             cells = me.cells,
107103             cls = me.selectedCls;
107104
107105         cells.removeCls(cls);
107106         cells.each(function(c){
107107             if (c.dom.firstChild.dateValue == t) {
107108                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
107109                 c.addCls(cls);
107110                 if(me.isVisible() && !me.cancelFocus){
107111                     Ext.fly(c.dom.firstChild).focus(50);
107112                 }
107113                 return false;
107114             }
107115         }, this);
107116     },
107117
107118     /**
107119      * Update the contents of the picker for a new month
107120      * @private
107121      * @param {Date} date The new date
107122      * @param {Date} active The active date
107123      */
107124     fullUpdate: function(date, active){
107125         var me = this,
107126             cells = me.cells.elements,
107127             textNodes = me.textNodes,
107128             disabledCls = me.disabledCellCls,
107129             eDate = Ext.Date,
107130             i = 0,
107131             extraDays = 0,
107132             visible = me.isVisible(),
107133             sel = +eDate.clearTime(date, true),
107134             today = +eDate.clearTime(new Date()),
107135             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
107136             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
107137             ddMatch = me.disabledDatesRE,
107138             ddText = me.disabledDatesText,
107139             ddays = me.disabledDays ? me.disabledDays.join('') : false,
107140             ddaysText = me.disabledDaysText,
107141             format = me.format,
107142             days = eDate.getDaysInMonth(date),
107143             firstOfMonth = eDate.getFirstDateOfMonth(date),
107144             startingPos = firstOfMonth.getDay() - me.startDay,
107145             previousMonth = eDate.add(date, eDate.MONTH, -1),
107146             longDayFormat = me.longDayFormat,
107147             prevStart,
107148             current,
107149             disableToday,
107150             tempDate,
107151             setCellClass,
107152             html,
107153             cls,
107154             formatValue,
107155             value;
107156
107157         if (startingPos < 0) {
107158             startingPos += 7;
107159         }
107160
107161         days += startingPos;
107162         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
107163         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
107164
107165         if (me.showToday) {
107166             tempDate = eDate.clearTime(new Date());
107167             disableToday = (tempDate < min || tempDate > max ||
107168                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
107169                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
107170
107171             if (!me.disabled) {
107172                 me.todayBtn.setDisabled(disableToday);
107173                 me.todayKeyListener.setDisabled(disableToday);
107174             }
107175         }
107176
107177         setCellClass = function(cell){
107178             value = +eDate.clearTime(current, true);
107179             cell.title = eDate.format(current, longDayFormat);
107180             // store dateValue number as an expando
107181             cell.firstChild.dateValue = value;
107182             if(value == today){
107183                 cell.className += ' ' + me.todayCls;
107184                 cell.title = me.todayText;
107185             }
107186             if(value == sel){
107187                 cell.className += ' ' + me.selectedCls;
107188                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
107189                 if (visible && me.floating) {
107190                     Ext.fly(cell.firstChild).focus(50);
107191                 }
107192             }
107193             // disabling
107194             if(value < min) {
107195                 cell.className = disabledCls;
107196                 cell.title = me.minText;
107197                 return;
107198             }
107199             if(value > max) {
107200                 cell.className = disabledCls;
107201                 cell.title = me.maxText;
107202                 return;
107203             }
107204             if(ddays){
107205                 if(ddays.indexOf(current.getDay()) != -1){
107206                     cell.title = ddaysText;
107207                     cell.className = disabledCls;
107208                 }
107209             }
107210             if(ddMatch && format){
107211                 formatValue = eDate.dateFormat(current, format);
107212                 if(ddMatch.test(formatValue)){
107213                     cell.title = ddText.replace('%0', formatValue);
107214                     cell.className = disabledCls;
107215                 }
107216             }
107217         };
107218
107219         for(; i < me.numDays; ++i) {
107220             if (i < startingPos) {
107221                 html = (++prevStart);
107222                 cls = me.prevCls;
107223             } else if (i >= days) {
107224                 html = (++extraDays);
107225                 cls = me.nextCls;
107226             } else {
107227                 html = i - startingPos + 1;
107228                 cls = me.activeCls;
107229             }
107230             textNodes[i].innerHTML = html;
107231             cells[i].className = cls;
107232             current.setDate(current.getDate() + 1);
107233             setCellClass(cells[i]);
107234         }
107235
107236         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
107237     },
107238
107239     /**
107240      * Update the contents of the picker
107241      * @private
107242      * @param {Date} date The new date
107243      * @param {Boolean} forceRefresh True to force a full refresh
107244      */
107245     update : function(date, forceRefresh){
107246         var me = this,
107247             active = me.activeDate;
107248
107249         if (me.rendered) {
107250             me.activeDate = date;
107251             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
107252                 me.selectedUpdate(date, active);
107253             } else {
107254                 me.fullUpdate(date, active);
107255             }
107256         }
107257         return me;
107258     },
107259
107260     // private, inherit docs
107261     beforeDestroy : function() {
107262         var me = this;
107263
107264         if (me.rendered) {
107265             Ext.destroy(
107266                 me.todayKeyListener,
107267                 me.keyNav,
107268                 me.monthPicker,
107269                 me.monthBtn,
107270                 me.nextRepeater,
107271                 me.prevRepeater,
107272                 me.todayBtn
107273             );
107274             delete me.textNodes;
107275             delete me.cells.elements;
107276         }
107277         me.callParent();
107278     },
107279
107280     // private, inherit docs
107281     onShow: function() {
107282         this.callParent(arguments);
107283         if (this.focusOnShow) {
107284             this.focus();
107285         }
107286     }
107287 },
107288
107289 // After dependencies have loaded:
107290 function() {
107291     var proto = this.prototype;
107292
107293     proto.monthNames = Ext.Date.monthNames;
107294
107295     proto.dayNames = Ext.Date.dayNames;
107296
107297     proto.format = Ext.Date.defaultFormat;
107298 });
107299
107300 /**
107301  * @docauthor Jason Johnston <jason@sencha.com>
107302  *
107303  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
107304  * validation.
107305  *
107306  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
107307  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
107308  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
107309  *
107310  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
107311  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
107312  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
107313  *
107314  * # Example usage
107315  *
107316  *     @example
107317  *     Ext.create('Ext.form.Panel', {
107318  *         renderTo: Ext.getBody(),
107319  *         width: 300,
107320  *         bodyPadding: 10,
107321  *         title: 'Dates',
107322  *         items: [{
107323  *             xtype: 'datefield',
107324  *             anchor: '100%',
107325  *             fieldLabel: 'From',
107326  *             name: 'from_date',
107327  *             maxValue: new Date()  // limited to the current date or prior
107328  *         }, {
107329  *             xtype: 'datefield',
107330  *             anchor: '100%',
107331  *             fieldLabel: 'To',
107332  *             name: 'to_date',
107333  *             value: new Date()  // defaults to today
107334  *         }]
107335  *     });
107336  *
107337  * # Date Formats Examples
107338  *
107339  * This example shows a couple of different date format parsing scenarios. Both use custom date format
107340  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
107341  *
107342  *     @example
107343  *     Ext.create('Ext.form.Panel', {
107344  *         renderTo: Ext.getBody(),
107345  *         width: 300,
107346  *         bodyPadding: 10,
107347  *         title: 'Dates',
107348  *         items: [{
107349  *             xtype: 'datefield',
107350  *             anchor: '100%',
107351  *             fieldLabel: 'Date',
107352  *             name: 'date',
107353  *             // The value matches the format; will be parsed and displayed using that format.
107354  *             format: 'm d Y',
107355  *             value: '2 4 1978'
107356  *         }, {
107357  *             xtype: 'datefield',
107358  *             anchor: '100%',
107359  *             fieldLabel: 'Date',
107360  *             name: 'date',
107361  *             // The value does not match the format, but does match an altFormat; will be parsed
107362  *             // using the altFormat and displayed using the format.
107363  *             format: 'm d Y',
107364  *             altFormats: 'm,d,Y|m.d.Y',
107365  *             value: '2.4.1978'
107366  *         }]
107367  *     });
107368  */
107369 Ext.define('Ext.form.field.Date', {
107370     extend:'Ext.form.field.Picker',
107371     alias: 'widget.datefield',
107372     requires: ['Ext.picker.Date'],
107373     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
107374
107375     /**
107376      * @cfg {String} format
107377      * The default date format string which can be overriden for localization support. The format must be valid
107378      * according to {@link Ext.Date#parse}.
107379      */
107380     format : "m/d/Y",
107381     /**
107382      * @cfg {String} altFormats
107383      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
107384      * format.
107385      */
107386     altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",
107387     /**
107388      * @cfg {String} disabledDaysText
107389      * The tooltip to display when the date falls on a disabled day.
107390      */
107391     disabledDaysText : "Disabled",
107392     /**
107393      * @cfg {String} disabledDatesText
107394      * The tooltip text to display when the date falls on a disabled date.
107395      */
107396     disabledDatesText : "Disabled",
107397     /**
107398      * @cfg {String} minText
107399      * The error text to display when the date in the cell is before {@link #minValue}.
107400      */
107401     minText : "The date in this field must be equal to or after {0}",
107402     /**
107403      * @cfg {String} maxText
107404      * The error text to display when the date in the cell is after {@link #maxValue}.
107405      */
107406     maxText : "The date in this field must be equal to or before {0}",
107407     /**
107408      * @cfg {String} invalidText
107409      * The error text to display when the date in the field is invalid.
107410      */
107411     invalidText : "{0} is not a valid date - it must be in the format {1}",
107412     /**
107413      * @cfg {String} [triggerCls='x-form-date-trigger']
107414      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
107415      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
107416      */
107417     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
107418     /**
107419      * @cfg {Boolean} showToday
107420      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
107421      * spacebar that selects the current date.
107422      */
107423     showToday : true,
107424     /**
107425      * @cfg {Date/String} minValue
107426      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
107427      */
107428     /**
107429      * @cfg {Date/String} maxValue
107430      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
107431      */
107432     /**
107433      * @cfg {Number[]} disabledDays
107434      * An array of days to disable, 0 based. Some examples:
107435      *
107436      *     // disable Sunday and Saturday:
107437      *     disabledDays:  [0, 6]
107438      *     // disable weekdays:
107439      *     disabledDays: [1,2,3,4,5]
107440      */
107441     /**
107442      * @cfg {String[]} disabledDates
107443      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
107444      * they are very powerful. Some examples:
107445      *
107446      *     // disable these exact dates:
107447      *     disabledDates: ["03/08/2003", "09/16/2003"]
107448      *     // disable these days for every year:
107449      *     disabledDates: ["03/08", "09/16"]
107450      *     // only match the beginning (useful if you are using short years):
107451      *     disabledDates: ["^03/08"]
107452      *     // disable every day in March 2006:
107453      *     disabledDates: ["03/../2006"]
107454      *     // disable every day in every March:
107455      *     disabledDates: ["^03"]
107456      *
107457      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
107458      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
107459      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
107460      */
107461
107462     /**
107463      * @cfg {String} submitFormat
107464      * The date format string which will be submitted to the server. The format must be valid according to {@link
107465      * Ext.Date#parse} (defaults to {@link #format}).
107466      */
107467
107468     // in the absence of a time value, a default value of 12 noon will be used
107469     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
107470     initTime: '12', // 24 hour format
107471
107472     initTimeFormat: 'H',
107473
107474     matchFieldWidth: false,
107475     /**
107476      * @cfg {Number} startDay
107477      * Day index at which the week should begin, 0-based (defaults to Sunday)
107478      */
107479     startDay: 0,
107480
107481     initComponent : function(){
107482         var me = this,
107483             isString = Ext.isString,
107484             min, max;
107485
107486         min = me.minValue;
107487         max = me.maxValue;
107488         if(isString(min)){
107489             me.minValue = me.parseDate(min);
107490         }
107491         if(isString(max)){
107492             me.maxValue = me.parseDate(max);
107493         }
107494         me.disabledDatesRE = null;
107495         me.initDisabledDays();
107496
107497         me.callParent();
107498     },
107499
107500     initValue: function() {
107501         var me = this,
107502             value = me.value;
107503
107504         // If a String value was supplied, try to convert it to a proper Date
107505         if (Ext.isString(value)) {
107506             me.value = me.rawToValue(value);
107507         }
107508
107509         me.callParent();
107510     },
107511
107512     // private
107513     initDisabledDays : function(){
107514         if(this.disabledDates){
107515             var dd = this.disabledDates,
107516                 len = dd.length - 1,
107517                 re = "(?:";
107518
107519             Ext.each(dd, function(d, i){
107520                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
107521                 if (i !== len) {
107522                     re += '|';
107523                 }
107524             }, this);
107525             this.disabledDatesRE = new RegExp(re + ')');
107526         }
107527     },
107528
107529     /**
107530      * Replaces any existing disabled dates with new values and refreshes the Date picker.
107531      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
107532      * supported values) used to disable a pattern of dates.
107533      */
107534     setDisabledDates : function(dd){
107535         var me = this,
107536             picker = me.picker;
107537
107538         me.disabledDates = dd;
107539         me.initDisabledDays();
107540         if (picker) {
107541             picker.setDisabledDates(me.disabledDatesRE);
107542         }
107543     },
107544
107545     /**
107546      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
107547      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
107548      * supported values.
107549      */
107550     setDisabledDays : function(dd){
107551         var picker = this.picker;
107552
107553         this.disabledDays = dd;
107554         if (picker) {
107555             picker.setDisabledDays(dd);
107556         }
107557     },
107558
107559     /**
107560      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
107561      * @param {Date} value The minimum date that can be selected
107562      */
107563     setMinValue : function(dt){
107564         var me = this,
107565             picker = me.picker,
107566             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107567
107568         me.minValue = minValue;
107569         if (picker) {
107570             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
107571             picker.setMinDate(minValue);
107572         }
107573     },
107574
107575     /**
107576      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
107577      * @param {Date} value The maximum date that can be selected
107578      */
107579     setMaxValue : function(dt){
107580         var me = this,
107581             picker = me.picker,
107582             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107583
107584         me.maxValue = maxValue;
107585         if (picker) {
107586             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
107587             picker.setMaxDate(maxValue);
107588         }
107589     },
107590
107591     /**
107592      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
107593      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
107594      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
107595      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
107596      * @param {Object} [value] The value to get errors for (defaults to the current field value)
107597      * @return {String[]} All validation errors for this field
107598      */
107599     getErrors: function(value) {
107600         var me = this,
107601             format = Ext.String.format,
107602             clearTime = Ext.Date.clearTime,
107603             errors = me.callParent(arguments),
107604             disabledDays = me.disabledDays,
107605             disabledDatesRE = me.disabledDatesRE,
107606             minValue = me.minValue,
107607             maxValue = me.maxValue,
107608             len = disabledDays ? disabledDays.length : 0,
107609             i = 0,
107610             svalue,
107611             fvalue,
107612             day,
107613             time;
107614
107615         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
107616
107617         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
107618              return errors;
107619         }
107620
107621         svalue = value;
107622         value = me.parseDate(value);
107623         if (!value) {
107624             errors.push(format(me.invalidText, svalue, me.format));
107625             return errors;
107626         }
107627
107628         time = value.getTime();
107629         if (minValue && time < clearTime(minValue).getTime()) {
107630             errors.push(format(me.minText, me.formatDate(minValue)));
107631         }
107632
107633         if (maxValue && time > clearTime(maxValue).getTime()) {
107634             errors.push(format(me.maxText, me.formatDate(maxValue)));
107635         }
107636
107637         if (disabledDays) {
107638             day = value.getDay();
107639
107640             for(; i < len; i++) {
107641                 if (day === disabledDays[i]) {
107642                     errors.push(me.disabledDaysText);
107643                     break;
107644                 }
107645             }
107646         }
107647
107648         fvalue = me.formatDate(value);
107649         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
107650             errors.push(format(me.disabledDatesText, fvalue));
107651         }
107652
107653         return errors;
107654     },
107655
107656     rawToValue: function(rawValue) {
107657         return this.parseDate(rawValue) || rawValue || null;
107658     },
107659
107660     valueToRaw: function(value) {
107661         return this.formatDate(this.parseDate(value));
107662     },
107663
107664     /**
107665      * @method setValue
107666      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
107667      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
107668      * format used is "m/d/Y").
107669      *
107670      * Usage:
107671      *
107672      *     //All of these calls set the same date value (May 4, 2006)
107673      *
107674      *     //Pass a date object:
107675      *     var dt = new Date('5/4/2006');
107676      *     dateField.setValue(dt);
107677      *
107678      *     //Pass a date string (default format):
107679      *     dateField.setValue('05/04/2006');
107680      *
107681      *     //Pass a date string (custom format):
107682      *     dateField.format = 'Y-m-d';
107683      *     dateField.setValue('2006-05-04');
107684      *
107685      * @param {String/Date} date The date or valid date string
107686      * @return {Ext.form.field.Date} this
107687      */
107688
107689     /**
107690      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
107691      * @param {String} value The value to attempt to parse
107692      * @param {String} format A valid date format (see {@link Ext.Date#parse})
107693      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
107694      */
107695     safeParse : function(value, format) {
107696         var me = this,
107697             utilDate = Ext.Date,
107698             parsedDate,
107699             result = null;
107700
107701         if (utilDate.formatContainsHourInfo(format)) {
107702             // if parse format contains hour information, no DST adjustment is necessary
107703             result = utilDate.parse(value, format);
107704         } else {
107705             // set time to 12 noon, then clear the time
107706             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
107707             if (parsedDate) {
107708                 result = utilDate.clearTime(parsedDate);
107709             }
107710         }
107711         return result;
107712     },
107713
107714     // @private
107715     getSubmitValue: function() {
107716         var format = this.submitFormat || this.format,
107717             value = this.getValue();
107718
107719         return value ? Ext.Date.format(value, format) : '';
107720     },
107721
107722     /**
107723      * @private
107724      */
107725     parseDate : function(value) {
107726         if(!value || Ext.isDate(value)){
107727             return value;
107728         }
107729
107730         var me = this,
107731             val = me.safeParse(value, me.format),
107732             altFormats = me.altFormats,
107733             altFormatsArray = me.altFormatsArray,
107734             i = 0,
107735             len;
107736
107737         if (!val && altFormats) {
107738             altFormatsArray = altFormatsArray || altFormats.split('|');
107739             len = altFormatsArray.length;
107740             for (; i < len && !val; ++i) {
107741                 val = me.safeParse(value, altFormatsArray[i]);
107742             }
107743         }
107744         return val;
107745     },
107746
107747     // private
107748     formatDate : function(date){
107749         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
107750     },
107751
107752     createPicker: function() {
107753         var me = this,
107754             format = Ext.String.format;
107755
107756         return Ext.create('Ext.picker.Date', {
107757             pickerField: me,
107758             ownerCt: me.ownerCt,
107759             renderTo: document.body,
107760             floating: true,
107761             hidden: true,
107762             focusOnShow: true,
107763             minDate: me.minValue,
107764             maxDate: me.maxValue,
107765             disabledDatesRE: me.disabledDatesRE,
107766             disabledDatesText: me.disabledDatesText,
107767             disabledDays: me.disabledDays,
107768             disabledDaysText: me.disabledDaysText,
107769             format: me.format,
107770             showToday: me.showToday,
107771             startDay: me.startDay,
107772             minText: format(me.minText, me.formatDate(me.minValue)),
107773             maxText: format(me.maxText, me.formatDate(me.maxValue)),
107774             listeners: {
107775                 scope: me,
107776                 select: me.onSelect
107777             },
107778             keyNavConfig: {
107779                 esc: function() {
107780                     me.collapse();
107781                 }
107782             }
107783         });
107784     },
107785
107786     onSelect: function(m, d) {
107787         var me = this;
107788
107789         me.setValue(d);
107790         me.fireEvent('select', me, d);
107791         me.collapse();
107792     },
107793
107794     /**
107795      * @private
107796      * Sets the Date picker's value to match the current field value when expanding.
107797      */
107798     onExpand: function() {
107799         var value = this.getValue();
107800         this.picker.setValue(Ext.isDate(value) ? value : new Date());
107801     },
107802
107803     /**
107804      * @private
107805      * Focuses the field when collapsing the Date picker.
107806      */
107807     onCollapse: function() {
107808         this.focus(false, 60);
107809     },
107810
107811     // private
107812     beforeBlur : function(){
107813         var me = this,
107814             v = me.parseDate(me.getRawValue()),
107815             focusTask = me.focusTask;
107816
107817         if (focusTask) {
107818             focusTask.cancel();
107819         }
107820
107821         if (v) {
107822             me.setValue(v);
107823         }
107824     }
107825
107826     /**
107827      * @hide
107828      * @cfg {Boolean} grow
107829      */
107830     /**
107831      * @hide
107832      * @cfg {Number} growMin
107833      */
107834     /**
107835      * @hide
107836      * @cfg {Number} growMax
107837      */
107838     /**
107839      * @hide
107840      * @method autoSize
107841      */
107842 });
107843
107844 /**
107845  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
107846  * value from a form's {@link Ext.form.Basic#load loaded data} but do not want to allow the user to edit or submit that
107847  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
107848  * to be rendered.
107849  *
107850  * If you have more complex content, or need to include components within the displayed content, also consider using a
107851  * {@link Ext.form.FieldContainer} instead.
107852  *
107853  * Example:
107854  *
107855  *     @example
107856  *     Ext.create('Ext.form.Panel', {
107857  *         renderTo: Ext.getBody(),
107858  *         width: 175,
107859  *         height: 120,
107860  *         bodyPadding: 10,
107861  *         title: 'Final Score',
107862  *         items: [{
107863  *             xtype: 'displayfield',
107864  *             fieldLabel: 'Home',
107865  *             name: 'home_score',
107866  *             value: '10'
107867  *         }, {
107868  *             xtype: 'displayfield',
107869  *             fieldLabel: 'Visitor',
107870  *             name: 'visitor_score',
107871  *             value: '11'
107872  *         }],
107873  *         buttons: [{
107874  *             text: 'Update',
107875  *         }]
107876  *     });
107877  */
107878 Ext.define('Ext.form.field.Display', {
107879     extend:'Ext.form.field.Base',
107880     alias: 'widget.displayfield',
107881     requires: ['Ext.util.Format', 'Ext.XTemplate'],
107882     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
107883     fieldSubTpl: [
107884         '<div id="{id}" class="{fieldCls}"></div>',
107885         {
107886             compiled: true,
107887             disableFormats: true
107888         }
107889     ],
107890
107891     /**
107892      * @cfg {String} [fieldCls="x-form-display-field"]
107893      * The default CSS class for the field.
107894      */
107895     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
107896
107897     /**
107898      * @cfg {Boolean} htmlEncode
107899      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
107900      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
107901      */
107902     htmlEncode: false,
107903
107904     validateOnChange: false,
107905
107906     initEvents: Ext.emptyFn,
107907
107908     submitValue: false,
107909
107910     isValid: function() {
107911         return true;
107912     },
107913
107914     validate: function() {
107915         return true;
107916     },
107917
107918     getRawValue: function() {
107919         return this.rawValue;
107920     },
107921
107922     setRawValue: function(value) {
107923         var me = this;
107924         value = Ext.value(value, '');
107925         me.rawValue = value;
107926         if (me.rendered) {
107927             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
107928         }
107929         return value;
107930     },
107931
107932     // private
107933     getContentTarget: function() {
107934         return this.inputEl;
107935     }
107936
107937     /**
107938      * @cfg {String} inputType
107939      * @hide
107940      */
107941     /**
107942      * @cfg {Boolean} disabled
107943      * @hide
107944      */
107945     /**
107946      * @cfg {Boolean} readOnly
107947      * @hide
107948      */
107949     /**
107950      * @cfg {Boolean} validateOnChange
107951      * @hide
107952      */
107953     /**
107954      * @cfg {Number} checkChangeEvents
107955      * @hide
107956      */
107957     /**
107958      * @cfg {Number} checkChangeBuffer
107959      * @hide
107960      */
107961 });
107962
107963 /**
107964  * @docauthor Jason Johnston <jason@sencha.com>
107965  *
107966  * A file upload field which has custom styling and allows control over the button text and other
107967  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
107968  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
107969  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
107970  *
107971  * Because there is no secure cross-browser way to programmatically set the value of a file input,
107972  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
107973  * a value that is browser-dependent; some have just the file name, some have a full path, some use
107974  * a fake path.
107975  *
107976  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
107977  * {@link Ext.form.Basic#hasUpload} for details.
107978  *
107979  * # Example Usage
107980  *
107981  *     @example
107982  *     Ext.create('Ext.form.Panel', {
107983  *         title: 'Upload a Photo',
107984  *         width: 400,
107985  *         bodyPadding: 10,
107986  *         frame: true,
107987  *         renderTo: Ext.getBody(),
107988  *         items: [{
107989  *             xtype: 'filefield',
107990  *             name: 'photo',
107991  *             fieldLabel: 'Photo',
107992  *             labelWidth: 50,
107993  *             msgTarget: 'side',
107994  *             allowBlank: false,
107995  *             anchor: '100%',
107996  *             buttonText: 'Select Photo...'
107997  *         }],
107998  *
107999  *         buttons: [{
108000  *             text: 'Upload',
108001  *             handler: function() {
108002  *                 var form = this.up('form').getForm();
108003  *                 if(form.isValid()){
108004  *                     form.submit({
108005  *                         url: 'photo-upload.php',
108006  *                         waitMsg: 'Uploading your photo...',
108007  *                         success: function(fp, o) {
108008  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
108009  *                         }
108010  *                     });
108011  *                 }
108012  *             }
108013  *         }]
108014  *     });
108015  */
108016 Ext.define("Ext.form.field.File", {
108017     extend: 'Ext.form.field.Text',
108018     alias: ['widget.filefield', 'widget.fileuploadfield'],
108019     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
108020     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
108021
108022     /**
108023      * @cfg {String} buttonText
108024      * The button text to display on the upload button. Note that if you supply a value for
108025      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
108026      */
108027     buttonText: 'Browse...',
108028
108029     /**
108030      * @cfg {Boolean} buttonOnly
108031      * True to display the file upload field as a button with no visible text field. If true, all
108032      * inherited Text members will still be available.
108033      */
108034     buttonOnly: false,
108035
108036     /**
108037      * @cfg {Number} buttonMargin
108038      * The number of pixels of space reserved between the button and the text field. Note that this only
108039      * applies if {@link #buttonOnly} = false.
108040      */
108041     buttonMargin: 3,
108042
108043     /**
108044      * @cfg {Object} buttonConfig
108045      * A standard {@link Ext.button.Button} config object.
108046      */
108047
108048     /**
108049      * @event change
108050      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
108051      * file selection dialog.
108052      * @param {Ext.ux.form.FileUploadField} this
108053      * @param {String} value The file value returned by the underlying file input field
108054      */
108055
108056     /**
108057      * @property {Ext.Element} fileInputEl
108058      * A reference to the invisible file input element created for this upload field. Only populated after this
108059      * component is rendered.
108060      */
108061
108062     /**
108063      * @property {Ext.button.Button} button
108064      * A reference to the trigger Button component created for this upload field. Only populated after this component is
108065      * rendered.
108066      */
108067
108068     /**
108069      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
108070      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
108071      */
108072     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
108073
108074     /**
108075      * @cfg {Boolean} readOnly
108076      * Unlike with other form fields, the readOnly config defaults to true in File field.
108077      */
108078     readOnly: true,
108079
108080     // private
108081     componentLayout: 'filefield',
108082
108083     // private
108084     onRender: function() {
108085         var me = this,
108086             inputEl;
108087
108088         me.callParent(arguments);
108089
108090         me.createButton();
108091         me.createFileInput();
108092         
108093         // we don't create the file/button til after onRender, the initial disable() is
108094         // called in the onRender of the component.
108095         if (me.disabled) {
108096             me.disableItems();
108097         }
108098
108099         inputEl = me.inputEl;
108100         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
108101         if (me.buttonOnly) {
108102             inputEl.setDisplayed(false);
108103         }
108104     },
108105
108106     /**
108107      * @private
108108      * Creates the custom trigger Button component. The fileInput will be inserted into this.
108109      */
108110     createButton: function() {
108111         var me = this;
108112         me.button = Ext.widget('button', Ext.apply({
108113             ui: me.ui,
108114             renderTo: me.bodyEl,
108115             text: me.buttonText,
108116             cls: Ext.baseCSSPrefix + 'form-file-btn',
108117             preventDefault: false,
108118             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
108119         }, me.buttonConfig));
108120     },
108121
108122     /**
108123      * @private
108124      * Creates the file input element. It is inserted into the trigger button component, made
108125      * invisible, and floated on top of the button's other content so that it will receive the
108126      * button's clicks.
108127      */
108128     createFileInput : function() {
108129         var me = this;
108130         me.fileInputEl = me.button.el.createChild({
108131             name: me.getName(),
108132             cls: Ext.baseCSSPrefix + 'form-file-input',
108133             tag: 'input',
108134             type: 'file',
108135             size: 1
108136         }).on('change', me.onFileChange, me);
108137     },
108138
108139     /**
108140      * @private Event handler fired when the user selects a file.
108141      */
108142     onFileChange: function() {
108143         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
108144         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
108145     },
108146
108147     /**
108148      * Overridden to do nothing
108149      * @hide
108150      */
108151     setValue: Ext.emptyFn,
108152
108153     reset : function(){
108154         var me = this;
108155         if (me.rendered) {
108156             me.fileInputEl.remove();
108157             me.createFileInput();
108158             me.inputEl.dom.value = '';
108159         }
108160         me.callParent();
108161     },
108162
108163     onDisable: function(){
108164         this.callParent();
108165         this.disableItems();
108166     },
108167     
108168     disableItems: function(){
108169         var file = this.fileInputEl,
108170             button = this.button;
108171              
108172         if (file) {
108173             file.dom.disabled = true;
108174         }
108175         if (button) {
108176             button.disable();
108177         }    
108178     },
108179
108180     onEnable: function(){
108181         var me = this;
108182         me.callParent();
108183         me.fileInputEl.dom.disabled = false;
108184         me.button.enable();
108185     },
108186
108187     isFileUpload: function() {
108188         return true;
108189     },
108190
108191     extractFileInput: function() {
108192         var fileInput = this.fileInputEl.dom;
108193         this.reset();
108194         return fileInput;
108195     },
108196
108197     onDestroy: function(){
108198         Ext.destroyMembers(this, 'fileInputEl', 'button');
108199         this.callParent();
108200     }
108201
108202
108203 });
108204
108205 /**
108206  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
108207  *
108208  * This creates an actual input element with type="submit" in the DOM. While its label is
108209  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
108210  * to its owner container's layout.
108211  *
108212  * Because of this, in most cases it is more convenient and less problematic to simply
108213  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
108214  * {@link Ext.form.Basic#submit submitting the form}.
108215  *
108216  * Example:
108217  *
108218  *     new Ext.form.Panel({
108219  *         title: 'My Form',
108220  *         items: [{
108221  *             xtype: 'textfield',
108222  *             fieldLabel: 'Text Field',
108223  *             name: 'text_field',
108224  *             value: 'value from text field'
108225  *         }, {
108226  *             xtype: 'hiddenfield',
108227  *             name: 'hidden_field_1',
108228  *             value: 'value from hidden field'
108229  *         }],
108230  *
108231  *         buttons: [{
108232  *             text: 'Submit',
108233  *             handler: function() {
108234  *                 this.up('form').getForm().submit({
108235  *                     params: {
108236  *                         hidden_field_2: 'value from submit call'
108237  *                     }
108238  *                 });
108239  *             }
108240  *         }]
108241  *     });
108242  *
108243  * Submitting the above form will result in three values sent to the server:
108244  *
108245  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
108246  *
108247  */
108248 Ext.define('Ext.form.field.Hidden', {
108249     extend:'Ext.form.field.Base',
108250     alias: ['widget.hiddenfield', 'widget.hidden'],
108251     alternateClassName: 'Ext.form.Hidden',
108252
108253     // private
108254     inputType : 'hidden',
108255     hideLabel: true,
108256     
108257     initComponent: function(){
108258         this.formItemCls += '-hidden';
108259         this.callParent();    
108260     },
108261     
108262     /**
108263      * @private
108264      * Override. Treat undefined and null values as equal to an empty string value.
108265      */
108266     isEqual: function(value1, value2) {
108267         return this.isEqualAsString(value1, value2);
108268     },
108269
108270     // These are all private overrides
108271     initEvents: Ext.emptyFn,
108272     setSize : Ext.emptyFn,
108273     setWidth : Ext.emptyFn,
108274     setHeight : Ext.emptyFn,
108275     setPosition : Ext.emptyFn,
108276     setPagePosition : Ext.emptyFn,
108277     markInvalid : Ext.emptyFn,
108278     clearInvalid : Ext.emptyFn
108279 });
108280
108281 /**
108282  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
108283  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
108284  *
108285  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
108286  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
108287  *
108288  *     @example
108289  *     Ext.create('Ext.picker.Color', {
108290  *         value: '993300',  // initial selected color
108291  *         renderTo: Ext.getBody(),
108292  *         listeners: {
108293  *             select: function(picker, selColor) {
108294  *                 alert(selColor);
108295  *             }
108296  *         }
108297  *     });
108298  */
108299 Ext.define('Ext.picker.Color', {
108300     extend: 'Ext.Component',
108301     requires: 'Ext.XTemplate',
108302     alias: 'widget.colorpicker',
108303     alternateClassName: 'Ext.ColorPalette',
108304
108305     /**
108306      * @cfg {String} [componentCls='x-color-picker']
108307      * The CSS class to apply to the containing element.
108308      */
108309     componentCls : Ext.baseCSSPrefix + 'color-picker',
108310
108311     /**
108312      * @cfg {String} [selectedCls='x-color-picker-selected']
108313      * The CSS class to apply to the selected element
108314      */
108315     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
108316
108317     /**
108318      * @cfg {String} value
108319      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
108320      * codes are case-sensitive.
108321      */
108322     value : null,
108323
108324     /**
108325      * @cfg {String} clickEvent
108326      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
108327      */
108328     clickEvent :'click',
108329
108330     /**
108331      * @cfg {Boolean} allowReselect
108332      * If set to true then reselecting a color that is already selected fires the {@link #select} event
108333      */
108334     allowReselect : false,
108335
108336     /**
108337      * @property {String[]} colors
108338      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
108339      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
108340      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
108341      * width setting until the box is symmetrical.
108342      *
108343      * You can override individual colors if needed:
108344      *
108345      *     var cp = new Ext.picker.Color();
108346      *     cp.colors[0] = 'FF0000';  // change the first box to red
108347      *
108348      * Or you can provide a custom array of your own for complete control:
108349      *
108350      *     var cp = new Ext.picker.Color();
108351      *     cp.colors = ['000000', '993300', '333300'];
108352      */
108353     colors : [
108354         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
108355         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
108356         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
108357         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
108358         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
108359     ],
108360
108361     /**
108362      * @cfg {Function} handler
108363      * A function that will handle the select event of this picker. The handler is passed the following parameters:
108364      *
108365      * - `picker` : ColorPicker
108366      *
108367      *   The {@link Ext.picker.Color picker}.
108368      *
108369      * - `color` : String
108370      *
108371      *   The 6-digit color hex code (without the # symbol).
108372      */
108373
108374     /**
108375      * @cfg {Object} scope
108376      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
108377      * Color picker instance.
108378      */
108379
108380     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
108381     
108382     renderTpl: [
108383         '<tpl for="colors">',
108384             '<a href="#" class="color-{.}" hidefocus="on">',
108385                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
108386             '</a>',
108387         '</tpl>'
108388     ],
108389
108390     // private
108391     initComponent : function(){
108392         var me = this;
108393
108394         me.callParent(arguments);
108395         me.addEvents(
108396             /**
108397              * @event select
108398              * Fires when a color is selected
108399              * @param {Ext.picker.Color} this
108400              * @param {String} color The 6-digit color hex code (without the # symbol)
108401              */
108402             'select'
108403         );
108404
108405         if (me.handler) {
108406             me.on('select', me.handler, me.scope, true);
108407         }
108408     },
108409
108410
108411     // private
108412     onRender : function(container, position){
108413         var me = this,
108414             clickEvent = me.clickEvent;
108415
108416         Ext.apply(me.renderData, {
108417             itemCls: me.itemCls,
108418             colors: me.colors
108419         });
108420         me.callParent(arguments);
108421
108422         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
108423         // always stop following the anchors
108424         if(clickEvent != 'click'){
108425             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
108426         }
108427     },
108428
108429     // private
108430     afterRender : function(){
108431         var me = this,
108432             value;
108433
108434         me.callParent(arguments);
108435         if (me.value) {
108436             value = me.value;
108437             me.value = null;
108438             me.select(value, true);
108439         }
108440     },
108441
108442     // private
108443     handleClick : function(event, target){
108444         var me = this,
108445             color;
108446
108447         event.stopEvent();
108448         if (!me.disabled) {
108449             color = target.className.match(me.colorRe)[1];
108450             me.select(color.toUpperCase());
108451         }
108452     },
108453
108454     /**
108455      * Selects the specified color in the picker (fires the {@link #select} event)
108456      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
108457      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
108458      */
108459     select : function(color, suppressEvent){
108460
108461         var me = this,
108462             selectedCls = me.selectedCls,
108463             value = me.value,
108464             el;
108465
108466         color = color.replace('#', '');
108467         if (!me.rendered) {
108468             me.value = color;
108469             return;
108470         }
108471
108472
108473         if (color != value || me.allowReselect) {
108474             el = me.el;
108475
108476             if (me.value) {
108477                 el.down('a.color-' + value).removeCls(selectedCls);
108478             }
108479             el.down('a.color-' + color).addCls(selectedCls);
108480             me.value = color;
108481             if (suppressEvent !== true) {
108482                 me.fireEvent('select', me, color);
108483             }
108484         }
108485     },
108486
108487     /**
108488      * Get the currently selected color value.
108489      * @return {String} value The selected value. Null if nothing is selected.
108490      */
108491     getValue: function(){
108492         return this.value || null;
108493     }
108494 });
108495
108496 /**
108497  * @private
108498  * @class Ext.layout.component.field.HtmlEditor
108499  * @extends Ext.layout.component.field.Field
108500  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
108501  * @private
108502  */
108503
108504 Ext.define('Ext.layout.component.field.HtmlEditor', {
108505     extend: 'Ext.layout.component.field.Field',
108506     alias: ['layout.htmleditor'],
108507
108508     type: 'htmleditor',
108509
108510     sizeBodyContents: function(width, height) {
108511         var me = this,
108512             owner = me.owner,
108513             bodyEl = owner.bodyEl,
108514             toolbar = owner.getToolbar(),
108515             textarea = owner.textareaEl,
108516             iframe = owner.iframeEl,
108517             editorHeight;
108518
108519         if (Ext.isNumber(width)) {
108520             width -= bodyEl.getFrameWidth('lr');
108521         }
108522         toolbar.setWidth(width);
108523         textarea.setWidth(width);
108524         iframe.setWidth(width);
108525
108526         // If fixed height, subtract toolbar height from the input area height
108527         if (Ext.isNumber(height)) {
108528             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
108529             textarea.setHeight(editorHeight);
108530             iframe.setHeight(editorHeight);
108531         }
108532     }
108533 });
108534 /**
108535  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
108536  * automatically hidden when needed. These are noted in the config options where appropriate.
108537  *
108538  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
108539  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
108540  * {@link Ext.tip.QuickTipManager#init initialized}.
108541  *
108542  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
108543  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
108544  * default iframe reloading bugs.
108545  *
108546  * # Example usage
108547  *
108548  * Simple example rendered with default options:
108549  *
108550  *     @example
108551  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108552  *     Ext.create('Ext.form.HtmlEditor', {
108553  *         width: 580,
108554  *         height: 250,
108555  *         renderTo: Ext.getBody()
108556  *     });
108557  *
108558  * Passed via xtype into a container and with custom options:
108559  *
108560  *     @example
108561  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108562  *     new Ext.panel.Panel({
108563  *         title: 'HTML Editor',
108564  *         renderTo: Ext.getBody(),
108565  *         width: 550,
108566  *         height: 250,
108567  *         frame: true,
108568  *         layout: 'fit',
108569  *         items: {
108570  *             xtype: 'htmleditor',
108571  *             enableColors: false,
108572  *             enableAlignments: false
108573  *         }
108574  *     });
108575  */
108576 Ext.define('Ext.form.field.HtmlEditor', {
108577     extend:'Ext.Component',
108578     mixins: {
108579         labelable: 'Ext.form.Labelable',
108580         field: 'Ext.form.field.Field'
108581     },
108582     alias: 'widget.htmleditor',
108583     alternateClassName: 'Ext.form.HtmlEditor',
108584     requires: [
108585         'Ext.tip.QuickTipManager',
108586         'Ext.picker.Color',
108587         'Ext.toolbar.Item',
108588         'Ext.toolbar.Toolbar',
108589         'Ext.util.Format',
108590         'Ext.layout.component.field.HtmlEditor'
108591     ],
108592
108593     fieldSubTpl: [
108594         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
108595         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
108596             'style="{size}" autocomplete="off"></textarea>',
108597         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
108598         {
108599             compiled: true,
108600             disableFormats: true
108601         }
108602     ],
108603
108604     /**
108605      * @cfg {Boolean} enableFormat
108606      * Enable the bold, italic and underline buttons
108607      */
108608     enableFormat : true,
108609     /**
108610      * @cfg {Boolean} enableFontSize
108611      * Enable the increase/decrease font size buttons
108612      */
108613     enableFontSize : true,
108614     /**
108615      * @cfg {Boolean} enableColors
108616      * Enable the fore/highlight color buttons
108617      */
108618     enableColors : true,
108619     /**
108620      * @cfg {Boolean} enableAlignments
108621      * Enable the left, center, right alignment buttons
108622      */
108623     enableAlignments : true,
108624     /**
108625      * @cfg {Boolean} enableLists
108626      * Enable the bullet and numbered list buttons. Not available in Safari.
108627      */
108628     enableLists : true,
108629     /**
108630      * @cfg {Boolean} enableSourceEdit
108631      * Enable the switch to source edit button. Not available in Safari.
108632      */
108633     enableSourceEdit : true,
108634     /**
108635      * @cfg {Boolean} enableLinks
108636      * Enable the create link button. Not available in Safari.
108637      */
108638     enableLinks : true,
108639     /**
108640      * @cfg {Boolean} enableFont
108641      * Enable font selection. Not available in Safari.
108642      */
108643     enableFont : true,
108644     /**
108645      * @cfg {String} createLinkText
108646      * The default text for the create link prompt
108647      */
108648     createLinkText : 'Please enter the URL for the link:',
108649     /**
108650      * @cfg {String} [defaultLinkValue='http://']
108651      * The default value for the create link prompt
108652      */
108653     defaultLinkValue : 'http:/'+'/',
108654     /**
108655      * @cfg {String[]} fontFamilies
108656      * An array of available font families
108657      */
108658     fontFamilies : [
108659         'Arial',
108660         'Courier New',
108661         'Tahoma',
108662         'Times New Roman',
108663         'Verdana'
108664     ],
108665     defaultFont: 'tahoma',
108666     /**
108667      * @cfg {String} defaultValue
108668      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
108669      * and IE6, â€‹(Zero-width space) in all other browsers).
108670      */
108671     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
108672
108673     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
108674
108675     componentLayout: 'htmleditor',
108676
108677     // private properties
108678     initialized : false,
108679     activated : false,
108680     sourceEditMode : false,
108681     iframePad:3,
108682     hideMode:'offsets',
108683
108684     maskOnDisable: true,
108685
108686     // private
108687     initComponent : function(){
108688         var me = this;
108689
108690         me.addEvents(
108691             /**
108692              * @event initialize
108693              * Fires when the editor is fully initialized (including the iframe)
108694              * @param {Ext.form.field.HtmlEditor} this
108695              */
108696             'initialize',
108697             /**
108698              * @event activate
108699              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
108700              * @param {Ext.form.field.HtmlEditor} this
108701              */
108702             'activate',
108703              /**
108704              * @event beforesync
108705              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
108706              * sync.
108707              * @param {Ext.form.field.HtmlEditor} this
108708              * @param {String} html
108709              */
108710             'beforesync',
108711              /**
108712              * @event beforepush
108713              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
108714              * push.
108715              * @param {Ext.form.field.HtmlEditor} this
108716              * @param {String} html
108717              */
108718             'beforepush',
108719              /**
108720              * @event sync
108721              * Fires when the textarea is updated with content from the editor iframe.
108722              * @param {Ext.form.field.HtmlEditor} this
108723              * @param {String} html
108724              */
108725             'sync',
108726              /**
108727              * @event push
108728              * Fires when the iframe editor is updated with content from the textarea.
108729              * @param {Ext.form.field.HtmlEditor} this
108730              * @param {String} html
108731              */
108732             'push',
108733              /**
108734              * @event editmodechange
108735              * Fires when the editor switches edit modes
108736              * @param {Ext.form.field.HtmlEditor} this
108737              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
108738              */
108739             'editmodechange'
108740         );
108741
108742         me.callParent(arguments);
108743
108744         // Init mixins
108745         me.initLabelable();
108746         me.initField();
108747     },
108748
108749     /**
108750      * Called when the editor creates its toolbar. Override this method if you need to
108751      * add custom toolbar buttons.
108752      * @param {Ext.form.field.HtmlEditor} editor
108753      * @protected
108754      */
108755     createToolbar : function(editor){
108756         var me = this,
108757             items = [],
108758             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
108759             baseCSSPrefix = Ext.baseCSSPrefix,
108760             fontSelectItem, toolbar, undef;
108761
108762         function btn(id, toggle, handler){
108763             return {
108764                 itemId : id,
108765                 cls : baseCSSPrefix + 'btn-icon',
108766                 iconCls: baseCSSPrefix + 'edit-'+id,
108767                 enableToggle:toggle !== false,
108768                 scope: editor,
108769                 handler:handler||editor.relayBtnCmd,
108770                 clickEvent:'mousedown',
108771                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
108772                 overflowText: editor.buttonTips[id].title || undef,
108773                 tabIndex:-1
108774             };
108775         }
108776
108777
108778         if (me.enableFont && !Ext.isSafari2) {
108779             fontSelectItem = Ext.widget('component', {
108780                 renderTpl: [
108781                     '<select id="{id}-selectEl" class="{cls}">',
108782                         '<tpl for="fonts">',
108783                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
108784                         '</tpl>',
108785                     '</select>'
108786                 ],
108787                 renderData: {
108788                     cls: baseCSSPrefix + 'font-select',
108789                     fonts: me.fontFamilies,
108790                     defaultFont: me.defaultFont
108791                 },
108792                 childEls: ['selectEl'],
108793                 onDisable: function() {
108794                     var selectEl = this.selectEl;
108795                     if (selectEl) {
108796                         selectEl.dom.disabled = true;
108797                     }
108798                     Ext.Component.superclass.onDisable.apply(this, arguments);
108799                 },
108800                 onEnable: function() {
108801                     var selectEl = this.selectEl;
108802                     if (selectEl) {
108803                         selectEl.dom.disabled = false;
108804                     }
108805                     Ext.Component.superclass.onEnable.apply(this, arguments);
108806                 }
108807             });
108808
108809             items.push(
108810                 fontSelectItem,
108811                 '-'
108812             );
108813         }
108814
108815         if (me.enableFormat) {
108816             items.push(
108817                 btn('bold'),
108818                 btn('italic'),
108819                 btn('underline')
108820             );
108821         }
108822
108823         if (me.enableFontSize) {
108824             items.push(
108825                 '-',
108826                 btn('increasefontsize', false, me.adjustFont),
108827                 btn('decreasefontsize', false, me.adjustFont)
108828             );
108829         }
108830
108831         if (me.enableColors) {
108832             items.push(
108833                 '-', {
108834                     itemId: 'forecolor',
108835                     cls: baseCSSPrefix + 'btn-icon',
108836                     iconCls: baseCSSPrefix + 'edit-forecolor',
108837                     overflowText: editor.buttonTips.forecolor.title,
108838                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
108839                     tabIndex:-1,
108840                     menu : Ext.widget('menu', {
108841                         plain: true,
108842                         items: [{
108843                             xtype: 'colorpicker',
108844                             allowReselect: true,
108845                             focus: Ext.emptyFn,
108846                             value: '000000',
108847                             plain: true,
108848                             clickEvent: 'mousedown',
108849                             handler: function(cp, color) {
108850                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108851                                 me.deferFocus();
108852                                 this.up('menu').hide();
108853                             }
108854                         }]
108855                     })
108856                 }, {
108857                     itemId: 'backcolor',
108858                     cls: baseCSSPrefix + 'btn-icon',
108859                     iconCls: baseCSSPrefix + 'edit-backcolor',
108860                     overflowText: editor.buttonTips.backcolor.title,
108861                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
108862                     tabIndex:-1,
108863                     menu : Ext.widget('menu', {
108864                         plain: true,
108865                         items: [{
108866                             xtype: 'colorpicker',
108867                             focus: Ext.emptyFn,
108868                             value: 'FFFFFF',
108869                             plain: true,
108870                             allowReselect: true,
108871                             clickEvent: 'mousedown',
108872                             handler: function(cp, color) {
108873                                 if (Ext.isGecko) {
108874                                     me.execCmd('useCSS', false);
108875                                     me.execCmd('hilitecolor', color);
108876                                     me.execCmd('useCSS', true);
108877                                     me.deferFocus();
108878                                 } else {
108879                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108880                                     me.deferFocus();
108881                                 }
108882                                 this.up('menu').hide();
108883                             }
108884                         }]
108885                     })
108886                 }
108887             );
108888         }
108889
108890         if (me.enableAlignments) {
108891             items.push(
108892                 '-',
108893                 btn('justifyleft'),
108894                 btn('justifycenter'),
108895                 btn('justifyright')
108896             );
108897         }
108898
108899         if (!Ext.isSafari2) {
108900             if (me.enableLinks) {
108901                 items.push(
108902                     '-',
108903                     btn('createlink', false, me.createLink)
108904                 );
108905             }
108906
108907             if (me.enableLists) {
108908                 items.push(
108909                     '-',
108910                     btn('insertorderedlist'),
108911                     btn('insertunorderedlist')
108912                 );
108913             }
108914             if (me.enableSourceEdit) {
108915                 items.push(
108916                     '-',
108917                     btn('sourceedit', true, function(btn){
108918                         me.toggleSourceEdit(!me.sourceEditMode);
108919                     })
108920                 );
108921             }
108922         }
108923
108924         // build the toolbar
108925         toolbar = Ext.widget('toolbar', {
108926             renderTo: me.toolbarWrap,
108927             enableOverflow: true,
108928             items: items
108929         });
108930
108931         if (fontSelectItem) {
108932             me.fontSelect = fontSelectItem.selectEl;
108933
108934             me.mon(me.fontSelect, 'change', function(){
108935                 me.relayCmd('fontname', me.fontSelect.dom.value);
108936                 me.deferFocus();
108937             });
108938         }
108939
108940         // stop form submits
108941         me.mon(toolbar.el, 'click', function(e){
108942             e.preventDefault();
108943         });
108944
108945         me.toolbar = toolbar;
108946     },
108947
108948     onDisable: function() {
108949         this.bodyEl.mask();
108950         this.callParent(arguments);
108951     },
108952
108953     onEnable: function() {
108954         this.bodyEl.unmask();
108955         this.callParent(arguments);
108956     },
108957
108958     /**
108959      * Sets the read only state of this field.
108960      * @param {Boolean} readOnly Whether the field should be read only.
108961      */
108962     setReadOnly: function(readOnly) {
108963         var me = this,
108964             textareaEl = me.textareaEl,
108965             iframeEl = me.iframeEl,
108966             body;
108967
108968         me.readOnly = readOnly;
108969
108970         if (textareaEl) {
108971             textareaEl.dom.readOnly = readOnly;
108972         }
108973
108974         if (me.initialized) {
108975             body = me.getEditorBody();
108976             if (Ext.isIE) {
108977                 // Hide the iframe while setting contentEditable so it doesn't grab focus
108978                 iframeEl.setDisplayed(false);
108979                 body.contentEditable = !readOnly;
108980                 iframeEl.setDisplayed(true);
108981             } else {
108982                 me.setDesignMode(!readOnly);
108983             }
108984             if (body) {
108985                 body.style.cursor = readOnly ? 'default' : 'text';
108986             }
108987             me.disableItems(readOnly);
108988         }
108989     },
108990
108991     /**
108992      * Called when the editor initializes the iframe with HTML contents. Override this method if you
108993      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
108994      *
108995      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
108996      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
108997      * Developer Tools to manually set the document mode, that will take precedence and override what this
108998      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
108999      * @protected
109000      */
109001     getDocMarkup: function() {
109002         var me = this,
109003             h = me.iframeEl.getHeight() - me.iframePad * 2;
109004         return Ext.String.format('<html><head><style type="text/css">body{border:0;margin:0;padding:{0}px;height:{1}px;box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;cursor:text}</style></head><body></body></html>', me.iframePad, h);
109005     },
109006
109007     // private
109008     getEditorBody: function() {
109009         var doc = this.getDoc();
109010         return doc.body || doc.documentElement;
109011     },
109012
109013     // private
109014     getDoc: function() {
109015         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
109016     },
109017
109018     // private
109019     getWin: function() {
109020         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
109021     },
109022
109023     // private
109024     onRender: function() {
109025         var me = this;
109026
109027         me.onLabelableRender();
109028
109029         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
109030
109031         me.callParent(arguments);
109032
109033         me.textareaEl.dom.value = me.value || '';
109034
109035         // Start polling for when the iframe document is ready to be manipulated
109036         me.monitorTask = Ext.TaskManager.start({
109037             run: me.checkDesignMode,
109038             scope: me,
109039             interval:100
109040         });
109041
109042         me.createToolbar(me);
109043         me.disableItems(true);
109044     },
109045
109046     initRenderTpl: function() {
109047         var me = this;
109048         if (!me.hasOwnProperty('renderTpl')) {
109049             me.renderTpl = me.getTpl('labelableRenderTpl');
109050         }
109051         return me.callParent();
109052     },
109053
109054     initRenderData: function() {
109055         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
109056     },
109057
109058     getSubTplData: function() {
109059         var cssPrefix = Ext.baseCSSPrefix;
109060         return {
109061             cmpId: this.id,
109062             id: this.getInputId(),
109063             toolbarWrapCls: cssPrefix + 'html-editor-tb',
109064             textareaCls: cssPrefix + 'hidden',
109065             iframeName: Ext.id(),
109066             iframeSrc: Ext.SSL_SECURE_URL,
109067             size: 'height:100px;'
109068         };
109069     },
109070
109071     getSubTplMarkup: function() {
109072         var data = this.getSubTplData();
109073         return this.getTpl('fieldSubTpl').apply(data);
109074     },
109075
109076     getBodyNaturalWidth: function() {
109077         return 565;
109078     },
109079
109080     initFrameDoc: function() {
109081         var me = this,
109082             doc, task;
109083
109084         Ext.TaskManager.stop(me.monitorTask);
109085
109086         doc = me.getDoc();
109087         me.win = me.getWin();
109088
109089         doc.open();
109090         doc.write(me.getDocMarkup());
109091         doc.close();
109092
109093         task = { // must defer to wait for browser to be ready
109094             run: function() {
109095                 var doc = me.getDoc();
109096                 if (doc.body || doc.readyState === 'complete') {
109097                     Ext.TaskManager.stop(task);
109098                     me.setDesignMode(true);
109099                     Ext.defer(me.initEditor, 10, me);
109100                 }
109101             },
109102             interval : 10,
109103             duration:10000,
109104             scope: me
109105         };
109106         Ext.TaskManager.start(task);
109107     },
109108
109109     checkDesignMode: function() {
109110         var me = this,
109111             doc = me.getDoc();
109112         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
109113             me.initFrameDoc();
109114         }
109115     },
109116
109117     /**
109118      * @private
109119      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
109120      */
109121     setDesignMode: function(mode) {
109122         var me = this,
109123             doc = me.getDoc();
109124         if (doc) {
109125             if (me.readOnly) {
109126                 mode = false;
109127             }
109128             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
109129         }
109130     },
109131
109132     // private
109133     getDesignMode: function() {
109134         var doc = this.getDoc();
109135         return !doc ? '' : String(doc.designMode).toLowerCase();
109136     },
109137
109138     disableItems: function(disabled) {
109139         this.getToolbar().items.each(function(item){
109140             if(item.getItemId() !== 'sourceedit'){
109141                 item.setDisabled(disabled);
109142             }
109143         });
109144     },
109145
109146     /**
109147      * Toggles the editor between standard and source edit mode.
109148      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
109149      */
109150     toggleSourceEdit: function(sourceEditMode) {
109151         var me = this,
109152             iframe = me.iframeEl,
109153             textarea = me.textareaEl,
109154             hiddenCls = Ext.baseCSSPrefix + 'hidden',
109155             btn = me.getToolbar().getComponent('sourceedit');
109156
109157         if (!Ext.isBoolean(sourceEditMode)) {
109158             sourceEditMode = !me.sourceEditMode;
109159         }
109160         me.sourceEditMode = sourceEditMode;
109161
109162         if (btn.pressed !== sourceEditMode) {
109163             btn.toggle(sourceEditMode);
109164         }
109165         if (sourceEditMode) {
109166             me.disableItems(true);
109167             me.syncValue();
109168             iframe.addCls(hiddenCls);
109169             textarea.removeCls(hiddenCls);
109170             textarea.dom.removeAttribute('tabIndex');
109171             textarea.focus();
109172         }
109173         else {
109174             if (me.initialized) {
109175                 me.disableItems(me.readOnly);
109176             }
109177             me.pushValue();
109178             iframe.removeCls(hiddenCls);
109179             textarea.addCls(hiddenCls);
109180             textarea.dom.setAttribute('tabIndex', -1);
109181             me.deferFocus();
109182         }
109183         me.fireEvent('editmodechange', me, sourceEditMode);
109184         me.doComponentLayout();
109185     },
109186
109187     // private used internally
109188     createLink : function() {
109189         var url = prompt(this.createLinkText, this.defaultLinkValue);
109190         if (url && url !== 'http:/'+'/') {
109191             this.relayCmd('createlink', url);
109192         }
109193     },
109194
109195     clearInvalid: Ext.emptyFn,
109196
109197     // docs inherit from Field
109198     setValue: function(value) {
109199         var me = this,
109200             textarea = me.textareaEl;
109201         me.mixins.field.setValue.call(me, value);
109202         if (value === null || value === undefined) {
109203             value = '';
109204         }
109205         if (textarea) {
109206             textarea.dom.value = value;
109207         }
109208         me.pushValue();
109209         return this;
109210     },
109211
109212     /**
109213      * If you need/want custom HTML cleanup, this is the method you should override.
109214      * @param {String} html The HTML to be cleaned
109215      * @return {String} The cleaned HTML
109216      * @protected
109217      */
109218     cleanHtml: function(html) {
109219         html = String(html);
109220         if (Ext.isWebKit) { // strip safari nonsense
109221             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
109222         }
109223
109224         /*
109225          * Neat little hack. Strips out all the non-digit characters from the default
109226          * value and compares it to the character code of the first character in the string
109227          * because it can cause encoding issues when posted to the server.
109228          */
109229         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
109230             html = html.substring(1);
109231         }
109232         return html;
109233     },
109234
109235     /**
109236      * Syncs the contents of the editor iframe with the textarea.
109237      * @protected
109238      */
109239     syncValue : function(){
109240         var me = this,
109241             body, html, bodyStyle, match;
109242         if (me.initialized) {
109243             body = me.getEditorBody();
109244             html = body.innerHTML;
109245             if (Ext.isWebKit) {
109246                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
109247                 match = bodyStyle.match(/text-align:(.*?);/i);
109248                 if (match && match[1]) {
109249                     html = '<div style="' + match[0] + '">' + html + '</div>';
109250                 }
109251             }
109252             html = me.cleanHtml(html);
109253             if (me.fireEvent('beforesync', me, html) !== false) {
109254                 me.textareaEl.dom.value = html;
109255                 me.fireEvent('sync', me, html);
109256             }
109257         }
109258     },
109259
109260     //docs inherit from Field
109261     getValue : function() {
109262         var me = this,
109263             value;
109264         if (!me.sourceEditMode) {
109265             me.syncValue();
109266         }
109267         value = me.rendered ? me.textareaEl.dom.value : me.value;
109268         me.value = value;
109269         return value;
109270     },
109271
109272     /**
109273      * Pushes the value of the textarea into the iframe editor.
109274      * @protected
109275      */
109276     pushValue: function() {
109277         var me = this,
109278             v;
109279         if(me.initialized){
109280             v = me.textareaEl.dom.value || '';
109281             if (!me.activated && v.length < 1) {
109282                 v = me.defaultValue;
109283             }
109284             if (me.fireEvent('beforepush', me, v) !== false) {
109285                 me.getEditorBody().innerHTML = v;
109286                 if (Ext.isGecko) {
109287                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
109288                     me.setDesignMode(false);  //toggle off first
109289                     me.setDesignMode(true);
109290                 }
109291                 me.fireEvent('push', me, v);
109292             }
109293         }
109294     },
109295
109296     // private
109297     deferFocus : function(){
109298          this.focus(false, true);
109299     },
109300
109301     getFocusEl: function() {
109302         var me = this,
109303             win = me.win;
109304         return win && !me.sourceEditMode ? win : me.textareaEl;
109305     },
109306
109307     // private
109308     initEditor : function(){
109309         //Destroying the component during/before initEditor can cause issues.
109310         try {
109311             var me = this,
109312                 dbody = me.getEditorBody(),
109313                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
109314                 doc,
109315                 fn;
109316
109317             ss['background-attachment'] = 'fixed'; // w3c
109318             dbody.bgProperties = 'fixed'; // ie
109319
109320             Ext.DomHelper.applyStyles(dbody, ss);
109321
109322             doc = me.getDoc();
109323
109324             if (doc) {
109325                 try {
109326                     Ext.EventManager.removeAll(doc);
109327                 } catch(e) {}
109328             }
109329
109330             /*
109331              * We need to use createDelegate here, because when using buffer, the delayed task is added
109332              * as a property to the function. When the listener is removed, the task is deleted from the function.
109333              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
109334              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
109335              */
109336             fn = Ext.Function.bind(me.onEditorEvent, me);
109337             Ext.EventManager.on(doc, {
109338                 mousedown: fn,
109339                 dblclick: fn,
109340                 click: fn,
109341                 keyup: fn,
109342                 buffer:100
109343             });
109344
109345             // These events need to be relayed from the inner document (where they stop
109346             // bubbling) up to the outer document. This has to be done at the DOM level so
109347             // the event reaches listeners on elements like the document body. The effected
109348             // mechanisms that depend on this bubbling behavior are listed to the right
109349             // of the event.
109350             fn = me.onRelayedEvent;
109351             Ext.EventManager.on(doc, {
109352                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
109353                 mousemove: fn, // window resize drag detection
109354                 mouseup: fn,   // window resize termination
109355                 click: fn,     // not sure, but just to be safe
109356                 dblclick: fn,  // not sure again
109357                 scope: me
109358             });
109359
109360             if (Ext.isGecko) {
109361                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
109362             }
109363             if (me.fixKeys) {
109364                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
109365             }
109366
109367             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
109368             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
109369             doc.editorInitialized = true;
109370
109371             me.initialized = true;
109372             me.pushValue();
109373             me.setReadOnly(me.readOnly);
109374             me.fireEvent('initialize', me);
109375         } catch(ex) {
109376             // ignore (why?)
109377         }
109378     },
109379
109380     // private
109381     beforeDestroy : function(){
109382         var me = this,
109383             monitorTask = me.monitorTask,
109384             doc, prop;
109385
109386         if (monitorTask) {
109387             Ext.TaskManager.stop(monitorTask);
109388         }
109389         if (me.rendered) {
109390             try {
109391                 doc = me.getDoc();
109392                 if (doc) {
109393                     Ext.EventManager.removeAll(doc);
109394                     for (prop in doc) {
109395                         if (doc.hasOwnProperty(prop)) {
109396                             delete doc[prop];
109397                         }
109398                     }
109399                 }
109400             } catch(e) {
109401                 // ignore (why?)
109402             }
109403             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
109404         }
109405         me.callParent();
109406     },
109407
109408     // private
109409     onRelayedEvent: function (event) {
109410         // relay event from the iframe's document to the document that owns the iframe...
109411
109412         var iframeEl = this.iframeEl,
109413             iframeXY = iframeEl.getXY(),
109414             eventXY = event.getXY();
109415
109416         // the event from the inner document has XY relative to that document's origin,
109417         // so adjust it to use the origin of the iframe in the outer document:
109418         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
109419
109420         event.injectEvent(iframeEl); // blame the iframe for the event...
109421
109422         event.xy = eventXY; // restore the original XY (just for safety)
109423     },
109424
109425     // private
109426     onFirstFocus : function(){
109427         var me = this,
109428             selection, range;
109429         me.activated = true;
109430         me.disableItems(me.readOnly);
109431         if (Ext.isGecko) { // prevent silly gecko errors
109432             me.win.focus();
109433             selection = me.win.getSelection();
109434             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
109435                 range = selection.getRangeAt(0);
109436                 range.selectNodeContents(me.getEditorBody());
109437                 range.collapse(true);
109438                 me.deferFocus();
109439             }
109440             try {
109441                 me.execCmd('useCSS', true);
109442                 me.execCmd('styleWithCSS', false);
109443             } catch(e) {
109444                 // ignore (why?)
109445             }
109446         }
109447         me.fireEvent('activate', me);
109448     },
109449
109450     // private
109451     adjustFont: function(btn) {
109452         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
109453             size = this.getDoc().queryCommandValue('FontSize') || '2',
109454             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
109455             isSafari;
109456         size = parseInt(size, 10);
109457         if (isPxSize) {
109458             // Safari 3 values
109459             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
109460             if (size <= 10) {
109461                 size = 1 + adjust;
109462             }
109463             else if (size <= 13) {
109464                 size = 2 + adjust;
109465             }
109466             else if (size <= 16) {
109467                 size = 3 + adjust;
109468             }
109469             else if (size <= 18) {
109470                 size = 4 + adjust;
109471             }
109472             else if (size <= 24) {
109473                 size = 5 + adjust;
109474             }
109475             else {
109476                 size = 6 + adjust;
109477             }
109478             size = Ext.Number.constrain(size, 1, 6);
109479         } else {
109480             isSafari = Ext.isSafari;
109481             if (isSafari) { // safari
109482                 adjust *= 2;
109483             }
109484             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
109485         }
109486         this.execCmd('FontSize', size);
109487     },
109488
109489     // private
109490     onEditorEvent: function(e) {
109491         this.updateToolbar();
109492     },
109493
109494     /**
109495      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
109496      * @protected
109497      */
109498     updateToolbar: function() {
109499         var me = this,
109500             btns, doc, name, fontSelect;
109501
109502         if (me.readOnly) {
109503             return;
109504         }
109505
109506         if (!me.activated) {
109507             me.onFirstFocus();
109508             return;
109509         }
109510
109511         btns = me.getToolbar().items.map;
109512         doc = me.getDoc();
109513
109514         if (me.enableFont && !Ext.isSafari2) {
109515             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
109516             fontSelect = me.fontSelect.dom;
109517             if (name !== fontSelect.value) {
109518                 fontSelect.value = name;
109519             }
109520         }
109521
109522         function updateButtons() {
109523             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
109524                 btns[name].toggle(doc.queryCommandState(name));
109525             });
109526         }
109527         if(me.enableFormat){
109528             updateButtons('bold', 'italic', 'underline');
109529         }
109530         if(me.enableAlignments){
109531             updateButtons('justifyleft', 'justifycenter', 'justifyright');
109532         }
109533         if(!Ext.isSafari2 && me.enableLists){
109534             updateButtons('insertorderedlist', 'insertunorderedlist');
109535         }
109536
109537         Ext.menu.Manager.hideAll();
109538
109539         me.syncValue();
109540     },
109541
109542     // private
109543     relayBtnCmd: function(btn) {
109544         this.relayCmd(btn.getItemId());
109545     },
109546
109547     /**
109548      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
109549      * **This should only be called after the editor is initialized.**
109550      * @param {String} cmd The Midas command
109551      * @param {String/Boolean} [value=null] The value to pass to the command
109552      */
109553     relayCmd: function(cmd, value) {
109554         Ext.defer(function() {
109555             var me = this;
109556             me.focus();
109557             me.execCmd(cmd, value);
109558             me.updateToolbar();
109559         }, 10, this);
109560     },
109561
109562     /**
109563      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
109564      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
109565      * @param {String} cmd The Midas command
109566      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
109567      */
109568     execCmd : function(cmd, value){
109569         var me = this,
109570             doc = me.getDoc(),
109571             undef;
109572         doc.execCommand(cmd, false, value === undef ? null : value);
109573         me.syncValue();
109574     },
109575
109576     // private
109577     applyCommand : function(e){
109578         if (e.ctrlKey) {
109579             var me = this,
109580                 c = e.getCharCode(), cmd;
109581             if (c > 0) {
109582                 c = String.fromCharCode(c);
109583                 switch (c) {
109584                     case 'b':
109585                         cmd = 'bold';
109586                     break;
109587                     case 'i':
109588                         cmd = 'italic';
109589                     break;
109590                     case 'u':
109591                         cmd = 'underline';
109592                     break;
109593                 }
109594                 if (cmd) {
109595                     me.win.focus();
109596                     me.execCmd(cmd);
109597                     me.deferFocus();
109598                     e.preventDefault();
109599                 }
109600             }
109601         }
109602     },
109603
109604     /**
109605      * Inserts the passed text at the current cursor position.
109606      * Note: the editor must be initialized and activated to insert text.
109607      * @param {String} text
109608      */
109609     insertAtCursor : function(text){
109610         var me = this,
109611             range;
109612
109613         if (me.activated) {
109614             me.win.focus();
109615             if (Ext.isIE) {
109616                 range = me.getDoc().selection.createRange();
109617                 if (range) {
109618                     range.pasteHTML(text);
109619                     me.syncValue();
109620                     me.deferFocus();
109621                 }
109622             }else{
109623                 me.execCmd('InsertHTML', text);
109624                 me.deferFocus();
109625             }
109626         }
109627     },
109628
109629     // private
109630     fixKeys: function() { // load time branching for fastest keydown performance
109631         if (Ext.isIE) {
109632             return function(e){
109633                 var me = this,
109634                     k = e.getKey(),
109635                     doc = me.getDoc(),
109636                     range, target;
109637                 if (k === e.TAB) {
109638                     e.stopEvent();
109639                     range = doc.selection.createRange();
109640                     if(range){
109641                         range.collapse(true);
109642                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
109643                         me.deferFocus();
109644                     }
109645                 }
109646                 else if (k === e.ENTER) {
109647                     range = doc.selection.createRange();
109648                     if (range) {
109649                         target = range.parentElement();
109650                         if(!target || target.tagName.toLowerCase() !== 'li'){
109651                             e.stopEvent();
109652                             range.pasteHTML('<br />');
109653                             range.collapse(false);
109654                             range.select();
109655                         }
109656                     }
109657                 }
109658             };
109659         }
109660
109661         if (Ext.isOpera) {
109662             return function(e){
109663                 var me = this;
109664                 if (e.getKey() === e.TAB) {
109665                     e.stopEvent();
109666                     me.win.focus();
109667                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
109668                     me.deferFocus();
109669                 }
109670             };
109671         }
109672
109673         if (Ext.isWebKit) {
109674             return function(e){
109675                 var me = this,
109676                     k = e.getKey();
109677                 if (k === e.TAB) {
109678                     e.stopEvent();
109679                     me.execCmd('InsertText','\t');
109680                     me.deferFocus();
109681                 }
109682                 else if (k === e.ENTER) {
109683                     e.stopEvent();
109684                     me.execCmd('InsertHtml','<br /><br />');
109685                     me.deferFocus();
109686                 }
109687             };
109688         }
109689
109690         return null; // not needed, so null
109691     }(),
109692
109693     /**
109694      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
109695      * @return {Ext.toolbar.Toolbar}
109696      */
109697     getToolbar : function(){
109698         return this.toolbar;
109699     },
109700
109701     /**
109702      * @property {Object} buttonTips
109703      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
109704      * that button and the value is a valid QuickTips object. For example:
109705      *
109706      *     {
109707      *         bold : {
109708      *             title: 'Bold (Ctrl+B)',
109709      *             text: 'Make the selected text bold.',
109710      *             cls: 'x-html-editor-tip'
109711      *         },
109712      *         italic : {
109713      *             title: 'Italic (Ctrl+I)',
109714      *             text: 'Make the selected text italic.',
109715      *             cls: 'x-html-editor-tip'
109716      *         },
109717      *         ...
109718      */
109719     buttonTips : {
109720         bold : {
109721             title: 'Bold (Ctrl+B)',
109722             text: 'Make the selected text bold.',
109723             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109724         },
109725         italic : {
109726             title: 'Italic (Ctrl+I)',
109727             text: 'Make the selected text italic.',
109728             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109729         },
109730         underline : {
109731             title: 'Underline (Ctrl+U)',
109732             text: 'Underline the selected text.',
109733             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109734         },
109735         increasefontsize : {
109736             title: 'Grow Text',
109737             text: 'Increase the font size.',
109738             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109739         },
109740         decreasefontsize : {
109741             title: 'Shrink Text',
109742             text: 'Decrease the font size.',
109743             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109744         },
109745         backcolor : {
109746             title: 'Text Highlight Color',
109747             text: 'Change the background color of the selected text.',
109748             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109749         },
109750         forecolor : {
109751             title: 'Font Color',
109752             text: 'Change the color of the selected text.',
109753             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109754         },
109755         justifyleft : {
109756             title: 'Align Text Left',
109757             text: 'Align text to the left.',
109758             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109759         },
109760         justifycenter : {
109761             title: 'Center Text',
109762             text: 'Center text in the editor.',
109763             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109764         },
109765         justifyright : {
109766             title: 'Align Text Right',
109767             text: 'Align text to the right.',
109768             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109769         },
109770         insertunorderedlist : {
109771             title: 'Bullet List',
109772             text: 'Start a bulleted list.',
109773             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109774         },
109775         insertorderedlist : {
109776             title: 'Numbered List',
109777             text: 'Start a numbered list.',
109778             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109779         },
109780         createlink : {
109781             title: 'Hyperlink',
109782             text: 'Make the selected text a hyperlink.',
109783             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109784         },
109785         sourceedit : {
109786             title: 'Source Edit',
109787             text: 'Switch to source editing mode.',
109788             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109789         }
109790     }
109791
109792     // hide stuff that is not compatible
109793     /**
109794      * @event blur
109795      * @hide
109796      */
109797     /**
109798      * @event change
109799      * @hide
109800      */
109801     /**
109802      * @event focus
109803      * @hide
109804      */
109805     /**
109806      * @event specialkey
109807      * @hide
109808      */
109809     /**
109810      * @cfg {String} fieldCls @hide
109811      */
109812     /**
109813      * @cfg {String} focusCls @hide
109814      */
109815     /**
109816      * @cfg {String} autoCreate @hide
109817      */
109818     /**
109819      * @cfg {String} inputType @hide
109820      */
109821     /**
109822      * @cfg {String} invalidCls @hide
109823      */
109824     /**
109825      * @cfg {String} invalidText @hide
109826      */
109827     /**
109828      * @cfg {String} msgFx @hide
109829      */
109830     /**
109831      * @cfg {Boolean} allowDomMove @hide
109832      */
109833     /**
109834      * @cfg {String} applyTo @hide
109835      */
109836     /**
109837      * @cfg {String} readOnly  @hide
109838      */
109839     /**
109840      * @cfg {String} tabIndex  @hide
109841      */
109842     /**
109843      * @method validate
109844      * @hide
109845      */
109846 });
109847
109848 /**
109849  * @docauthor Robert Dougan <rob@sencha.com>
109850  *
109851  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
109852  * at a time within a group of radios with the same name.
109853  *
109854  * # Labeling
109855  *
109856  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
109857  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
109858  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
109859  *
109860  * # Values
109861  *
109862  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
109863  *
109864  * The following values will check the radio:
109865  *
109866  * - `true`
109867  * - `'true'`
109868  * - `'1'`
109869  * - `'on'`
109870  *
109871  * Any other value will uncheck it.
109872  *
109873  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
109874  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
109875  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
109876  *
109877  * # Example usage
109878  *
109879  *     @example
109880  *     Ext.create('Ext.form.Panel', {
109881  *         title      : 'Order Form',
109882  *         width      : 300,
109883  *         bodyPadding: 10,
109884  *         renderTo   : Ext.getBody(),
109885  *         items: [
109886  *             {
109887  *                 xtype      : 'fieldcontainer',
109888  *                 fieldLabel : 'Size',
109889  *                 defaultType: 'radiofield',
109890  *                 defaults: {
109891  *                     flex: 1
109892  *                 },
109893  *                 layout: 'hbox',
109894  *                 items: [
109895  *                     {
109896  *                         boxLabel  : 'M',
109897  *                         name      : 'size',
109898  *                         inputValue: 'm',
109899  *                         id        : 'radio1'
109900  *                     }, {
109901  *                         boxLabel  : 'L',
109902  *                         name      : 'size',
109903  *                         inputValue: 'l',
109904  *                         id        : 'radio2'
109905  *                     }, {
109906  *                         boxLabel  : 'XL',
109907  *                         name      : 'size',
109908  *                         inputValue: 'xl',
109909  *                         id        : 'radio3'
109910  *                     }
109911  *                 ]
109912  *             },
109913  *             {
109914  *                 xtype      : 'fieldcontainer',
109915  *                 fieldLabel : 'Color',
109916  *                 defaultType: 'radiofield',
109917  *                 defaults: {
109918  *                     flex: 1
109919  *                 },
109920  *                 layout: 'hbox',
109921  *                 items: [
109922  *                     {
109923  *                         boxLabel  : 'Blue',
109924  *                         name      : 'color',
109925  *                         inputValue: 'blue',
109926  *                         id        : 'radio4'
109927  *                     }, {
109928  *                         boxLabel  : 'Grey',
109929  *                         name      : 'color',
109930  *                         inputValue: 'grey',
109931  *                         id        : 'radio5'
109932  *                     }, {
109933  *                         boxLabel  : 'Black',
109934  *                         name      : 'color',
109935  *                         inputValue: 'black',
109936  *                         id        : 'radio6'
109937  *                     }
109938  *                 ]
109939  *             }
109940  *         ],
109941  *         bbar: [
109942  *             {
109943  *                 text: 'Smaller Size',
109944  *                 handler: function() {
109945  *                     var radio1 = Ext.getCmp('radio1'),
109946  *                         radio2 = Ext.getCmp('radio2'),
109947  *                         radio3 = Ext.getCmp('radio3');
109948  *
109949  *                     //if L is selected, change to M
109950  *                     if (radio2.getValue()) {
109951  *                         radio1.setValue(true);
109952  *                         return;
109953  *                     }
109954  *
109955  *                     //if XL is selected, change to L
109956  *                     if (radio3.getValue()) {
109957  *                         radio2.setValue(true);
109958  *                         return;
109959  *                     }
109960  *
109961  *                     //if nothing is set, set size to S
109962  *                     radio1.setValue(true);
109963  *                 }
109964  *             },
109965  *             {
109966  *                 text: 'Larger Size',
109967  *                 handler: function() {
109968  *                     var radio1 = Ext.getCmp('radio1'),
109969  *                         radio2 = Ext.getCmp('radio2'),
109970  *                         radio3 = Ext.getCmp('radio3');
109971  *
109972  *                     //if M is selected, change to L
109973  *                     if (radio1.getValue()) {
109974  *                         radio2.setValue(true);
109975  *                         return;
109976  *                     }
109977  *
109978  *                     //if L is selected, change to XL
109979  *                     if (radio2.getValue()) {
109980  *                         radio3.setValue(true);
109981  *                         return;
109982  *                     }
109983  *
109984  *                     //if nothing is set, set size to XL
109985  *                     radio3.setValue(true);
109986  *                 }
109987  *             },
109988  *             '-',
109989  *             {
109990  *                 text: 'Select color',
109991  *                 menu: {
109992  *                     indent: false,
109993  *                     items: [
109994  *                         {
109995  *                             text: 'Blue',
109996  *                             handler: function() {
109997  *                                 var radio = Ext.getCmp('radio4');
109998  *                                 radio.setValue(true);
109999  *                             }
110000  *                         },
110001  *                         {
110002  *                             text: 'Grey',
110003  *                             handler: function() {
110004  *                                 var radio = Ext.getCmp('radio5');
110005  *                                 radio.setValue(true);
110006  *                             }
110007  *                         },
110008  *                         {
110009  *                             text: 'Black',
110010  *                             handler: function() {
110011  *                                 var radio = Ext.getCmp('radio6');
110012  *                                 radio.setValue(true);
110013  *                             }
110014  *                         }
110015  *                     ]
110016  *                 }
110017  *             }
110018  *         ]
110019  *     });
110020  */
110021 Ext.define('Ext.form.field.Radio', {
110022     extend:'Ext.form.field.Checkbox',
110023     alias: ['widget.radiofield', 'widget.radio'],
110024     alternateClassName: 'Ext.form.Radio',
110025     requires: ['Ext.form.RadioManager'],
110026
110027     isRadio: true,
110028
110029     /**
110030      * @cfg {String} uncheckedValue @hide
110031      */
110032
110033     // private
110034     inputType: 'radio',
110035     ariaRole: 'radio',
110036
110037     /**
110038      * If this radio is part of a group, it will return the selected value
110039      * @return {String}
110040      */
110041     getGroupValue: function() {
110042         var selected = this.getManager().getChecked(this.name);
110043         return selected ? selected.inputValue : null;
110044     },
110045
110046     /**
110047      * @private Handle click on the radio button
110048      */
110049     onBoxClick: function(e) {
110050         var me = this;
110051         if (!me.disabled && !me.readOnly) {
110052             this.setValue(true);
110053         }
110054     },
110055
110056     /**
110057      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
110058      * of the same name whose value is the value specified.
110059      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
110060      * @return {Ext.form.field.Radio} this
110061      */
110062     setValue: function(v) {
110063         var me = this,
110064             active;
110065
110066         if (Ext.isBoolean(v)) {
110067             me.callParent(arguments);
110068         } else {
110069             active = me.getManager().getWithValue(me.name, v).getAt(0);
110070             if (active) {
110071                 active.setValue(true);
110072             }
110073         }
110074         return me;
110075     },
110076
110077     /**
110078      * Returns the submit value for the checkbox which can be used when submitting forms.
110079      * @return {Boolean/Object} True if checked, null if not.
110080      */
110081     getSubmitValue: function() {
110082         return this.checked ? this.inputValue : null;
110083     },
110084
110085     getModelData: function() {
110086         return this.getSubmitData();
110087     },
110088
110089     // inherit docs
110090     onChange: function(newVal, oldVal) {
110091         var me = this;
110092         me.callParent(arguments);
110093
110094         if (newVal) {
110095             this.getManager().getByName(me.name).each(function(item){
110096                 if (item !== me) {
110097                     item.setValue(false);
110098                 }
110099             }, me);
110100         }
110101     },
110102
110103     // inherit docs
110104     getManager: function() {
110105         return Ext.form.RadioManager;
110106     }
110107 });
110108
110109 /**
110110  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
110111  * class to allow browsing and selection of valid times, but could also be used with other components.
110112  *
110113  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
110114  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
110115  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
110116  * config.
110117  *
110118  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
110119  *
110120  *     @example
110121  *     Ext.create('Ext.picker.Time', {
110122  *        width: 60,
110123  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
110124  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
110125  *        renderTo: Ext.getBody()
110126  *     });
110127  */
110128 Ext.define('Ext.picker.Time', {
110129     extend: 'Ext.view.BoundList',
110130     alias: 'widget.timepicker',
110131     requires: ['Ext.data.Store', 'Ext.Date'],
110132
110133     /**
110134      * @cfg {Date} minValue
110135      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
110136      * used); no parsing of String values will be done.
110137      */
110138
110139     /**
110140      * @cfg {Date} maxValue
110141      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
110142      * used); no parsing of String values will be done.
110143      */
110144
110145     /**
110146      * @cfg {Number} increment
110147      * The number of minutes between each time value in the list.
110148      */
110149     increment: 15,
110150
110151     /**
110152      * @cfg {String} format
110153      * The default time format string which can be overriden for localization support. The format must be valid
110154      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110155      * instead.
110156      */
110157     format : "g:i A",
110158
110159     /**
110160      * @hide
110161      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
110162      * an internal field name only and is not useful to change via config.
110163      */
110164     displayField: 'disp',
110165
110166     /**
110167      * @private
110168      * Year, month, and day that all times will be normalized into internally.
110169      */
110170     initDate: [2008,0,1],
110171
110172     componentCls: Ext.baseCSSPrefix + 'timepicker',
110173
110174     /**
110175      * @hide
110176      */
110177     loadMask: false,
110178
110179     initComponent: function() {
110180         var me = this,
110181             dateUtil = Ext.Date,
110182             clearTime = dateUtil.clearTime,
110183             initDate = me.initDate;
110184
110185         // Set up absolute min and max for the entire day
110186         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
110187         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
110188
110189         me.store = me.createStore();
110190         me.updateList();
110191
110192         me.callParent();
110193     },
110194
110195     /**
110196      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
110197      * fields will be used); no parsing of String values will be done.
110198      * @param {Date} value
110199      */
110200     setMinValue: function(value) {
110201         this.minValue = value;
110202         this.updateList();
110203     },
110204
110205     /**
110206      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
110207      * fields will be used); no parsing of String values will be done.
110208      * @param {Date} value
110209      */
110210     setMaxValue: function(value) {
110211         this.maxValue = value;
110212         this.updateList();
110213     },
110214
110215     /**
110216      * @private
110217      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
110218      * the time fields are significant. This makes values suitable for time comparison.
110219      * @param {Date} date
110220      */
110221     normalizeDate: function(date) {
110222         var initDate = this.initDate;
110223         date.setFullYear(initDate[0], initDate[1], initDate[2]);
110224         return date;
110225     },
110226
110227     /**
110228      * Update the list of available times in the list to be constrained within the {@link #minValue}
110229      * and {@link #maxValue}.
110230      */
110231     updateList: function() {
110232         var me = this,
110233             min = me.normalizeDate(me.minValue || me.absMin),
110234             max = me.normalizeDate(me.maxValue || me.absMax);
110235
110236         me.store.filterBy(function(record) {
110237             var date = record.get('date');
110238             return date >= min && date <= max;
110239         });
110240     },
110241
110242     /**
110243      * @private
110244      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
110245      * is loaded with all possible times, and it is later filtered to hide those times outside
110246      * the minValue/maxValue.
110247      */
110248     createStore: function() {
110249         var me = this,
110250             utilDate = Ext.Date,
110251             times = [],
110252             min = me.absMin,
110253             max = me.absMax;
110254
110255         while(min <= max){
110256             times.push({
110257                 disp: utilDate.dateFormat(min, me.format),
110258                 date: min
110259             });
110260             min = utilDate.add(min, 'mi', me.increment);
110261         }
110262
110263         return Ext.create('Ext.data.Store', {
110264             fields: ['disp', 'date'],
110265             data: times
110266         });
110267     }
110268
110269 });
110270
110271 /**
110272  * Provides a time input field with a time dropdown and automatic time validation.
110273  *
110274  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
110275  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
110276  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
110277  * the user's locale.
110278  *
110279  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
110280  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
110281  *
110282  * Example usage:
110283  *
110284  *     @example
110285  *     Ext.create('Ext.form.Panel', {
110286  *         title: 'Time Card',
110287  *         width: 300,
110288  *         bodyPadding: 10,
110289  *         renderTo: Ext.getBody(),
110290  *         items: [{
110291  *             xtype: 'timefield',
110292  *             name: 'in',
110293  *             fieldLabel: 'Time In',
110294  *             minValue: '6:00 AM',
110295  *             maxValue: '8:00 PM',
110296  *             increment: 30,
110297  *             anchor: '100%'
110298  *         }, {
110299  *             xtype: 'timefield',
110300  *             name: 'out',
110301  *             fieldLabel: 'Time Out',
110302  *             minValue: '6:00 AM',
110303  *             maxValue: '8:00 PM',
110304  *             increment: 30,
110305  *             anchor: '100%'
110306  *        }]
110307  *     });
110308  */
110309 Ext.define('Ext.form.field.Time', {
110310     extend:'Ext.form.field.Picker',
110311     alias: 'widget.timefield',
110312     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
110313     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
110314
110315     /**
110316      * @cfg {String} triggerCls
110317      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
110318      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
110319      * trigger.
110320      */
110321     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
110322
110323     /**
110324      * @cfg {Date/String} minValue
110325      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110326      * valid format -- see {@link #format} and {@link #altFormats}.
110327      */
110328
110329     /**
110330      * @cfg {Date/String} maxValue
110331      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110332      * valid format -- see {@link #format} and {@link #altFormats}.
110333      */
110334
110335     /**
110336      * @cfg {String} minText
110337      * The error text to display when the entered time is before {@link #minValue}.
110338      */
110339     minText : "The time in this field must be equal to or after {0}",
110340
110341     /**
110342      * @cfg {String} maxText
110343      * The error text to display when the entered time is after {@link #maxValue}.
110344      */
110345     maxText : "The time in this field must be equal to or before {0}",
110346
110347     /**
110348      * @cfg {String} invalidText
110349      * The error text to display when the time in the field is invalid.
110350      */
110351     invalidText : "{0} is not a valid time",
110352
110353     /**
110354      * @cfg {String} format
110355      * The default time format string which can be overriden for localization support. The format must be valid
110356      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110357      * instead.
110358      */
110359     format : "g:i A",
110360
110361     /**
110362      * @cfg {String} submitFormat
110363      * The date format string which will be submitted to the server. The format must be valid according to {@link
110364      * Ext.Date#parse} (defaults to {@link #format}).
110365      */
110366
110367     /**
110368      * @cfg {String} altFormats
110369      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
110370      * format.
110371      */
110372     altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",
110373
110374     /**
110375      * @cfg {Number} increment
110376      * The number of minutes between each time value in the list.
110377      */
110378     increment: 15,
110379
110380     /**
110381      * @cfg {Number} pickerMaxHeight
110382      * The maximum height of the {@link Ext.picker.Time} dropdown.
110383      */
110384     pickerMaxHeight: 300,
110385
110386     /**
110387      * @cfg {Boolean} selectOnTab
110388      * Whether the Tab key should select the currently highlighted item.
110389      */
110390     selectOnTab: true,
110391
110392     /**
110393      * @private
110394      * This is the date to use when generating time values in the absence of either minValue
110395      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
110396      * arbitrary "safe" date that can be any date aside from DST boundary dates.
110397      */
110398     initDate: '1/1/2008',
110399     initDateFormat: 'j/n/Y',
110400
110401
110402     initComponent: function() {
110403         var me = this,
110404             min = me.minValue,
110405             max = me.maxValue;
110406         if (min) {
110407             me.setMinValue(min);
110408         }
110409         if (max) {
110410             me.setMaxValue(max);
110411         }
110412         this.callParent();
110413     },
110414
110415     initValue: function() {
110416         var me = this,
110417             value = me.value;
110418
110419         // If a String value was supplied, try to convert it to a proper Date object
110420         if (Ext.isString(value)) {
110421             me.value = me.rawToValue(value);
110422         }
110423
110424         me.callParent();
110425     },
110426
110427     /**
110428      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
110429      * @param {Date/String} value The minimum time that can be selected
110430      */
110431     setMinValue: function(value) {
110432         var me = this,
110433             picker = me.picker;
110434         me.setLimit(value, true);
110435         if (picker) {
110436             picker.setMinValue(me.minValue);
110437         }
110438     },
110439
110440     /**
110441      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
110442      * @param {Date/String} value The maximum time that can be selected
110443      */
110444     setMaxValue: function(value) {
110445         var me = this,
110446             picker = me.picker;
110447         me.setLimit(value, false);
110448         if (picker) {
110449             picker.setMaxValue(me.maxValue);
110450         }
110451     },
110452
110453     /**
110454      * @private
110455      * Updates either the min or max value. Converts the user's value into a Date object whose
110456      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
110457      */
110458     setLimit: function(value, isMin) {
110459         var me = this,
110460             d, val;
110461         if (Ext.isString(value)) {
110462             d = me.parseDate(value);
110463         }
110464         else if (Ext.isDate(value)) {
110465             d = value;
110466         }
110467         if (d) {
110468             val = Ext.Date.clearTime(new Date(me.initDate));
110469             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
110470             me[isMin ? 'minValue' : 'maxValue'] = val;
110471         }
110472     },
110473
110474     rawToValue: function(rawValue) {
110475         return this.parseDate(rawValue) || rawValue || null;
110476     },
110477
110478     valueToRaw: function(value) {
110479         return this.formatDate(this.parseDate(value));
110480     },
110481
110482     /**
110483      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
110484      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
110485      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
110486      * set.
110487      * @param {Object} [value] The value to get errors for (defaults to the current field value)
110488      * @return {String[]} All validation errors for this field
110489      */
110490     getErrors: function(value) {
110491         var me = this,
110492             format = Ext.String.format,
110493             errors = me.callParent(arguments),
110494             minValue = me.minValue,
110495             maxValue = me.maxValue,
110496             date;
110497
110498         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
110499
110500         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
110501              return errors;
110502         }
110503
110504         date = me.parseDate(value);
110505         if (!date) {
110506             errors.push(format(me.invalidText, value, me.format));
110507             return errors;
110508         }
110509
110510         if (minValue && date < minValue) {
110511             errors.push(format(me.minText, me.formatDate(minValue)));
110512         }
110513
110514         if (maxValue && date > maxValue) {
110515             errors.push(format(me.maxText, me.formatDate(maxValue)));
110516         }
110517
110518         return errors;
110519     },
110520
110521     formatDate: function() {
110522         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
110523     },
110524
110525     /**
110526      * @private
110527      * Parses an input value into a valid Date object.
110528      * @param {String/Date} value
110529      */
110530     parseDate: function(value) {
110531         if (!value || Ext.isDate(value)) {
110532             return value;
110533         }
110534
110535         var me = this,
110536             val = me.safeParse(value, me.format),
110537             altFormats = me.altFormats,
110538             altFormatsArray = me.altFormatsArray,
110539             i = 0,
110540             len;
110541
110542         if (!val && altFormats) {
110543             altFormatsArray = altFormatsArray || altFormats.split('|');
110544             len = altFormatsArray.length;
110545             for (; i < len && !val; ++i) {
110546                 val = me.safeParse(value, altFormatsArray[i]);
110547             }
110548         }
110549         return val;
110550     },
110551
110552     safeParse: function(value, format){
110553         var me = this,
110554             utilDate = Ext.Date,
110555             parsedDate,
110556             result = null;
110557
110558         if (utilDate.formatContainsDateInfo(format)) {
110559             // assume we've been given a full date
110560             result = utilDate.parse(value, format);
110561         } else {
110562             // Use our initial safe date
110563             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
110564             if (parsedDate) {
110565                 result = parsedDate;
110566             }
110567         }
110568         return result;
110569     },
110570
110571     // @private
110572     getSubmitValue: function() {
110573         var me = this,
110574             format = me.submitFormat || me.format,
110575             value = me.getValue();
110576
110577         return value ? Ext.Date.format(value, format) : null;
110578     },
110579
110580     /**
110581      * @private
110582      * Creates the {@link Ext.picker.Time}
110583      */
110584     createPicker: function() {
110585         var me = this,
110586             picker = Ext.create('Ext.picker.Time', {
110587                 pickerField: me,
110588                 selModel: {
110589                     mode: 'SINGLE'
110590                 },
110591                 floating: true,
110592                 hidden: true,
110593                 minValue: me.minValue,
110594                 maxValue: me.maxValue,
110595                 increment: me.increment,
110596                 format: me.format,
110597                 ownerCt: this.ownerCt,
110598                 renderTo: document.body,
110599                 maxHeight: me.pickerMaxHeight,
110600                 focusOnToFront: false
110601             });
110602
110603         me.mon(picker.getSelectionModel(), {
110604             selectionchange: me.onListSelect,
110605             scope: me
110606         });
110607
110608         return picker;
110609     },
110610
110611     /**
110612      * @private
110613      * Enables the key nav for the Time picker when it is expanded.
110614      * TODO this is largely the same logic as ComboBox, should factor out.
110615      */
110616     onExpand: function() {
110617         var me = this,
110618             keyNav = me.pickerKeyNav,
110619             selectOnTab = me.selectOnTab,
110620             picker = me.getPicker(),
110621             lastSelected = picker.getSelectionModel().lastSelected,
110622             itemNode;
110623
110624         if (!keyNav) {
110625             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
110626                 boundList: picker,
110627                 forceKeyDown: true,
110628                 tab: function(e) {
110629                     if (selectOnTab) {
110630                         if(me.picker.highlightedItem) {
110631                             this.selectHighlighted(e);
110632                         } else {
110633                             me.collapse();
110634                         }
110635                         me.triggerBlur();
110636                     }
110637                     // Tab key event is allowed to propagate to field
110638                     return true;
110639                 }
110640             });
110641             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
110642             if (selectOnTab) {
110643                 me.ignoreMonitorTab = true;
110644             }
110645         }
110646         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
110647
110648         // Highlight the last selected item and scroll it into view
110649         if (lastSelected) {
110650             itemNode = picker.getNode(lastSelected);
110651             if (itemNode) {
110652                 picker.highlightItem(itemNode);
110653                 picker.el.scrollChildIntoView(itemNode, false);
110654             }
110655         }
110656     },
110657
110658     /**
110659      * @private
110660      * Disables the key nav for the Time picker when it is collapsed.
110661      */
110662     onCollapse: function() {
110663         var me = this,
110664             keyNav = me.pickerKeyNav;
110665         if (keyNav) {
110666             keyNav.disable();
110667             me.ignoreMonitorTab = false;
110668         }
110669     },
110670
110671     /**
110672      * @private
110673      * Clears the highlighted item in the picker on change.
110674      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
110675      */
110676     onChange: function() {
110677         var me = this,
110678             picker = me.picker;
110679
110680         me.callParent(arguments);
110681         if(picker) {
110682             picker.clearHighlight();
110683         }
110684     },
110685
110686     /**
110687      * @private
110688      * Handles a time being selected from the Time picker.
110689      */
110690     onListSelect: function(list, recordArray) {
110691         var me = this,
110692             record = recordArray[0],
110693             val = record ? record.get('date') : null;
110694         me.setValue(val);
110695         me.fireEvent('select', me, val);
110696         me.picker.clearHighlight();
110697         me.collapse();
110698         me.inputEl.focus();
110699     }
110700 });
110701
110702
110703 /**
110704  * @class Ext.grid.CellEditor
110705  * @extends Ext.Editor
110706  * Internal utility class that provides default configuration for cell editing.
110707  * @ignore
110708  */
110709 Ext.define('Ext.grid.CellEditor', {
110710     extend: 'Ext.Editor',
110711     constructor: function(config) {
110712         config = Ext.apply({}, config);
110713         
110714         if (config.field) {
110715             config.field.monitorTab = false;
110716         }
110717         if (!Ext.isDefined(config.autoSize)) {
110718             config.autoSize = {
110719                 width: 'boundEl'
110720             };
110721         }
110722         this.callParent([config]);
110723     },
110724     
110725     /**
110726      * @private
110727      * Hide the grid cell when editor is shown.
110728      */
110729     onShow: function() {
110730         var first = this.boundEl.first();
110731         if (first) {
110732             first.hide();
110733         }
110734         this.callParent(arguments);
110735     },
110736     
110737     /**
110738      * @private
110739      * Show grid cell when editor is hidden.
110740      */
110741     onHide: function() {
110742         var first = this.boundEl.first();
110743         if (first) {
110744             first.show();
110745         }
110746         this.callParent(arguments);
110747     },
110748     
110749     /**
110750      * @private
110751      * Fix checkbox blur when it is clicked.
110752      */
110753     afterRender: function() {
110754         this.callParent(arguments);
110755         var field = this.field;
110756         if (field.isXType('checkboxfield')) {
110757             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
110758             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
110759         }
110760     },
110761     
110762     /**
110763      * @private
110764      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
110765      */
110766     onCheckBoxMouseDown: function() {
110767         this.completeEdit = Ext.emptyFn;
110768     },
110769     
110770     /**
110771      * @private
110772      * Restore checkbox focus and completeEdit method.
110773      */
110774     onCheckBoxClick: function() {
110775         delete this.completeEdit;
110776         this.field.focus(false, 10);
110777     },
110778     
110779     alignment: "tl-tl",
110780     hideEl : false,
110781     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
110782     shim: false,
110783     shadow: false
110784 });
110785 /**
110786  * @class Ext.grid.ColumnLayout
110787  * @extends Ext.layout.container.HBox
110788  * @private
110789  *
110790  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
110791  *
110792  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
110793  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
110794  *
110795  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
110796  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
110797  */
110798 Ext.define('Ext.grid.ColumnLayout', {
110799     extend: 'Ext.layout.container.HBox',
110800     alias: 'layout.gridcolumn',
110801     type : 'column',
110802
110803     reserveOffset: false,
110804
110805     shrinkToFit: false,
110806
110807     // Height-stretched innerCt must be able to revert back to unstretched height
110808     clearInnerCtOnLayout: true,
110809
110810     beforeLayout: function() {
110811         var me = this,
110812             i = 0,
110813             items = me.getLayoutItems(),
110814             len = items.length,
110815             item, returnValue,
110816             s;
110817
110818         // Scrollbar offset defined by width of any vertical scroller in the owning grid
110819         if (!Ext.isDefined(me.availableSpaceOffset)) {
110820             s = me.owner.up('tablepanel').verticalScroller;
110821             me.availableSpaceOffset = s ? s.width-1 : 0;
110822         }
110823
110824         returnValue = me.callParent(arguments);
110825
110826         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
110827         me.innerCt.setHeight(23);
110828
110829         // Unstretch child items before the layout which stretches them.
110830         for (; i < len; i++) {
110831             item = items[i];
110832             item.el.setStyle({
110833                 height: 'auto'
110834             });
110835             item.titleContainer.setStyle({
110836                 height: 'auto',
110837                 paddingTop: '0'
110838             });
110839             if (item.componentLayout && item.componentLayout.lastComponentSize) {
110840                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
110841             }
110842         }
110843         return returnValue;
110844     },
110845
110846     // Override to enforce the forceFit config.
110847     calculateChildBoxes: function(visibleItems, targetSize) {
110848         var me = this,
110849             calculations = me.callParent(arguments),
110850             boxes = calculations.boxes,
110851             metaData = calculations.meta,
110852             len = boxes.length, i = 0, box, item;
110853
110854         if (targetSize.width && !me.isHeader) {
110855             // If configured forceFit then all columns will be flexed
110856             if (me.owner.forceFit) {
110857
110858                 for (; i < len; i++) {
110859                     box = boxes[i];
110860                     item = box.component;
110861
110862                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
110863                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
110864
110865                     // For forceFit, just use allocated width as the flex value, and the proportions
110866                     // will end up the same whatever HeaderContainer width they are being forced into.
110867                     item.flex = box.width;
110868                 }
110869
110870                 // Recalculate based upon all columns now being flexed instead of sized.
110871                 calculations = me.callParent(arguments);
110872             }
110873             else if (metaData.tooNarrow) {
110874                 targetSize.width = metaData.desiredSize;
110875             }
110876         }
110877
110878         return calculations;
110879     },
110880
110881     afterLayout: function() {
110882         var me = this,
110883             owner = me.owner,
110884             topGrid,
110885             bothHeaderCts,
110886             otherHeaderCt,
110887             thisHeight,
110888             otherHeight,
110889             modifiedGrid,
110890             i = 0,
110891             items,
110892             len,
110893             headerHeight;
110894
110895         me.callParent(arguments);
110896
110897         // Set up padding in items
110898         if (!me.owner.hideHeaders) {
110899
110900             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
110901             // of the highest one, and sync the other one to that height.
110902             if (owner.lockableInjected) {
110903                 topGrid = owner.up('tablepanel').up('tablepanel');
110904                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
110905                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
110906
110907                 // Both sides must be rendered for this syncing operation to work.
110908                 if (!otherHeaderCt.rendered) {
110909                     return;
110910                 }
110911
110912                 // Get the height of the highest of both HeaderContainers
110913                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
110914                 if (!otherHeight) {
110915                     return;
110916                 }
110917                 thisHeight = this.getRenderTarget().getViewSize().height;
110918                 if (!thisHeight) {
110919                     return;
110920                 }
110921
110922                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
110923                 // Block the upward notification by flagging the top grid's component layout as busy.
110924                 topGrid.componentLayout.layoutBusy = true;
110925
110926                 // Assume that the correct header height is the height of this HeaderContainer
110927                 headerHeight = thisHeight;
110928
110929                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
110930                 if (thisHeight > otherHeight) {
110931                     otherHeaderCt.layout.align = 'stretch';
110932                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
110933                     delete otherHeaderCt.layout.align;
110934                     modifiedGrid = otherHeaderCt.up('tablepanel');
110935                 } else if (otherHeight > thisHeight) {
110936                     headerHeight = otherHeight;
110937                     this.align = 'stretch';
110938                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
110939                     delete this.align;
110940                     modifiedGrid = owner.up('tablepanel');
110941                 }
110942                 topGrid.componentLayout.layoutBusy = false;
110943
110944                 // Gather all Header items across both Grids.
110945                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
110946             } else {
110947                 headerHeight = this.getRenderTarget().getViewSize().height;
110948                 items = me.getLayoutItems();
110949             }
110950
110951             len = items.length;
110952             for (; i < len; i++) {
110953                 items[i].setPadding(headerHeight);
110954             }
110955
110956             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
110957             if (modifiedGrid) {
110958                 setTimeout(function() {
110959                     modifiedGrid.doLayout();
110960                 }, 1);
110961             }
110962         }
110963     },
110964
110965     // FIX: when flexing we actually don't have enough space as we would
110966     // typically because of the scrollOffset on the GridView, must reserve this
110967     updateInnerCtSize: function(tSize, calcs) {
110968         var me = this,
110969             extra;
110970
110971         // Columns must not account for scroll offset
110972         if (!me.isHeader) {
110973             me.tooNarrow = calcs.meta.tooNarrow;
110974             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
110975
110976             if (calcs.meta.tooNarrow) {
110977                 tSize.width = calcs.meta.desiredSize + extra;
110978             } else {
110979                 tSize.width += extra;
110980             }
110981         }
110982
110983         return me.callParent(arguments);
110984     },
110985
110986     doOwnerCtLayouts: function() {
110987         var ownerCt = this.owner.ownerCt;
110988         if (!ownerCt.componentLayout.layoutBusy) {
110989             ownerCt.doComponentLayout();
110990         }
110991     }
110992 });
110993 /**
110994  * @class Ext.grid.LockingView
110995  * This class is used internally to provide a single interface when using
110996  * a locking grid. Internally, the locking grid creates two separate grids,
110997  * so this class is used to map calls appropriately.
110998  * @ignore
110999  */
111000 Ext.define('Ext.grid.LockingView', {
111001
111002     mixins: {
111003         observable: 'Ext.util.Observable'
111004     },
111005
111006     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
111007
111008     constructor: function(config){
111009         var me = this,
111010             eventNames = [],
111011             eventRe = me.eventRelayRe,
111012             locked = config.locked.getView(),
111013             normal = config.normal.getView(),
111014             events,
111015             event;
111016
111017         Ext.apply(me, {
111018             lockedView: locked,
111019             normalView: normal,
111020             lockedGrid: config.locked,
111021             normalGrid: config.normal,
111022             panel: config.panel
111023         });
111024         me.mixins.observable.constructor.call(me, config);
111025
111026         // relay events
111027         events = locked.events;
111028         for (event in events) {
111029             if (events.hasOwnProperty(event) && eventRe.test(event)) {
111030                 eventNames.push(event);
111031             }
111032         }
111033         me.relayEvents(locked, eventNames);
111034         me.relayEvents(normal, eventNames);
111035
111036         normal.on({
111037             scope: me,
111038             itemmouseleave: me.onItemMouseLeave,
111039             itemmouseenter: me.onItemMouseEnter
111040         });
111041
111042         locked.on({
111043             scope: me,
111044             itemmouseleave: me.onItemMouseLeave,
111045             itemmouseenter: me.onItemMouseEnter
111046         });
111047     },
111048
111049     getGridColumns: function() {
111050         var cols = this.lockedGrid.headerCt.getGridColumns();
111051         return cols.concat(this.normalGrid.headerCt.getGridColumns());
111052     },
111053
111054     getEl: function(column){
111055         return this.getViewForColumn(column).getEl();
111056     },
111057
111058     getViewForColumn: function(column) {
111059         var view = this.lockedView,
111060             inLocked;
111061
111062         view.headerCt.cascade(function(col){
111063             if (col === column) {
111064                 inLocked = true;
111065                 return false;
111066             }
111067         });
111068
111069         return inLocked ? view : this.normalView;
111070     },
111071
111072     onItemMouseEnter: function(view, record){
111073         var me = this,
111074             locked = me.lockedView,
111075             other = me.normalView,
111076             item;
111077
111078         if (view.trackOver) {
111079             if (view !== locked) {
111080                 other = locked;
111081             }
111082             item = other.getNode(record);
111083             other.highlightItem(item);
111084         }
111085     },
111086
111087     onItemMouseLeave: function(view, record){
111088         var me = this,
111089             locked = me.lockedView,
111090             other = me.normalView;
111091
111092         if (view.trackOver) {
111093             if (view !== locked) {
111094                 other = locked;
111095             }
111096             other.clearHighlight();
111097         }
111098     },
111099
111100     relayFn: function(name, args){
111101         args = args || [];
111102
111103         var view = this.lockedView;
111104         view[name].apply(view, args || []);
111105         view = this.normalView;
111106         view[name].apply(view, args || []);
111107     },
111108
111109     getSelectionModel: function(){
111110         return this.panel.getSelectionModel();
111111     },
111112
111113     getStore: function(){
111114         return this.panel.store;
111115     },
111116
111117     getNode: function(nodeInfo){
111118         // default to the normal view
111119         return this.normalView.getNode(nodeInfo);
111120     },
111121
111122     getCell: function(record, column){
111123         var view = this.getViewForColumn(column),
111124             row;
111125
111126         row = view.getNode(record);
111127         return Ext.fly(row).down(column.getCellSelector());
111128     },
111129
111130     getRecord: function(node){
111131         var result = this.lockedView.getRecord(node);
111132         if (!node) {
111133             result = this.normalView.getRecord(node);
111134         }
111135         return result;
111136     },
111137
111138     addElListener: function(eventName, fn, scope){
111139         this.relayFn('addElListener', arguments);
111140     },
111141
111142     refreshNode: function(){
111143         this.relayFn('refreshNode', arguments);
111144     },
111145
111146     refresh: function(){
111147         this.relayFn('refresh', arguments);
111148     },
111149
111150     bindStore: function(){
111151         this.relayFn('bindStore', arguments);
111152     },
111153
111154     addRowCls: function(){
111155         this.relayFn('addRowCls', arguments);
111156     },
111157
111158     removeRowCls: function(){
111159         this.relayFn('removeRowCls', arguments);
111160     }
111161
111162 });
111163 /**
111164  * @class Ext.grid.Lockable
111165  * @private
111166  *
111167  * Lockable is a private mixin which injects lockable behavior into any
111168  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
111169  * automatically inject the Ext.grid.Lockable mixin in when one of the
111170  * these conditions are met:
111171  *
111172  *  - The TablePanel has the lockable configuration set to true
111173  *  - One of the columns in the TablePanel has locked set to true/false
111174  *
111175  * Each TablePanel subclass must register an alias. It should have an array
111176  * of configurations to copy to the 2 separate tablepanel's that will be generated
111177  * to note what configurations should be copied. These are named normalCfgCopy and
111178  * lockedCfgCopy respectively.
111179  *
111180  * Columns which are locked must specify a fixed width. They do NOT support a
111181  * flex width.
111182  *
111183  * Configurations which are specified in this class will be available on any grid or
111184  * tree which is using the lockable functionality.
111185  */
111186 Ext.define('Ext.grid.Lockable', {
111187
111188     requires: ['Ext.grid.LockingView'],
111189
111190     /**
111191      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
111192      * locked grid view. This is turned on by default. If your grid is guaranteed
111193      * to have rows of all the same height, you should set this to false to
111194      * optimize performance.
111195      */
111196     syncRowHeight: true,
111197
111198     /**
111199      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
111200      * not specified lockable will determine the subgrid xtype to create by the
111201      * following rule. Use the superclasses xtype if the superclass is NOT
111202      * tablepanel, otherwise use the xtype itself.
111203      */
111204
111205     /**
111206      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
111207      * locked side of the grid. Any conflicting configurations between lockedViewConfig
111208      * and viewConfig will be overwritten by the lockedViewConfig.
111209      */
111210
111211     /**
111212      * @cfg {Object} normalViewConfig A view configuration to be applied to the
111213      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
111214      * and viewConfig will be overwritten by the normalViewConfig.
111215      */
111216
111217     // private variable to track whether or not the spacer is hidden/visible
111218     spacerHidden: true,
111219
111220     headerCounter: 0,
111221
111222     // i8n text
111223     unlockText: 'Unlock',
111224     lockText: 'Lock',
111225
111226     determineXTypeToCreate: function() {
111227         var me = this,
111228             typeToCreate;
111229
111230         if (me.subGridXType) {
111231             typeToCreate = me.subGridXType;
111232         } else {
111233             var xtypes     = this.getXTypes().split('/'),
111234                 xtypesLn   = xtypes.length,
111235                 xtype      = xtypes[xtypesLn - 1],
111236                 superxtype = xtypes[xtypesLn - 2];
111237
111238             if (superxtype !== 'tablepanel') {
111239                 typeToCreate = superxtype;
111240             } else {
111241                 typeToCreate = xtype;
111242             }
111243         }
111244
111245         return typeToCreate;
111246     },
111247
111248     // injectLockable will be invoked before initComponent's parent class implementation
111249     // is called, so throughout this method this. are configurations
111250     injectLockable: function() {
111251         // ensure lockable is set to true in the TablePanel
111252         this.lockable = true;
111253         // Instruct the TablePanel it already has a view and not to create one.
111254         // We are going to aggregate 2 copies of whatever TablePanel we are using
111255         this.hasView = true;
111256
111257         var me = this,
111258             // xtype of this class, 'treepanel' or 'gridpanel'
111259             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
111260             // alias.)
111261             xtype = me.determineXTypeToCreate(),
111262             // share the selection model
111263             selModel = me.getSelectionModel(),
111264             lockedGrid = {
111265                 xtype: xtype,
111266                 // Lockable does NOT support animations for Tree
111267                 enableAnimations: false,
111268                 scroll: false,
111269                 scrollerOwner: false,
111270                 selModel: selModel,
111271                 border: false,
111272                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
111273             },
111274             normalGrid = {
111275                 xtype: xtype,
111276                 enableAnimations: false,
111277                 scrollerOwner: false,
111278                 selModel: selModel,
111279                 border: false
111280             },
111281             i = 0,
111282             columns,
111283             lockedHeaderCt,
111284             normalHeaderCt;
111285
111286         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
111287
111288         // copy appropriate configurations to the respective
111289         // aggregated tablepanel instances and then delete them
111290         // from the master tablepanel.
111291         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
111292         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
111293         for (; i < me.normalCfgCopy.length; i++) {
111294             delete me[me.normalCfgCopy[i]];
111295         }
111296         for (i = 0; i < me.lockedCfgCopy.length; i++) {
111297             delete me[me.lockedCfgCopy[i]];
111298         }
111299
111300         me.addEvents(
111301             /**
111302              * @event lockcolumn
111303              * Fires when a column is locked.
111304              * @param {Ext.grid.Panel} this The gridpanel.
111305              * @param {Ext.grid.column.Column} column The column being locked.
111306              */
111307             'lockcolumn',
111308
111309             /**
111310              * @event unlockcolumn
111311              * Fires when a column is unlocked.
111312              * @param {Ext.grid.Panel} this The gridpanel.
111313              * @param {Ext.grid.column.Column} column The column being unlocked.
111314              */
111315             'unlockcolumn'
111316         );
111317
111318         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
111319
111320         me.lockedHeights = [];
111321         me.normalHeights = [];
111322
111323         columns = me.processColumns(me.columns);
111324
111325         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
111326         lockedGrid.columns = columns.locked;
111327         normalGrid.columns = columns.normal;
111328
111329         me.store = Ext.StoreManager.lookup(me.store);
111330         lockedGrid.store = me.store;
111331         normalGrid.store = me.store;
111332
111333         // normal grid should flex the rest of the width
111334         normalGrid.flex = 1;
111335         lockedGrid.viewConfig = me.lockedViewConfig || {};
111336         lockedGrid.viewConfig.loadingUseMsg = false;
111337         normalGrid.viewConfig = me.normalViewConfig || {};
111338
111339         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
111340         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
111341
111342         me.normalGrid = Ext.ComponentManager.create(normalGrid);
111343         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
111344
111345         me.view = Ext.create('Ext.grid.LockingView', {
111346             locked: me.lockedGrid,
111347             normal: me.normalGrid,
111348             panel: me
111349         });
111350
111351         if (me.syncRowHeight) {
111352             me.lockedGrid.getView().on({
111353                 refresh: me.onLockedGridAfterRefresh,
111354                 itemupdate: me.onLockedGridAfterUpdate,
111355                 scope: me
111356             });
111357
111358             me.normalGrid.getView().on({
111359                 refresh: me.onNormalGridAfterRefresh,
111360                 itemupdate: me.onNormalGridAfterUpdate,
111361                 scope: me
111362             });
111363         }
111364
111365         lockedHeaderCt = me.lockedGrid.headerCt;
111366         normalHeaderCt = me.normalGrid.headerCt;
111367
111368         lockedHeaderCt.lockedCt = true;
111369         lockedHeaderCt.lockableInjected = true;
111370         normalHeaderCt.lockableInjected = true;
111371
111372         lockedHeaderCt.on({
111373             columnshow: me.onLockedHeaderShow,
111374             columnhide: me.onLockedHeaderHide,
111375             columnmove: me.onLockedHeaderMove,
111376             sortchange: me.onLockedHeaderSortChange,
111377             columnresize: me.onLockedHeaderResize,
111378             scope: me
111379         });
111380
111381         normalHeaderCt.on({
111382             columnmove: me.onNormalHeaderMove,
111383             sortchange: me.onNormalHeaderSortChange,
111384             scope: me
111385         });
111386
111387         me.normalGrid.on({
111388             scrollershow: me.onScrollerShow,
111389             scrollerhide: me.onScrollerHide,
111390             scope: me
111391         });
111392
111393         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
111394
111395         me.modifyHeaderCt();
111396         me.items = [me.lockedGrid, me.normalGrid];
111397
111398         me.relayHeaderCtEvents(lockedHeaderCt);
111399         me.relayHeaderCtEvents(normalHeaderCt);
111400
111401         me.layout = {
111402             type: 'hbox',
111403             align: 'stretch'
111404         };
111405     },
111406
111407     processColumns: function(columns){
111408         // split apart normal and lockedWidths
111409         var i = 0,
111410             len = columns.length,
111411             lockedWidth = 1,
111412             lockedHeaders = [],
111413             normalHeaders = [],
111414             column;
111415
111416         for (; i < len; ++i) {
111417             column = columns[i];
111418             // mark the column as processed so that the locked attribute does not
111419             // trigger trying to aggregate the columns again.
111420             column.processed = true;
111421             if (column.locked) {
111422                 if (!column.hidden) {
111423                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
111424                 }
111425                 lockedHeaders.push(column);
111426             } else {
111427                 normalHeaders.push(column);
111428             }
111429             if (!column.headerId) {
111430                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
111431             }
111432         }
111433         return {
111434             lockedWidth: lockedWidth,
111435             locked: lockedHeaders,
111436             normal: normalHeaders
111437         };
111438     },
111439
111440     // create a new spacer after the table is refreshed
111441     onLockedGridAfterLayout: function() {
111442         var me         = this,
111443             lockedView = me.lockedGrid.getView();
111444         lockedView.on({
111445             beforerefresh: me.destroySpacer,
111446             scope: me
111447         });
111448     },
111449
111450     // trigger a pseudo refresh on the normal side
111451     onLockedHeaderMove: function() {
111452         if (this.syncRowHeight) {
111453             this.onNormalGridAfterRefresh();
111454         }
111455     },
111456
111457     // trigger a pseudo refresh on the locked side
111458     onNormalHeaderMove: function() {
111459         if (this.syncRowHeight) {
111460             this.onLockedGridAfterRefresh();
111461         }
111462     },
111463
111464     // create a spacer in lockedsection and store a reference
111465     // TODO: Should destroy before refreshing content
111466     getSpacerEl: function() {
111467         var me   = this,
111468             w,
111469             view,
111470             el;
111471
111472         if (!me.spacerEl) {
111473             // This affects scrolling all the way to the bottom of a locked grid
111474             // additional test, sort a column and make sure it synchronizes
111475             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
111476             view = me.lockedGrid.getView();
111477             el   = view.el;
111478
111479             me.spacerEl = Ext.DomHelper.append(el, {
111480                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
111481                 style: 'height: ' + w + 'px;'
111482             }, true);
111483         }
111484         return me.spacerEl;
111485     },
111486
111487     destroySpacer: function() {
111488         var me = this;
111489         if (me.spacerEl) {
111490             me.spacerEl.destroy();
111491             delete me.spacerEl;
111492         }
111493     },
111494
111495     // cache the heights of all locked rows and sync rowheights
111496     onLockedGridAfterRefresh: function() {
111497         var me     = this,
111498             view   = me.lockedGrid.getView(),
111499             el     = view.el,
111500             rowEls = el.query(view.getItemSelector()),
111501             ln     = rowEls.length,
111502             i = 0;
111503
111504         // reset heights each time.
111505         me.lockedHeights = [];
111506
111507         for (; i < ln; i++) {
111508             me.lockedHeights[i] = rowEls[i].clientHeight;
111509         }
111510         me.syncRowHeights();
111511     },
111512
111513     // cache the heights of all normal rows and sync rowheights
111514     onNormalGridAfterRefresh: function() {
111515         var me     = this,
111516             view   = me.normalGrid.getView(),
111517             el     = view.el,
111518             rowEls = el.query(view.getItemSelector()),
111519             ln     = rowEls.length,
111520             i = 0;
111521
111522         // reset heights each time.
111523         me.normalHeights = [];
111524
111525         for (; i < ln; i++) {
111526             me.normalHeights[i] = rowEls[i].clientHeight;
111527         }
111528         me.syncRowHeights();
111529     },
111530
111531     // rows can get bigger/smaller
111532     onLockedGridAfterUpdate: function(record, index, node) {
111533         this.lockedHeights[index] = node.clientHeight;
111534         this.syncRowHeights();
111535     },
111536
111537     // rows can get bigger/smaller
111538     onNormalGridAfterUpdate: function(record, index, node) {
111539         this.normalHeights[index] = node.clientHeight;
111540         this.syncRowHeights();
111541     },
111542
111543     // match the rowheights to the biggest rowheight on either
111544     // side
111545     syncRowHeights: function() {
111546         var me = this,
111547             lockedHeights = me.lockedHeights,
111548             normalHeights = me.normalHeights,
111549             calcHeights   = [],
111550             ln = lockedHeights.length,
111551             i  = 0,
111552             lockedView, normalView,
111553             lockedRowEls, normalRowEls,
111554             vertScroller = me.getVerticalScroller(),
111555             scrollTop;
111556
111557         // ensure there are an equal num of locked and normal
111558         // rows before synchronization
111559         if (lockedHeights.length && normalHeights.length) {
111560             lockedView = me.lockedGrid.getView();
111561             normalView = me.normalGrid.getView();
111562             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
111563             normalRowEls = normalView.el.query(normalView.getItemSelector());
111564
111565             // loop thru all of the heights and sync to the other side
111566             for (; i < ln; i++) {
111567                 // ensure both are numbers
111568                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
111569                     if (lockedHeights[i] > normalHeights[i]) {
111570                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
111571                     } else if (lockedHeights[i] < normalHeights[i]) {
111572                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
111573                     }
111574                 }
111575             }
111576
111577             // invalidate the scroller and sync the scrollers
111578             me.normalGrid.invalidateScroller();
111579
111580             // synchronize the view with the scroller, if we have a virtualScrollTop
111581             // then the user is using a PagingScroller
111582             if (vertScroller && vertScroller.setViewScrollTop) {
111583                 vertScroller.setViewScrollTop(me.virtualScrollTop);
111584             } else {
111585                 // We don't use setScrollTop here because if the scrollTop is
111586                 // set to the exact same value some browsers won't fire the scroll
111587                 // event. Instead, we directly set the scrollTop.
111588                 scrollTop = normalView.el.dom.scrollTop;
111589                 normalView.el.dom.scrollTop = scrollTop;
111590                 lockedView.el.dom.scrollTop = scrollTop;
111591             }
111592
111593             // reset the heights
111594             me.lockedHeights = [];
111595             me.normalHeights = [];
111596         }
111597     },
111598
111599     // track when scroller is shown
111600     onScrollerShow: function(scroller, direction) {
111601         if (direction === 'horizontal') {
111602             this.spacerHidden = false;
111603             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
111604         }
111605     },
111606
111607     // track when scroller is hidden
111608     onScrollerHide: function(scroller, direction) {
111609         if (direction === 'horizontal') {
111610             this.spacerHidden = true;
111611             if (this.spacerEl) {
111612                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
111613             }
111614         }
111615     },
111616
111617
111618     // inject Lock and Unlock text
111619     modifyHeaderCt: function() {
111620         var me = this;
111621         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
111622         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
111623     },
111624
111625     onUnlockMenuClick: function() {
111626         this.unlock();
111627     },
111628
111629     onLockMenuClick: function() {
111630         this.lock();
111631     },
111632
111633     getMenuItems: function(locked) {
111634         var me            = this,
111635             unlockText    = me.unlockText,
111636             lockText      = me.lockText,
111637             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
111638             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
111639             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
111640             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
111641
111642         // runs in the scope of headerCt
111643         return function() {
111644             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
111645             o.push('-',{
111646                 cls: unlockCls,
111647                 text: unlockText,
111648                 handler: unlockHandler,
111649                 disabled: !locked
111650             });
111651             o.push({
111652                 cls: lockCls,
111653                 text: lockText,
111654                 handler: lockHandler,
111655                 disabled: locked
111656             });
111657             return o;
111658         };
111659     },
111660
111661     // going from unlocked section to locked
111662     /**
111663      * Locks the activeHeader as determined by which menu is open OR a header
111664      * as specified.
111665      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111666      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
111667      * @private
111668      */
111669     lock: function(activeHd, toIdx) {
111670         var me         = this,
111671             normalGrid = me.normalGrid,
111672             lockedGrid = me.lockedGrid,
111673             normalHCt  = normalGrid.headerCt,
111674             lockedHCt  = lockedGrid.headerCt;
111675
111676         activeHd = activeHd || normalHCt.getMenu().activeHeader;
111677
111678         // if column was previously flexed, get/set current width
111679         // and remove the flex
111680         if (activeHd.flex) {
111681             activeHd.width = activeHd.getWidth();
111682             delete activeHd.flex;
111683         }
111684
111685         normalHCt.remove(activeHd, false);
111686         lockedHCt.suspendLayout = true;
111687         activeHd.locked = true;
111688         if (Ext.isDefined(toIdx)) {
111689             lockedHCt.insert(toIdx, activeHd);
111690         } else {
111691             lockedHCt.add(activeHd);
111692         }
111693         lockedHCt.suspendLayout = false;
111694         me.syncLockedSection();
111695
111696         me.fireEvent('lockcolumn', me, activeHd);
111697     },
111698
111699     syncLockedSection: function() {
111700         var me = this;
111701         me.syncLockedWidth();
111702         me.lockedGrid.getView().refresh();
111703         me.normalGrid.getView().refresh();
111704     },
111705
111706     // adjust the locked section to the width of its respective
111707     // headerCt
111708     syncLockedWidth: function() {
111709         var me = this,
111710             width = me.lockedGrid.headerCt.getFullWidth(true);
111711         me.lockedGrid.setWidth(width+1); // +1 for border pixel
111712         me.doComponentLayout();
111713     },
111714
111715     onLockedHeaderResize: function() {
111716         this.syncLockedWidth();
111717     },
111718
111719     onLockedHeaderHide: function() {
111720         this.syncLockedWidth();
111721     },
111722
111723     onLockedHeaderShow: function() {
111724         this.syncLockedWidth();
111725     },
111726
111727     onLockedHeaderSortChange: function(headerCt, header, sortState) {
111728         if (sortState) {
111729             // no real header, and silence the event so we dont get into an
111730             // infinite loop
111731             this.normalGrid.headerCt.clearOtherSortStates(null, true);
111732         }
111733     },
111734
111735     onNormalHeaderSortChange: function(headerCt, header, sortState) {
111736         if (sortState) {
111737             // no real header, and silence the event so we dont get into an
111738             // infinite loop
111739             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
111740         }
111741     },
111742
111743     // going from locked section to unlocked
111744     /**
111745      * Unlocks the activeHeader as determined by which menu is open OR a header
111746      * as specified.
111747      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111748      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
111749      * @private
111750      */
111751     unlock: function(activeHd, toIdx) {
111752         var me         = this,
111753             normalGrid = me.normalGrid,
111754             lockedGrid = me.lockedGrid,
111755             normalHCt  = normalGrid.headerCt,
111756             lockedHCt  = lockedGrid.headerCt;
111757
111758         if (!Ext.isDefined(toIdx)) {
111759             toIdx = 0;
111760         }
111761         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
111762
111763         lockedHCt.remove(activeHd, false);
111764         me.syncLockedWidth();
111765         me.lockedGrid.getView().refresh();
111766         activeHd.locked = false;
111767         normalHCt.insert(toIdx, activeHd);
111768         me.normalGrid.getView().refresh();
111769
111770         me.fireEvent('unlockcolumn', me, activeHd);
111771     },
111772
111773     applyColumnsState: function (columns) {
111774         var me = this,
111775             lockedGrid = me.lockedGrid,
111776             lockedHeaderCt = lockedGrid.headerCt,
111777             normalHeaderCt = me.normalGrid.headerCt,
111778             lockedCols = lockedHeaderCt.items,
111779             normalCols = normalHeaderCt.items,
111780             existing,
111781             locked = [],
111782             normal = [],
111783             lockedDefault,
111784             lockedWidth = 1;
111785
111786         Ext.each(columns, function (col) {
111787             function matches (item) {
111788                 return item.headerId == col.id;
111789             }
111790
111791             lockedDefault = true;
111792             if (!(existing = lockedCols.findBy(matches))) {
111793                 existing = normalCols.findBy(matches);
111794                 lockedDefault = false;
111795             }
111796
111797             if (existing) {
111798                 if (existing.applyColumnState) {
111799                     existing.applyColumnState(col);
111800                 }
111801                 if (!Ext.isDefined(existing.locked)) {
111802                     existing.locked = lockedDefault;
111803                 }
111804                 if (existing.locked) {
111805                     locked.push(existing);
111806                     if (!existing.hidden && Ext.isNumber(existing.width)) {
111807                         lockedWidth += existing.width;
111808                     }
111809                 } else {
111810                     normal.push(existing);
111811                 }
111812             }
111813         });
111814
111815         // state and config must have the same columns (compare counts for now):
111816         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
111817             lockedHeaderCt.removeAll(false);
111818             normalHeaderCt.removeAll(false);
111819
111820             lockedHeaderCt.add(locked);
111821             normalHeaderCt.add(normal);
111822
111823             lockedGrid.setWidth(lockedWidth);
111824         }
111825     },
111826
111827     getColumnsState: function () {
111828         var me = this,
111829             locked = me.lockedGrid.headerCt.getColumnsState(),
111830             normal = me.normalGrid.headerCt.getColumnsState();
111831
111832         return locked.concat(normal);
111833     },
111834
111835     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
111836     reconfigureLockable: function(store, columns) {
111837         var me = this,
111838             lockedGrid = me.lockedGrid,
111839             normalGrid = me.normalGrid;
111840
111841         if (columns) {
111842             lockedGrid.headerCt.suspendLayout = true;
111843             normalGrid.headerCt.suspendLayout = true;
111844             lockedGrid.headerCt.removeAll();
111845             normalGrid.headerCt.removeAll();
111846
111847             columns = me.processColumns(columns);
111848             lockedGrid.setWidth(columns.lockedWidth);
111849             lockedGrid.headerCt.add(columns.locked);
111850             normalGrid.headerCt.add(columns.normal);
111851         }
111852
111853         if (store) {
111854             store = Ext.data.StoreManager.lookup(store);
111855             me.store = store;
111856             lockedGrid.bindStore(store);
111857             normalGrid.bindStore(store);
111858         } else {
111859             lockedGrid.getView().refresh();
111860             normalGrid.getView().refresh();
111861         }
111862
111863         if (columns) {
111864             lockedGrid.headerCt.suspendLayout = false;
111865             normalGrid.headerCt.suspendLayout = false;
111866             lockedGrid.headerCt.forceComponentLayout();
111867             normalGrid.headerCt.forceComponentLayout();
111868         }
111869     }
111870 });
111871
111872 /**
111873  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
111874  * across different sections.
111875  */
111876 Ext.define('Ext.grid.Scroller', {
111877     extend: 'Ext.Component',
111878     alias: 'widget.gridscroller',
111879     weight: 110,
111880     baseCls: Ext.baseCSSPrefix + 'scroller',
111881     focusable: false,
111882     reservedSpace: 0,
111883
111884     renderTpl: [
111885         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
111886             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
111887         '</div>'
111888     ],
111889
111890     initComponent: function() {
111891         var me       = this,
111892             dock     = me.dock,
111893             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
111894
111895         me.offsets = {bottom: 0};
111896         me.scrollProp = 'scrollTop';
111897         me.vertical = true;
111898         me.sizeProp = 'width';
111899
111900         if (dock === 'top' || dock === 'bottom') {
111901             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
111902             me.sizeProp = 'height';
111903             me.scrollProp = 'scrollLeft';
111904             me.vertical = false;
111905             me.weight += 5;
111906         }
111907
111908         me.cls += (' ' + cls);
111909
111910         Ext.applyIf(me.renderSelectors, {
111911             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
111912             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
111913         });
111914         me.callParent();
111915     },
111916     
111917     ensureDimension: function(){
111918         var me = this,
111919             sizeProp = me.sizeProp;
111920             
111921         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
111922     },
111923
111924     initRenderData: function () {
111925         var me = this,
111926             ret = me.callParent(arguments) || {};
111927
111928         ret.baseId = me.id;
111929
111930         return ret;
111931     },
111932
111933     afterRender: function() {
111934         var me = this;
111935         me.callParent();
111936         
111937         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
111938         Ext.cache[me.el.id].skipGarbageCollection = true;
111939     },
111940
111941     onAdded: function(container) {
111942         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
111943         this.ownerGrid = container;
111944         this.callParent(arguments);
111945     },
111946
111947     getSizeCalculation: function() {
111948         var me     = this,
111949             owner  = me.getPanel(),
111950             width  = 1,
111951             height = 1,
111952             view, tbl;
111953
111954         if (!me.vertical) {
111955             // TODO: Must gravitate to a single region..
111956             // Horizontal scrolling only scrolls virtualized region
111957             var items  = owner.query('tableview'),
111958                 center = items[1] || items[0];
111959
111960             if (!center) {
111961                 return false;
111962             }
111963             // center is not guaranteed to have content, such as when there
111964             // are zero rows in the grid/tree. We read the width from the
111965             // headerCt instead.
111966             width = center.headerCt.getFullWidth();
111967
111968             if (Ext.isIEQuirks) {
111969                 width--;
111970             }
111971         } else {
111972             view = owner.down('tableview:not([lockableInjected])');
111973             if (!view || !view.el) {
111974                 return false;
111975             }
111976             tbl = view.el.child('table', true);
111977             if (!tbl) {
111978                 return false;
111979             }
111980
111981             // needs to also account for header and scroller (if still in picture)
111982             // should calculate from headerCt.
111983             height = tbl.offsetHeight;
111984         }
111985         if (isNaN(width)) {
111986             width = 1;
111987         }
111988         if (isNaN(height)) {
111989             height = 1;
111990         }
111991         return {
111992             width: width,
111993             height: height
111994         };
111995     },
111996
111997     invalidate: function(firstPass) {
111998         var me = this,
111999             stretchEl = me.stretchEl;
112000
112001         if (!stretchEl || !me.ownerCt) {
112002             return;
112003         }
112004
112005         var size  = me.getSizeCalculation(),
112006             scrollEl = me.scrollEl,
112007             elDom = scrollEl.dom,
112008             reservedSpace = me.reservedSpace,
112009             pos,
112010             extra = 5;
112011
112012         if (size) {
112013             stretchEl.setSize(size);
112014
112015             size = me.el.getSize(true);
112016
112017             if (me.vertical) {
112018                 size.width += extra;
112019                 size.height -= reservedSpace;
112020                 pos = 'left';
112021             } else {
112022                 size.width -= reservedSpace;
112023                 size.height += extra;
112024                 pos = 'top';
112025             }
112026
112027             scrollEl.setSize(size);
112028             elDom.style[pos] = (-extra) + 'px';
112029
112030             // BrowserBug: IE7
112031             // This makes the scroller enabled, when initially rendering.
112032             elDom.scrollTop = elDom.scrollTop;
112033         }
112034     },
112035
112036     afterComponentLayout: function() {
112037         this.callParent(arguments);
112038         this.invalidate();
112039     },
112040
112041     restoreScrollPos: function () {
112042         var me = this,
112043             el = this.scrollEl,
112044             elDom = el && el.dom;
112045
112046         if (me._scrollPos !== null && elDom) {
112047             elDom[me.scrollProp] = me._scrollPos;
112048             me._scrollPos = null;
112049         }
112050     },
112051
112052     setReservedSpace: function (reservedSpace) {
112053         var me = this;
112054         if (me.reservedSpace !== reservedSpace) {
112055             me.reservedSpace = reservedSpace;
112056             me.invalidate();
112057         }
112058     },
112059
112060     saveScrollPos: function () {
112061         var me = this,
112062             el = this.scrollEl,
112063             elDom = el && el.dom;
112064
112065         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
112066     },
112067
112068     /**
112069      * Sets the scrollTop and constrains the value between 0 and max.
112070      * @param {Number} scrollTop
112071      * @return {Number} The resulting scrollTop value after being constrained
112072      */
112073     setScrollTop: function(scrollTop) {
112074         var el = this.scrollEl,
112075             elDom = el && el.dom;
112076
112077         if (elDom) {
112078             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
112079         }
112080     },
112081
112082     /**
112083      * Sets the scrollLeft and constrains the value between 0 and max.
112084      * @param {Number} scrollLeft
112085      * @return {Number} The resulting scrollLeft value after being constrained
112086      */
112087     setScrollLeft: function(scrollLeft) {
112088         var el = this.scrollEl,
112089             elDom = el && el.dom;
112090
112091         if (elDom) {
112092             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
112093         }
112094     },
112095
112096     /**
112097      * Scroll by deltaY
112098      * @param {Number} delta
112099      * @return {Number} The resulting scrollTop value
112100      */
112101     scrollByDeltaY: function(delta) {
112102         var el = this.scrollEl,
112103             elDom = el && el.dom;
112104
112105         if (elDom) {
112106             return this.setScrollTop(elDom.scrollTop + delta);
112107         }
112108     },
112109
112110     /**
112111      * Scroll by deltaX
112112      * @param {Number} delta
112113      * @return {Number} The resulting scrollLeft value
112114      */
112115     scrollByDeltaX: function(delta) {
112116         var el = this.scrollEl,
112117             elDom = el && el.dom;
112118
112119         if (elDom) {
112120             return this.setScrollLeft(elDom.scrollLeft + delta);
112121         }
112122     },
112123
112124
112125     /**
112126      * Scroll to the top.
112127      */
112128     scrollToTop : function(){
112129         this.setScrollTop(0);
112130     },
112131
112132     // synchronize the scroller with the bound gridviews
112133     onElScroll: function(event, target) {
112134         this.fireEvent('bodyscroll', event, target);
112135     },
112136
112137     getPanel: function() {
112138         var me = this;
112139         if (!me.panel) {
112140             me.panel = this.up('[scrollerOwner]');
112141         }
112142         return me.panel;
112143     }
112144 });
112145
112146
112147 /**
112148  * @class Ext.grid.PagingScroller
112149  * @extends Ext.grid.Scroller
112150  */
112151 Ext.define('Ext.grid.PagingScroller', {
112152     extend: 'Ext.grid.Scroller',
112153     alias: 'widget.paginggridscroller',
112154     //renderTpl: null,
112155     //tpl: [
112156     //    '<tpl for="pages">',
112157     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
112158     //    '</tpl>'
112159     //],
112160     /**
112161      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
112162      * at what percentage to begin fetching the next page. For example if the pageSize is 100
112163      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
112164      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
112165      */
112166     percentageFromEdge: 0.35,
112167
112168     /**
112169      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
112170      * when scrolling the PagingScrollbar.
112171      */
112172     scrollToLoadBuffer: 200,
112173
112174     activePrefetch: true,
112175
112176     chunkSize: 50,
112177     snapIncrement: 25,
112178
112179     syncScroll: true,
112180
112181     initComponent: function() {
112182         var me = this,
112183             ds = me.store;
112184
112185         ds.on('guaranteedrange', me.onGuaranteedRange, me);
112186         me.callParent(arguments);
112187     },
112188
112189     onGuaranteedRange: function(range, start, end) {
112190         var me = this,
112191             ds = me.store,
112192             rs;
112193         // this should never happen
112194         if (range.length && me.visibleStart < range[0].index) {
112195             return;
112196         }
112197
112198         ds.loadRecords(range);
112199
112200         if (!me.firstLoad) {
112201             if (me.rendered) {
112202                 me.invalidate();
112203             } else {
112204                 me.on('afterrender', me.invalidate, me, {single: true});
112205             }
112206             me.firstLoad = true;
112207         } else {
112208             // adjust to visible
112209             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
112210             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
112211                 me.syncTo();
112212             }
112213         }
112214     },
112215
112216     syncTo: function() {
112217         var me            = this,
112218             pnl           = me.getPanel(),
112219             store         = pnl.store,
112220             scrollerElDom = this.scrollEl.dom,
112221             rowOffset     = me.visibleStart - store.guaranteedStart,
112222             scrollBy      = rowOffset * me.rowHeight,
112223             scrollHeight  = scrollerElDom.scrollHeight,
112224             clientHeight  = scrollerElDom.clientHeight,
112225             scrollTop     = scrollerElDom.scrollTop,
112226             useMaximum;
112227             
112228
112229         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
112230         // Instead we are using offsetHeight and hardcoding borders
112231         if (Ext.isIE9 && Ext.isStrict) {
112232             clientHeight = scrollerElDom.offsetHeight + 2;
112233         }
112234
112235         // This should always be zero or greater than zero but staying
112236         // safe and less than 0 we'll scroll to the bottom.
112237         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
112238         this.setViewScrollTop(scrollBy, useMaximum);
112239     },
112240
112241     getPageData : function(){
112242         var panel = this.getPanel(),
112243             store = panel.store,
112244             totalCount = store.getTotalCount();
112245
112246         return {
112247             total : totalCount,
112248             currentPage : store.currentPage,
112249             pageCount: Math.ceil(totalCount / store.pageSize),
112250             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
112251             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
112252         };
112253     },
112254
112255     onElScroll: function(e, t) {
112256         var me = this,
112257             panel = me.getPanel(),
112258             store = panel.store,
112259             pageSize = store.pageSize,
112260             guaranteedStart = store.guaranteedStart,
112261             guaranteedEnd = store.guaranteedEnd,
112262             totalCount = store.getTotalCount(),
112263             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
112264             position = t.scrollTop,
112265             visibleStart = Math.floor(position / me.rowHeight),
112266             view = panel.down('tableview'),
112267             viewEl = view.el,
112268             visibleHeight = viewEl.getHeight(),
112269             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
112270             visibleEnd = visibleStart + visibleAhead,
112271             prevPage = Math.floor(visibleStart / pageSize),
112272             nextPage = Math.floor(visibleEnd / pageSize) + 2,
112273             lastPage = Math.ceil(totalCount / pageSize),
112274             snap = me.snapIncrement,
112275             requestStart = Math.floor(visibleStart / snap) * snap,
112276             requestEnd = requestStart + pageSize - 1,
112277             activePrefetch = me.activePrefetch;
112278
112279         me.visibleStart = visibleStart;
112280         me.visibleEnd = visibleEnd;
112281         
112282         
112283         me.syncScroll = true;
112284         if (totalCount >= pageSize) {
112285             // end of request was past what the total is, grab from the end back a pageSize
112286             if (requestEnd > totalCount - 1) {
112287                 me.cancelLoad();
112288                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
112289                     me.syncScroll = true;
112290                 }
112291                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
112292             // Out of range, need to reset the current data set
112293             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
112294                 if (visibleStart <= guaranteedStart) {
112295                     // need to scroll up
112296                     requestStart -= snap;
112297                     requestEnd -= snap;
112298                     
112299                     if (requestStart < 0) {
112300                         requestStart = 0;
112301                         requestEnd = pageSize;
112302                     }
112303                 }
112304                 if (store.rangeSatisfied(requestStart, requestEnd)) {
112305                     me.cancelLoad();
112306                     store.guaranteeRange(requestStart, requestEnd);
112307                 } else {
112308                     store.mask();
112309                     me.attemptLoad(requestStart, requestEnd);
112310                 }
112311                 // dont sync the scroll view immediately, sync after the range has been guaranteed
112312                 me.syncScroll = false;
112313             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
112314                 me.syncScroll = true;
112315                 store.prefetchPage(prevPage);
112316             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
112317                 me.syncScroll = true;
112318                 store.prefetchPage(nextPage);
112319             }
112320         }
112321
112322         if (me.syncScroll) {
112323             me.syncTo();
112324         }
112325     },
112326
112327     getSizeCalculation: function() {
112328         // Use the direct ownerCt here rather than the scrollerOwner
112329         // because we are calculating widths/heights.
112330         var me     = this,
112331             owner  = me.ownerGrid,
112332             view   = owner.getView(),
112333             store  = me.store,
112334             dock   = me.dock,
112335             elDom  = me.el.dom,
112336             width  = 1,
112337             height = 1;
112338
112339         if (!me.rowHeight) {
112340             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
112341         }
112342
112343         // If the Store is *locally* filtered, use the filtered count from getCount.
112344         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
112345
112346         if (isNaN(width)) {
112347             width = 1;
112348         }
112349         if (isNaN(height)) {
112350             height = 1;
112351         }
112352         return {
112353             width: width,
112354             height: height
112355         };
112356     },
112357
112358     attemptLoad: function(start, end) {
112359         var me = this;
112360         if (!me.loadTask) {
112361             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
112362         }
112363         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
112364     },
112365
112366     cancelLoad: function() {
112367         if (this.loadTask) {
112368             this.loadTask.cancel();
112369         }
112370     },
112371
112372     doAttemptLoad:  function(start, end) {
112373         var store = this.getPanel().store;
112374         store.guaranteeRange(start, end);
112375     },
112376
112377     setViewScrollTop: function(scrollTop, useMax) {
112378         var me = this,
112379             owner = me.getPanel(),
112380             items = owner.query('tableview'),
112381             i = 0,
112382             len = items.length,
112383             center,
112384             centerEl,
112385             calcScrollTop,
112386             maxScrollTop,
112387             scrollerElDom = me.el.dom;
112388
112389         owner.virtualScrollTop = scrollTop;
112390
112391         center = items[1] || items[0];
112392         centerEl = center.el.dom;
112393
112394         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
112395         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
112396         if (useMax) {
112397             calcScrollTop = maxScrollTop;
112398         }
112399         if (calcScrollTop > maxScrollTop) {
112400             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
112401             return;
112402             // calcScrollTop = maxScrollTop;
112403         }
112404         for (; i < len; i++) {
112405             items[i].el.dom.scrollTop = calcScrollTop;
112406         }
112407     }
112408 });
112409
112410 /**
112411  * @author Nicolas Ferrero
112412  *
112413  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
112414  *
112415  * TablePanel aggregates:
112416  *
112417  *  - a Selection Model
112418  *  - a View
112419  *  - a Store
112420  *  - Scrollers
112421  *  - Ext.grid.header.Container
112422  */
112423 Ext.define('Ext.panel.Table', {
112424     extend: 'Ext.panel.Panel',
112425
112426     alias: 'widget.tablepanel',
112427
112428     uses: [
112429         'Ext.selection.RowModel',
112430         'Ext.grid.Scroller',
112431         'Ext.grid.header.Container',
112432         'Ext.grid.Lockable'
112433     ],
112434
112435     extraBaseCls: Ext.baseCSSPrefix + 'grid',
112436     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
112437
112438     layout: 'fit',
112439     /**
112440      * @property {Boolean} hasView
112441      * True to indicate that a view has been injected into the panel.
112442      */
112443     hasView: false,
112444
112445     // each panel should dictate what viewType and selType to use
112446     /**
112447      * @cfg {String} viewType
112448      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
112449      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
112450      */
112451     viewType: null,
112452
112453     /**
112454      * @cfg {Object} viewConfig
112455      * A config object that will be applied to the grid's UI view. Any of the config options available for
112456      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
112457      */
112458
112459     /**
112460      * @cfg {Ext.view.Table} view
112461      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
112462      * view (instead of creating an entire View instance).
112463      */
112464
112465     /**
112466      * @cfg {String} selType
112467      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
112468      * a config object or nothing at all given in {@link #selModel} config.
112469      */
112470     selType: 'rowmodel',
112471
112472     /**
112473      * @cfg {Ext.selection.Model/Object} selModel
112474      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
112475      * config option determines to which type of selection model this config is applied.
112476      */
112477
112478     /**
112479      * @cfg {Boolean} multiSelect
112480      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112481      */
112482
112483     /**
112484      * @cfg {Boolean} simpleSelect
112485      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112486      */
112487
112488     /**
112489      * @cfg {Ext.data.Store} store (required)
112490      * The {@link Ext.data.Store Store} the grid should use as its data source.
112491      */
112492
112493     /**
112494      * @cfg {Number} scrollDelta
112495      * Number of pixels to scroll when scrolling with mousewheel.
112496      */
112497     scrollDelta: 40,
112498
112499     /**
112500      * @cfg {String/Boolean} scroll
112501      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
112502      * True implies 'both'. False implies 'none'.
112503      */
112504     scroll: true,
112505
112506     /**
112507      * @cfg {Ext.grid.column.Column[]} columns
112508      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
112509      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
112510      * column comes from.
112511      */
112512
112513     /**
112514      * @cfg {Boolean} forceFit
112515      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
112516      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
112517      * content width is used.
112518      */
112519
112520     /**
112521      * @cfg {Ext.grid.feature.Feature[]} features
112522      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
112523      */
112524
112525     /**
112526      * @cfg {Boolean} [hideHeaders=false]
112527      * True to hide column headers.
112528      */
112529
112530     /**
112531      * @cfg {Boolean} deferRowRender
112532      * Defaults to true to enable deferred row rendering.
112533      *
112534      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
112535      * that layouts with GridPanels appear, and lay out more quickly.
112536      */
112537
112538      deferRowRender: true,
112539      
112540     /**
112541      * @cfg {Boolean} sortableColumns
112542      * False to disable column sorting via clicking the header and via the Sorting menu items.
112543      */
112544     sortableColumns: true,
112545
112546     /**
112547      * @cfg {Boolean} [enableLocking=false]
112548      * True to enable locking support for this grid. Alternatively, locking will also be automatically
112549      * enabled if any of the columns in the column configuration contain the locked config option.
112550      */
112551     enableLocking: false,
112552
112553     verticalScrollDock: 'right',
112554     verticalScrollerType: 'gridscroller',
112555
112556     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
112557     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
112558
112559     // private property used to determine where to go down to find views
112560     // this is here to support locking.
112561     scrollerOwner: true,
112562
112563     invalidateScrollerOnRefresh: true,
112564
112565     /**
112566      * @cfg {Boolean} enableColumnMove
112567      * False to disable column dragging within this grid.
112568      */
112569     enableColumnMove: true,
112570
112571     /**
112572      * @cfg {Boolean} enableColumnResize
112573      * False to disable column resizing within this grid.
112574      */
112575     enableColumnResize: true,
112576
112577     /**
112578      * @cfg {Boolean} enableColumnHide
112579      * False to disable column hiding within this grid.
112580      */
112581     enableColumnHide: true,
112582
112583     initComponent: function() {
112584
112585         var me          = this,
112586             scroll      = me.scroll,
112587             vertical    = false,
112588             horizontal  = false,
112589             headerCtCfg = me.columns || me.colModel,
112590             i           = 0,
112591             view,
112592             border = me.border;
112593
112594         if (me.hideHeaders) {
112595             border = false;
112596         }
112597
112598         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
112599         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
112600
112601         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
112602         // Either way, we extract a columns property referencing an array of Column definitions.
112603         if (headerCtCfg instanceof Ext.grid.header.Container) {
112604             me.headerCt = headerCtCfg;
112605             me.headerCt.border = border;
112606             me.columns = me.headerCt.items.items;
112607         } else {
112608             if (Ext.isArray(headerCtCfg)) {
112609                 headerCtCfg = {
112610                     items: headerCtCfg,
112611                     border: border
112612                 };
112613             }
112614             Ext.apply(headerCtCfg, {
112615                 forceFit: me.forceFit,
112616                 sortable: me.sortableColumns,
112617                 enableColumnMove: me.enableColumnMove,
112618                 enableColumnResize: me.enableColumnResize,
112619                 enableColumnHide: me.enableColumnHide,
112620                 border:  border
112621             });
112622             me.columns = headerCtCfg.items;
112623
112624              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
112625              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
112626              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
112627                  me.self.mixin('lockable', Ext.grid.Lockable);
112628                  me.injectLockable();
112629              }
112630         }
112631
112632         me.addEvents(
112633             /**
112634              * @event reconfigure
112635              * Fires after a reconfigure.
112636              * @param {Ext.panel.Table} this
112637              */
112638             'reconfigure',
112639             /**
112640              * @event viewready
112641              * Fires when the grid view is available (use this for selecting a default row).
112642              * @param {Ext.panel.Table} this
112643              */
112644             'viewready',
112645             /**
112646              * @event scrollerhide
112647              * Fires when a scroller is hidden.
112648              * @param {Ext.grid.Scroller} scroller
112649              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112650              */
112651             'scrollerhide',
112652             /**
112653              * @event scrollershow
112654              * Fires when a scroller is shown.
112655              * @param {Ext.grid.Scroller} scroller
112656              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112657              */
112658             'scrollershow'
112659         );
112660
112661         me.bodyCls = me.bodyCls || '';
112662         me.bodyCls += (' ' + me.extraBodyCls);
112663         
112664         me.cls = me.cls || '';
112665         me.cls += (' ' + me.extraBaseCls);
112666
112667         // autoScroll is not a valid configuration
112668         delete me.autoScroll;
112669
112670         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
112671         // than a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI.
112672         if (!me.hasView) {
112673
112674             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
112675             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
112676             if (!me.headerCt) {
112677                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
112678             }
112679
112680             // Extract the array of Column objects
112681             me.columns = me.headerCt.items.items;
112682
112683             if (me.hideHeaders) {
112684                 me.headerCt.height = 0;
112685                 me.headerCt.border = false;
112686                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
112687                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
112688                 // IE Quirks Mode fix
112689                 // If hidden configuration option was used, several layout calculations will be bypassed.
112690                 if (Ext.isIEQuirks) {
112691                     me.headerCt.style = {
112692                         display: 'none'
112693                     };
112694                 }
112695             }
112696
112697             // turn both on.
112698             if (scroll === true || scroll === 'both') {
112699                 vertical = horizontal = true;
112700             } else if (scroll === 'horizontal') {
112701                 horizontal = true;
112702             } else if (scroll === 'vertical') {
112703                 vertical = true;
112704             // All other values become 'none' or false.
112705             } else {
112706                 me.headerCt.availableSpaceOffset = 0;
112707             }
112708
112709             if (vertical) {
112710                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
112711                 me.mon(me.verticalScroller, {
112712                     bodyscroll: me.onVerticalScroll,
112713                     scope: me
112714                 });
112715             }
112716
112717             if (horizontal) {
112718                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
112719                 me.mon(me.horizontalScroller, {
112720                     bodyscroll: me.onHorizontalScroll,
112721                     scope: me
112722                 });
112723             }
112724
112725             me.headerCt.on('resize', me.onHeaderResize, me);
112726             me.relayHeaderCtEvents(me.headerCt);
112727             me.features = me.features || [];
112728             if (!Ext.isArray(me.features)) {
112729                 me.features = [me.features];
112730             }
112731             me.dockedItems = me.dockedItems || [];
112732             me.dockedItems.unshift(me.headerCt);
112733             me.viewConfig = me.viewConfig || {};
112734             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
112735
112736             // AbstractDataView will look up a Store configured as an object
112737             // getView converts viewConfig into a View instance
112738             view = me.getView();
112739
112740             view.on({
112741                 afterrender: function () {
112742                     // hijack the view el's scroll method
112743                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
112744                     // We use to listen to document.body wheel events, but that's a
112745                     // little much. We scope just to the view now.
112746                     me.mon(view.el, {
112747                         mousewheel: me.onMouseWheel,
112748                         scope: me
112749                     });
112750                 },
112751                 single: true
112752             });
112753             me.items = [view];
112754             me.hasView = true;
112755
112756             me.mon(view.store, {
112757                 load: me.onStoreLoad,
112758                 scope: me
112759             });
112760             me.mon(view, {
112761                 viewReady: me.onViewReady,
112762                 resize: me.onViewResize,
112763                 refresh: {
112764                     fn: me.onViewRefresh,
112765                     scope: me,
112766                     buffer: 50
112767                 },
112768                 scope: me
112769             });
112770             this.relayEvents(view, [
112771                 /**
112772                  * @event beforeitemmousedown
112773                  * @alias Ext.view.View#beforeitemmousedown
112774                  */
112775                 'beforeitemmousedown',
112776                 /**
112777                  * @event beforeitemmouseup
112778                  * @alias Ext.view.View#beforeitemmouseup
112779                  */
112780                 'beforeitemmouseup',
112781                 /**
112782                  * @event beforeitemmouseenter
112783                  * @alias Ext.view.View#beforeitemmouseenter
112784                  */
112785                 'beforeitemmouseenter',
112786                 /**
112787                  * @event beforeitemmouseleave
112788                  * @alias Ext.view.View#beforeitemmouseleave
112789                  */
112790                 'beforeitemmouseleave',
112791                 /**
112792                  * @event beforeitemclick
112793                  * @alias Ext.view.View#beforeitemclick
112794                  */
112795                 'beforeitemclick',
112796                 /**
112797                  * @event beforeitemdblclick
112798                  * @alias Ext.view.View#beforeitemdblclick
112799                  */
112800                 'beforeitemdblclick',
112801                 /**
112802                  * @event beforeitemcontextmenu
112803                  * @alias Ext.view.View#beforeitemcontextmenu
112804                  */
112805                 'beforeitemcontextmenu',
112806                 /**
112807                  * @event itemmousedown
112808                  * @alias Ext.view.View#itemmousedown
112809                  */
112810                 'itemmousedown',
112811                 /**
112812                  * @event itemmouseup
112813                  * @alias Ext.view.View#itemmouseup
112814                  */
112815                 'itemmouseup',
112816                 /**
112817                  * @event itemmouseenter
112818                  * @alias Ext.view.View#itemmouseenter
112819                  */
112820                 'itemmouseenter',
112821                 /**
112822                  * @event itemmouseleave
112823                  * @alias Ext.view.View#itemmouseleave
112824                  */
112825                 'itemmouseleave',
112826                 /**
112827                  * @event itemclick
112828                  * @alias Ext.view.View#itemclick
112829                  */
112830                 'itemclick',
112831                 /**
112832                  * @event itemdblclick
112833                  * @alias Ext.view.View#itemdblclick
112834                  */
112835                 'itemdblclick',
112836                 /**
112837                  * @event itemcontextmenu
112838                  * @alias Ext.view.View#itemcontextmenu
112839                  */
112840                 'itemcontextmenu',
112841                 /**
112842                  * @event beforecontainermousedown
112843                  * @alias Ext.view.View#beforecontainermousedown
112844                  */
112845                 'beforecontainermousedown',
112846                 /**
112847                  * @event beforecontainermouseup
112848                  * @alias Ext.view.View#beforecontainermouseup
112849                  */
112850                 'beforecontainermouseup',
112851                 /**
112852                  * @event beforecontainermouseover
112853                  * @alias Ext.view.View#beforecontainermouseover
112854                  */
112855                 'beforecontainermouseover',
112856                 /**
112857                  * @event beforecontainermouseout
112858                  * @alias Ext.view.View#beforecontainermouseout
112859                  */
112860                 'beforecontainermouseout',
112861                 /**
112862                  * @event beforecontainerclick
112863                  * @alias Ext.view.View#beforecontainerclick
112864                  */
112865                 'beforecontainerclick',
112866                 /**
112867                  * @event beforecontainerdblclick
112868                  * @alias Ext.view.View#beforecontainerdblclick
112869                  */
112870                 'beforecontainerdblclick',
112871                 /**
112872                  * @event beforecontainercontextmenu
112873                  * @alias Ext.view.View#beforecontainercontextmenu
112874                  */
112875                 'beforecontainercontextmenu',
112876                 /**
112877                  * @event containermouseup
112878                  * @alias Ext.view.View#containermouseup
112879                  */
112880                 'containermouseup',
112881                 /**
112882                  * @event containermouseover
112883                  * @alias Ext.view.View#containermouseover
112884                  */
112885                 'containermouseover',
112886                 /**
112887                  * @event containermouseout
112888                  * @alias Ext.view.View#containermouseout
112889                  */
112890                 'containermouseout',
112891                 /**
112892                  * @event containerclick
112893                  * @alias Ext.view.View#containerclick
112894                  */
112895                 'containerclick',
112896                 /**
112897                  * @event containerdblclick
112898                  * @alias Ext.view.View#containerdblclick
112899                  */
112900                 'containerdblclick',
112901                 /**
112902                  * @event containercontextmenu
112903                  * @alias Ext.view.View#containercontextmenu
112904                  */
112905                 'containercontextmenu',
112906                 /**
112907                  * @event selectionchange
112908                  * @alias Ext.selection.Model#selectionchange
112909                  */
112910                 'selectionchange',
112911                 /**
112912                  * @event beforeselect
112913                  * @alias Ext.selection.RowModel#beforeselect
112914                  */
112915                 'beforeselect',
112916                 /**
112917                  * @event select
112918                  * @alias Ext.selection.RowModel#select
112919                  */
112920                 'select',
112921                 /**
112922                  * @event beforedeselect
112923                  * @alias Ext.selection.RowModel#beforedeselect
112924                  */
112925                 'beforedeselect',
112926                 /**
112927                  * @event deselect
112928                  * @alias Ext.selection.RowModel#deselect
112929                  */
112930                 'deselect'
112931             ]);
112932         }
112933
112934         me.callParent(arguments);
112935     },
112936     
112937     onRender: function(){
112938         var vScroll = this.verticalScroller,
112939             hScroll = this.horizontalScroller;
112940
112941         if (vScroll) {
112942             vScroll.ensureDimension();
112943         }
112944         if (hScroll) {
112945             hScroll.ensureDimension();
112946         }
112947         this.callParent(arguments);    
112948     },
112949
112950     // state management
112951     initStateEvents: function(){
112952         var events = this.stateEvents;
112953         // push on stateEvents if they don't exist
112954         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
112955             if (Ext.Array.indexOf(events, event)) {
112956                 events.push(event);
112957             }
112958         });
112959         this.callParent();
112960     },
112961
112962     /**
112963      * Returns the horizontal scroller config.
112964      */
112965     initHorizontalScroller: function () {
112966         var me = this,
112967             ret = {
112968                 xtype: 'gridscroller',
112969                 dock: 'bottom',
112970                 section: me,
112971                 store: me.store
112972             };
112973
112974         return ret;
112975     },
112976
112977     /**
112978      * Returns the vertical scroller config.
112979      */
112980     initVerticalScroller: function () {
112981         var me = this,
112982             ret = me.verticalScroller || {};
112983
112984         Ext.applyIf(ret, {
112985             xtype: me.verticalScrollerType,
112986             dock: me.verticalScrollDock,
112987             store: me.store
112988         });
112989
112990         return ret;
112991     },
112992
112993     relayHeaderCtEvents: function (headerCt) {
112994         this.relayEvents(headerCt, [
112995             /**
112996              * @event columnresize
112997              * @alias Ext.grid.header.Container#columnresize
112998              */
112999             'columnresize',
113000             /**
113001              * @event columnmove
113002              * @alias Ext.grid.header.Container#columnmove
113003              */
113004             'columnmove',
113005             /**
113006              * @event columnhide
113007              * @alias Ext.grid.header.Container#columnhide
113008              */
113009             'columnhide',
113010             /**
113011              * @event columnshow
113012              * @alias Ext.grid.header.Container#columnshow
113013              */
113014             'columnshow',
113015             /**
113016              * @event sortchange
113017              * @alias Ext.grid.header.Container#sortchange
113018              */
113019             'sortchange'
113020         ]);
113021     },
113022
113023     getState: function(){
113024         var me = this,
113025             state = me.callParent(),
113026             sorter = me.store.sorters.first();
113027
113028         state.columns = (me.headerCt || me).getColumnsState();
113029
113030         if (sorter) {
113031             state.sort = {
113032                 property: sorter.property,
113033                 direction: sorter.direction
113034             };
113035         }
113036
113037         return state;
113038     },
113039
113040     applyState: function(state) {
113041         var me = this,
113042             sorter = state.sort,
113043             store = me.store,
113044             columns = state.columns;
113045
113046         delete state.columns;
113047
113048         // Ensure superclass has applied *its* state.
113049         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
113050         me.callParent(arguments);
113051
113052         if (columns) {
113053             (me.headerCt || me).applyColumnsState(columns);
113054         }
113055
113056         if (sorter) {
113057             if (store.remoteSort) {
113058                 store.sorters.add(Ext.create('Ext.util.Sorter', {
113059                     property: sorter.property,
113060                     direction: sorter.direction
113061                 }));
113062             }
113063             else {
113064                 store.sort(sorter.property, sorter.direction);
113065             }
113066         }
113067     },
113068
113069     /**
113070      * Returns the store associated with this Panel.
113071      * @return {Ext.data.Store} The store
113072      */
113073     getStore: function(){
113074         return this.store;
113075     },
113076
113077     /**
113078      * Gets the view for this panel.
113079      * @return {Ext.view.Table}
113080      */
113081     getView: function() {
113082         var me = this,
113083             sm;
113084
113085         if (!me.view) {
113086             sm = me.getSelectionModel();
113087             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
113088                 deferInitialRefresh: me.deferRowRender,
113089                 xtype: me.viewType,
113090                 store: me.store,
113091                 headerCt: me.headerCt,
113092                 selModel: sm,
113093                 features: me.features,
113094                 panel: me
113095             }));
113096             me.mon(me.view, {
113097                 uievent: me.processEvent,
113098                 scope: me
113099             });
113100             sm.view = me.view;
113101             me.headerCt.view = me.view;
113102             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
113103         }
113104         return me.view;
113105     },
113106
113107     /**
113108      * @private
113109      * @override
113110      * autoScroll is never valid for all classes which extend TablePanel.
113111      */
113112     setAutoScroll: Ext.emptyFn,
113113
113114     // This method hijacks Ext.view.Table's el scroll method.
113115     // This enables us to keep the virtualized scrollbars in sync
113116     // with the view. It currently does NOT support animation.
113117     elScroll: function(direction, distance, animate) {
113118         var me = this,
113119             scroller;
113120
113121         if (direction === "up" || direction === "left") {
113122             distance = -distance;
113123         }
113124         
113125         if (direction === "down" || direction === "up") {
113126             scroller = me.getVerticalScroller();
113127             
113128             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
113129             if (scroller) {
113130                 scroller.scrollByDeltaY(distance);
113131             }
113132         } else {
113133             scroller = me.getHorizontalScroller();
113134             
113135             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
113136             if (scroller) {
113137                 scroller.scrollByDeltaX(distance);
113138             }
113139         }
113140     },
113141
113142     /**
113143      * @private
113144      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
113145      * @param {String} type Event type, eg 'click'
113146      * @param {Ext.view.Table} view TableView Component
113147      * @param {HTMLElement} cell Cell HtmlElement the event took place within
113148      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
113149      * @param {Number} cellIndex Cell index within the row
113150      * @param {Ext.EventObject} e Original event
113151      */
113152     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
113153         var me = this,
113154             header;
113155
113156         if (cellIndex !== -1) {
113157             header = me.headerCt.getGridColumns()[cellIndex];
113158             return header.processEvent.apply(header, arguments);
113159         }
113160     },
113161
113162     /**
113163      * Requests a recalculation of scrollbars and puts them in if they are needed.
113164      */
113165     determineScrollbars: function() {
113166         // Set a flag so that afterComponentLayout does not recurse back into here.
113167         if (this.determineScrollbarsRunning) {
113168             return;
113169         }
113170         this.determineScrollbarsRunning = true;
113171         var me = this,
113172             view = me.view,
113173             box,
113174             tableEl,
113175             scrollWidth,
113176             clientWidth,
113177             scrollHeight,
113178             clientHeight,
113179             verticalScroller = me.verticalScroller,
113180             horizontalScroller = me.horizontalScroller,
113181             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
113182                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
113183             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
113184
113185         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
113186         if (!me.collapsed && view && view.viewReady) {
113187
113188             // Calculate maximum, *scrollbarless* space which the view has available.
113189             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
113190             box = view.el.getSize();
113191
113192             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
113193             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
113194
113195             // Calculate the width of the scrolling block
113196             // There will never be a horizontal scrollbar if all columns are flexed.
113197
113198             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
113199
113200             // Calculate the height of the scrolling block
113201             if (verticalScroller && verticalScroller.el) {
113202                 scrollHeight = verticalScroller.getSizeCalculation().height;
113203             } else {
113204                 tableEl = view.el.child('table', true);
113205                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
113206             }
113207
113208             // View is too high.
113209             // Definitely need a vertical scrollbar
113210             if (scrollHeight > clientHeight) {
113211                 reqScrollbars = 1;
113212
113213                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
113214                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
113215                     reqScrollbars = 3;
113216                 }
113217             }
113218
113219             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
113220             else {
113221                 // View is too wide.
113222                 // Definitely need a horizontal scrollbar
113223                 if (scrollWidth > clientWidth) {
113224                     reqScrollbars = 2;
113225
113226                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
113227                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
113228                         reqScrollbars = 3;
113229                     }
113230                 }
113231             }
113232
113233             // If scrollbar requirements have changed, change 'em...
113234             if (reqScrollbars !== curScrollbars) {
113235
113236                 // Suspend component layout while we add/remove the docked scrollers
113237                 me.suspendLayout = true;
113238                 if (reqScrollbars & 1) {
113239                     me.showVerticalScroller();
113240                 } else {
113241                     me.hideVerticalScroller();
113242                 }
113243                 if (reqScrollbars & 2) {
113244                     me.showHorizontalScroller();
113245                 } else {
113246                     me.hideHorizontalScroller();
113247                 }
113248                 me.suspendLayout = false;
113249
113250                 // Lay out the Component.
113251                 me.doComponentLayout();
113252                 // Lay out me.items
113253                 me.getLayout().layout();
113254             }
113255         }
113256         delete me.determineScrollbarsRunning;
113257     },
113258
113259     onViewResize: function() {
113260         this.determineScrollbars();
113261     },
113262
113263     afterComponentLayout: function() {
113264         this.callParent(arguments);
113265         this.determineScrollbars();
113266         this.invalidateScroller();
113267     },
113268
113269     onHeaderResize: function() {
113270         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
113271             this.determineScrollbars();
113272             this.invalidateScroller();
113273         }
113274     },
113275
113276     afterCollapse: function() {
113277         var me = this;
113278         if (me.verticalScroller) {
113279             me.verticalScroller.saveScrollPos();
113280         }
113281         if (me.horizontalScroller) {
113282             me.horizontalScroller.saveScrollPos();
113283         }
113284         me.callParent(arguments);
113285     },
113286
113287     afterExpand: function() {
113288         var me = this;
113289         me.callParent(arguments);
113290         if (me.verticalScroller) {
113291             me.verticalScroller.restoreScrollPos();
113292         }
113293         if (me.horizontalScroller) {
113294             me.horizontalScroller.restoreScrollPos();
113295         }
113296     },
113297
113298     /**
113299      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
113300      */
113301     hideHorizontalScroller: function() {
113302         var me = this;
113303
113304         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
113305             me.verticalScroller.setReservedSpace(0);
113306             me.removeDocked(me.horizontalScroller, false);
113307             me.removeCls(me.horizontalScrollerPresentCls);
113308             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
113309         }
113310
113311     },
113312
113313     /**
113314      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
113315      */
113316     showHorizontalScroller: function() {
113317         var me = this;
113318
113319         if (me.verticalScroller) {
113320             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
113321         }
113322         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
113323             me.addDocked(me.horizontalScroller);
113324             me.addCls(me.horizontalScrollerPresentCls);
113325             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
113326         }
113327     },
113328
113329     /**
113330      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
113331      */
113332     hideVerticalScroller: function() {
113333         var me = this;
113334
113335         me.setHeaderReserveOffset(false);
113336         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
113337             me.removeDocked(me.verticalScroller, false);
113338             me.removeCls(me.verticalScrollerPresentCls);
113339             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
113340         }
113341     },
113342
113343     /**
113344      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
113345      */
113346     showVerticalScroller: function() {
113347         var me = this;
113348
113349         me.setHeaderReserveOffset(true);
113350         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
113351             me.addDocked(me.verticalScroller);
113352             me.addCls(me.verticalScrollerPresentCls);
113353             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
113354         }
113355     },
113356
113357     setHeaderReserveOffset: function (reserveOffset) {
113358         var headerCt = this.headerCt,
113359             layout = headerCt.layout;
113360
113361         // only trigger a layout when reserveOffset is changing
113362         if (layout && layout.reserveOffset !== reserveOffset) {
113363             layout.reserveOffset = reserveOffset;
113364             if (!this.suspendLayout) {
113365                 headerCt.doLayout();
113366             }
113367         }
113368     },
113369
113370     /**
113371      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
113372      */
113373     invalidateScroller: function() {
113374         var me = this,
113375             vScroll = me.verticalScroller,
113376             hScroll = me.horizontalScroller;
113377
113378         if (vScroll) {
113379             vScroll.invalidate();
113380         }
113381         if (hScroll) {
113382             hScroll.invalidate();
113383         }
113384     },
113385
113386     // refresh the view when a header moves
113387     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
113388         this.view.refresh();
113389     },
113390
113391     // Section onHeaderHide is invoked after view.
113392     onHeaderHide: function(headerCt, header) {
113393         this.invalidateScroller();
113394     },
113395
113396     onHeaderShow: function(headerCt, header) {
113397         this.invalidateScroller();
113398     },
113399
113400     getVerticalScroller: function() {
113401         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
113402     },
113403
113404     getHorizontalScroller: function() {
113405         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
113406     },
113407
113408     onMouseWheel: function(e) {
113409         var me = this,
113410             vertScroller = me.getVerticalScroller(),
113411             horizScroller = me.getHorizontalScroller(),
113412             scrollDelta = -me.scrollDelta,
113413             deltas = e.getWheelDeltas(),
113414             deltaX = scrollDelta * deltas.x,
113415             deltaY = scrollDelta * deltas.y,
113416             vertScrollerEl, horizScrollerEl,
113417             vertScrollerElDom, horizScrollerElDom,
113418             horizontalCanScrollLeft, horizontalCanScrollRight,
113419             verticalCanScrollDown, verticalCanScrollUp;
113420
113421         // calculate whether or not both scrollbars can scroll right/left and up/down
113422         if (horizScroller) {
113423             horizScrollerEl = horizScroller.scrollEl;
113424             if (horizScrollerEl) {
113425                 horizScrollerElDom = horizScrollerEl.dom;
113426                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
113427                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
113428             }
113429         }
113430         if (vertScroller) {
113431             vertScrollerEl = vertScroller.scrollEl;
113432             if (vertScrollerEl) {
113433                 vertScrollerElDom = vertScrollerEl.dom;
113434                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
113435                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
113436             }
113437         }
113438
113439         if (horizScroller) {
113440             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
113441                 e.stopEvent();
113442                 horizScroller.scrollByDeltaX(deltaX);
113443             }
113444         }
113445         if (vertScroller) {
113446             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
113447                 e.stopEvent();
113448                 vertScroller.scrollByDeltaY(deltaY);
113449             }
113450         }
113451     },
113452
113453     /**
113454      * @private
113455      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
113456      */
113457     onViewReady: function() {
113458         var me = this;
113459         me.fireEvent('viewready', me);
113460         if (me.deferRowRender) {
113461             me.determineScrollbars();
113462             me.invalidateScroller();
113463         }
113464     },
113465
113466     /**
113467      * @private
113468      * Determines and invalidates scrollers on view refresh
113469      */
113470     onViewRefresh: function() {
113471         var me = this;
113472
113473         // Refresh *during* render must be ignored.
113474         if (!me.rendering) {
113475             this.determineScrollbars();
113476             if (this.invalidateScrollerOnRefresh) {
113477                 this.invalidateScroller();
113478             }
113479         }
113480     },
113481
113482     /**
113483      * Sets the scrollTop of the TablePanel.
113484      * @param {Number} top
113485      */
113486     setScrollTop: function(top) {
113487         var me               = this,
113488             rootCmp          = me.getScrollerOwner(),
113489             verticalScroller = me.getVerticalScroller();
113490
113491         rootCmp.virtualScrollTop = top;
113492         if (verticalScroller) {
113493             verticalScroller.setScrollTop(top);
113494         }
113495     },
113496
113497     getScrollerOwner: function() {
113498         var rootCmp = this;
113499         if (!this.scrollerOwner) {
113500             rootCmp = this.up('[scrollerOwner]');
113501         }
113502         return rootCmp;
113503     },
113504
113505     /**
113506      * Scrolls the TablePanel by deltaY
113507      * @param {Number} deltaY
113508      */
113509     scrollByDeltaY: function(deltaY) {
113510         var verticalScroller = this.getVerticalScroller();
113511
113512         if (verticalScroller) {
113513             verticalScroller.scrollByDeltaY(deltaY);
113514         }
113515     },
113516
113517     /**
113518      * Scrolls the TablePanel by deltaX
113519      * @param {Number} deltaX
113520      */
113521     scrollByDeltaX: function(deltaX) {
113522         var horizontalScroller = this.getHorizontalScroller();
113523
113524         if (horizontalScroller) {
113525             horizontalScroller.scrollByDeltaX(deltaX);
113526         }
113527     },
113528
113529     /**
113530      * Gets left hand side marker for header resizing.
113531      * @private
113532      */
113533     getLhsMarker: function() {
113534         var me = this;
113535
113536         if (!me.lhsMarker) {
113537             me.lhsMarker = Ext.DomHelper.append(me.el, {
113538                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113539             }, true);
113540         }
113541         return me.lhsMarker;
113542     },
113543
113544     /**
113545      * Gets right hand side marker for header resizing.
113546      * @private
113547      */
113548     getRhsMarker: function() {
113549         var me = this;
113550
113551         if (!me.rhsMarker) {
113552             me.rhsMarker = Ext.DomHelper.append(me.el, {
113553                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113554             }, true);
113555         }
113556         return me.rhsMarker;
113557     },
113558
113559     /**
113560      * Returns the selection model being used and creates it via the configuration if it has not been created already.
113561      * @return {Ext.selection.Model} selModel
113562      */
113563     getSelectionModel: function(){
113564         if (!this.selModel) {
113565             this.selModel = {};
113566         }
113567
113568         var mode = 'SINGLE',
113569             type;
113570         if (this.simpleSelect) {
113571             mode = 'SIMPLE';
113572         } else if (this.multiSelect) {
113573             mode = 'MULTI';
113574         }
113575
113576         Ext.applyIf(this.selModel, {
113577             allowDeselect: this.allowDeselect,
113578             mode: mode
113579         });
113580
113581         if (!this.selModel.events) {
113582             type = this.selModel.selType || this.selType;
113583             this.selModel = Ext.create('selection.' + type, this.selModel);
113584         }
113585
113586         if (!this.selModel.hasRelaySetup) {
113587             this.relayEvents(this.selModel, [
113588                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
113589             ]);
113590             this.selModel.hasRelaySetup = true;
113591         }
113592
113593         // lock the selection model if user
113594         // has disabled selection
113595         if (this.disableSelection) {
113596             this.selModel.locked = true;
113597         }
113598         return this.selModel;
113599     },
113600
113601     onVerticalScroll: function(event, target) {
113602         var owner = this.getScrollerOwner(),
113603             items = owner.query('tableview'),
113604             i = 0,
113605             len = items.length;
113606
113607         for (; i < len; i++) {
113608             items[i].el.dom.scrollTop = target.scrollTop;
113609         }
113610     },
113611
113612     onHorizontalScroll: function(event, target) {
113613         var owner = this.getScrollerOwner(),
113614             items = owner.query('tableview'),
113615             center = items[1] || items[0];
113616
113617         center.el.dom.scrollLeft = target.scrollLeft;
113618         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
113619     },
113620
113621     // template method meant to be overriden
113622     onStoreLoad: Ext.emptyFn,
113623
113624     getEditorParent: function() {
113625         return this.body;
113626     },
113627
113628     bindStore: function(store) {
113629         var me = this;
113630         me.store = store;
113631         me.getView().bindStore(store);
113632     },
113633     
113634     beforeDestroy: function(){
113635         // may be some duplication here since the horizontal and vertical
113636         // scroller may be part of the docked items, but we need to clean
113637         // them up in case they aren't visible.
113638         Ext.destroy(this.horizontalScroller, this.verticalScroller);
113639         this.callParent();
113640     },
113641
113642     /**
113643      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
113644      * to change them.
113645      * @param {Ext.data.Store} store (Optional) The new store.
113646      * @param {Object[]} columns (Optional) An array of column configs
113647      */
113648     reconfigure: function(store, columns) {
113649         var me = this,
113650             headerCt = me.headerCt;
113651
113652         if (me.lockable) {
113653             me.reconfigureLockable(store, columns);
113654         } else {
113655             if (columns) {
113656                 headerCt.suspendLayout = true;
113657                 headerCt.removeAll();
113658                 headerCt.add(columns);
113659             }
113660             if (store) {
113661                 store = Ext.StoreManager.lookup(store);
113662                 me.bindStore(store);
113663             } else {
113664                 me.getView().refresh();
113665             }
113666             if (columns) {
113667                 headerCt.suspendLayout = false;
113668                 me.forceComponentLayout();
113669             }
113670         }
113671         me.fireEvent('reconfigure', me);
113672     }
113673 });
113674 /**
113675  * This class encapsulates the user interface for a tabular data set.
113676  * It acts as a centralized manager for controlling the various interface
113677  * elements of the view. This includes handling events, such as row and cell
113678  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
113679  * to provide visual feedback to the user.
113680  *
113681  * This class does not provide ways to manipulate the underlying data of the configured
113682  * {@link Ext.data.Store}.
113683  *
113684  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
113685  * to be used directly.
113686  */
113687 Ext.define('Ext.view.Table', {
113688     extend: 'Ext.view.View',
113689     alias: 'widget.tableview',
113690     uses: [
113691         'Ext.view.TableChunker',
113692         'Ext.util.DelayedTask',
113693         'Ext.util.MixedCollection'
113694     ],
113695
113696     baseCls: Ext.baseCSSPrefix + 'grid-view',
113697
113698     // row
113699     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
113700     // cell
113701     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
113702
113703     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
113704     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
113705     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
113706     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
113707     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
113708     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
113709     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
113710
113711     // cfg docs inherited
113712     trackOver: true,
113713
113714     /**
113715      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
113716      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
113717      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
113718      * Example usage:
113719      *
113720      *     viewConfig: {
113721      *         getRowClass: function(record, rowIndex, rowParams, store){
113722      *             return record.get("valid") ? "row-valid" : "row-error";
113723      *         }
113724      *     }
113725      *
113726      * @param {Ext.data.Model} record The record corresponding to the current row.
113727      * @param {Number} index The row index.
113728      * @param {Object} rowParams **DEPRECATED.** For row body use the
113729      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
113730      * @param {Ext.data.Store} store The store this grid is bound to
113731      * @return {String} a CSS class name to add to the row.
113732      * @method
113733      */
113734     getRowClass: null,
113735
113736     initComponent: function() {
113737         var me = this;
113738
113739         me.scrollState = {};
113740         me.selModel.view = me;
113741         me.headerCt.view = me;
113742         me.initFeatures();
113743         me.tpl = '<div></div>';
113744         me.callParent();
113745         me.mon(me.store, {
113746             load: me.onStoreLoad,
113747             scope: me
113748         });
113749
113750         // this.addEvents(
113751         //     /**
113752         //      * @event rowfocus
113753         //      * @param {Ext.data.Model} record
113754         //      * @param {HTMLElement} row
113755         //      * @param {Number} rowIdx
113756         //      */
113757         //     'rowfocus'
113758         // );
113759     },
113760
113761     // scroll to top of the grid when store loads
113762     onStoreLoad: function(){
113763         var me = this;
113764
113765         if (me.invalidateScrollerOnRefresh) {
113766             if (Ext.isGecko) {
113767                 if (!me.scrollToTopTask) {
113768                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
113769                 }
113770                 me.scrollToTopTask.delay(1);
113771             } else {
113772                 me    .scrollToTop();
113773             }
113774         }
113775     },
113776
113777     // scroll the view to the top
113778     scrollToTop: Ext.emptyFn,
113779
113780     /**
113781      * Add a listener to the main view element. It will be destroyed with the view.
113782      * @private
113783      */
113784     addElListener: function(eventName, fn, scope){
113785         this.mon(this, eventName, fn, scope, {
113786             element: 'el'
113787         });
113788     },
113789
113790     /**
113791      * Get the columns used for generating a template via TableChunker.
113792      * See {@link Ext.grid.header.Container#getGridColumns}.
113793      * @private
113794      */
113795     getGridColumns: function() {
113796         return this.headerCt.getGridColumns();
113797     },
113798
113799     /**
113800      * Get a leaf level header by index regardless of what the nesting
113801      * structure is.
113802      * @private
113803      * @param {Number} index The index
113804      */
113805     getHeaderAtIndex: function(index) {
113806         return this.headerCt.getHeaderAtIndex(index);
113807     },
113808
113809     /**
113810      * Get the cell (td) for a particular record and column.
113811      * @param {Ext.data.Model} record
113812      * @param {Ext.grid.column.Column} column
113813      * @private
113814      */
113815     getCell: function(record, column) {
113816         var row = this.getNode(record);
113817         return Ext.fly(row).down(column.getCellSelector());
113818     },
113819
113820     /**
113821      * Get a reference to a feature
113822      * @param {String} id The id of the feature
113823      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
113824      */
113825     getFeature: function(id) {
113826         var features = this.featuresMC;
113827         if (features) {
113828             return features.get(id);
113829         }
113830     },
113831
113832     /**
113833      * Initializes each feature and bind it to this view.
113834      * @private
113835      */
113836     initFeatures: function() {
113837         var me = this,
113838             i = 0,
113839             features,
113840             len;
113841
113842         me.features = me.features || [];
113843         features = me.features;
113844         len = features.length;
113845
113846         me.featuresMC = Ext.create('Ext.util.MixedCollection');
113847         for (; i < len; i++) {
113848             // ensure feature hasnt already been instantiated
113849             if (!features[i].isFeature) {
113850                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
113851             }
113852             // inject a reference to view
113853             features[i].view = me;
113854             me.featuresMC.add(features[i]);
113855         }
113856     },
113857
113858     /**
113859      * Gives features an injection point to attach events to the markup that
113860      * has been created for this view.
113861      * @private
113862      */
113863     attachEventsForFeatures: function() {
113864         var features = this.features,
113865             ln       = features.length,
113866             i        = 0;
113867
113868         for (; i < ln; i++) {
113869             if (features[i].isFeature) {
113870                 features[i].attachEvents();
113871             }
113872         }
113873     },
113874
113875     afterRender: function() {
113876         var me = this;
113877
113878         me.callParent();
113879         me.mon(me.el, {
113880             scroll: me.fireBodyScroll,
113881             scope: me
113882         });
113883         me.el.unselectable();
113884         me.attachEventsForFeatures();
113885     },
113886
113887     fireBodyScroll: function(e, t) {
113888         this.fireEvent('bodyscroll', e, t);
113889     },
113890
113891     // TODO: Refactor headerCt dependency here to colModel
113892     /**
113893      * Uses the headerCt to transform data from dataIndex keys in a record to
113894      * headerId keys in each header and then run them through each feature to
113895      * get additional data for variables they have injected into the view template.
113896      * @private
113897      */
113898     prepareData: function(data, idx, record) {
113899         var me       = this,
113900             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
113901             features = me.features,
113902             ln       = features.length,
113903             i        = 0,
113904             node, feature;
113905
113906         for (; i < ln; i++) {
113907             feature = features[i];
113908             if (feature.isFeature) {
113909                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
113910             }
113911         }
113912
113913         return orig;
113914     },
113915
113916     // TODO: Refactor headerCt dependency here to colModel
113917     collectData: function(records, startIndex) {
113918         var preppedRecords = this.callParent(arguments),
113919             headerCt  = this.headerCt,
113920             fullWidth = headerCt.getFullWidth(),
113921             features  = this.features,
113922             ln = features.length,
113923             o = {
113924                 rows: preppedRecords,
113925                 fullWidth: fullWidth
113926             },
113927             i  = 0,
113928             feature,
113929             j = 0,
113930             jln,
113931             rowParams;
113932
113933         jln = preppedRecords.length;
113934         // process row classes, rowParams has been deprecated and has been moved
113935         // to the individual features that implement the behavior.
113936         if (this.getRowClass) {
113937             for (; j < jln; j++) {
113938                 rowParams = {};
113939                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
113940             }
113941         }
113942         // currently only one feature may implement collectData. This is to modify
113943         // what's returned to the view before its rendered
113944         for (; i < ln; i++) {
113945             feature = features[i];
113946             if (feature.isFeature && feature.collectData && !feature.disabled) {
113947                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
113948                 break;
113949             }
113950         }
113951         return o;
113952     },
113953
113954     // TODO: Refactor header resizing to column resizing
113955     /**
113956      * When a header is resized, setWidth on the individual columns resizer class,
113957      * the top level table, save/restore scroll state, generate a new template and
113958      * restore focus to the grid view's element so that keyboard navigation
113959      * continues to work.
113960      * @private
113961      */
113962     onHeaderResize: function(header, w, suppressFocus) {
113963         var me = this,
113964             el = me.el;
113965
113966         if (el) {
113967             me.saveScrollState();
113968             // Grab the col and set the width, css
113969             // class is generated in TableChunker.
113970             // Select composites because there may be several chunks.
113971
113972             // IE6 and IE7 bug.
113973             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
113974             // We need to increment the passed with in this case.
113975             if (Ext.isIE6 || Ext.isIE7) {
113976                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
113977                     w += 1;
113978                 }
113979             }
113980             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
113981             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
113982             me.restoreScrollState();
113983             if (!me.ignoreTemplate) {
113984                 me.setNewTemplate();
113985             }
113986             if (!suppressFocus) {
113987                 me.el.focus();
113988             }
113989         }
113990     },
113991
113992     /**
113993      * When a header is shown restore its oldWidth if it was previously hidden.
113994      * @private
113995      */
113996     onHeaderShow: function(headerCt, header, suppressFocus) {
113997         var me = this;
113998         me.ignoreTemplate = true;
113999         // restore headers that were dynamically hidden
114000         if (header.oldWidth) {
114001             me.onHeaderResize(header, header.oldWidth, suppressFocus);
114002             delete header.oldWidth;
114003         // flexed headers will have a calculated size set
114004         // this additional check has to do with the fact that
114005         // defaults: {width: 100} will fight with a flex value
114006         } else if (header.width && !header.flex) {
114007             me.onHeaderResize(header, header.width, suppressFocus);
114008         }
114009         delete me.ignoreTemplate;
114010         me.setNewTemplate();
114011     },
114012
114013     /**
114014      * When the header hides treat it as a resize to 0.
114015      * @private
114016      */
114017     onHeaderHide: function(headerCt, header, suppressFocus) {
114018         this.onHeaderResize(header, 0, suppressFocus);
114019     },
114020
114021     /**
114022      * Set a new template based on the current columns displayed in the
114023      * grid.
114024      * @private
114025      */
114026     setNewTemplate: function() {
114027         var me = this,
114028             columns = me.headerCt.getColumnsForTpl(true);
114029
114030         me.tpl = me.getTableChunker().getTableTpl({
114031             columns: columns,
114032             features: me.features
114033         });
114034     },
114035
114036     /**
114037      * Returns the configured chunker or default of Ext.view.TableChunker
114038      */
114039     getTableChunker: function() {
114040         return this.chunker || Ext.view.TableChunker;
114041     },
114042
114043     /**
114044      * Adds a CSS Class to a specific row.
114045      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114046      * representing this row
114047      * @param {String} cls
114048      */
114049     addRowCls: function(rowInfo, cls) {
114050         var row = this.getNode(rowInfo);
114051         if (row) {
114052             Ext.fly(row).addCls(cls);
114053         }
114054     },
114055
114056     /**
114057      * Removes a CSS Class from a specific row.
114058      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114059      * representing this row
114060      * @param {String} cls
114061      */
114062     removeRowCls: function(rowInfo, cls) {
114063         var row = this.getNode(rowInfo);
114064         if (row) {
114065             Ext.fly(row).removeCls(cls);
114066         }
114067     },
114068
114069     // GridSelectionModel invokes onRowSelect as selection changes
114070     onRowSelect : function(rowIdx) {
114071         this.addRowCls(rowIdx, this.selectedItemCls);
114072     },
114073
114074     // GridSelectionModel invokes onRowDeselect as selection changes
114075     onRowDeselect : function(rowIdx) {
114076         var me = this;
114077
114078         me.removeRowCls(rowIdx, me.selectedItemCls);
114079         me.removeRowCls(rowIdx, me.focusedItemCls);
114080     },
114081
114082     onCellSelect: function(position) {
114083         var cell = this.getCellByPosition(position);
114084         if (cell) {
114085             cell.addCls(this.selectedCellCls);
114086         }
114087     },
114088
114089     onCellDeselect: function(position) {
114090         var cell = this.getCellByPosition(position);
114091         if (cell) {
114092             cell.removeCls(this.selectedCellCls);
114093         }
114094
114095     },
114096
114097     onCellFocus: function(position) {
114098         //var cell = this.getCellByPosition(position);
114099         this.focusCell(position);
114100     },
114101
114102     getCellByPosition: function(position) {
114103         var row    = position.row,
114104             column = position.column,
114105             store  = this.store,
114106             node   = this.getNode(row),
114107             header = this.headerCt.getHeaderAtIndex(column),
114108             cellSelector,
114109             cell = false;
114110
114111         if (header && node) {
114112             cellSelector = header.getCellSelector();
114113             cell = Ext.fly(node).down(cellSelector);
114114         }
114115         return cell;
114116     },
114117
114118     // GridSelectionModel invokes onRowFocus to 'highlight'
114119     // the last row focused
114120     onRowFocus: function(rowIdx, highlight, supressFocus) {
114121         var me = this,
114122             row = me.getNode(rowIdx);
114123
114124         if (highlight) {
114125             me.addRowCls(rowIdx, me.focusedItemCls);
114126             if (!supressFocus) {
114127                 me.focusRow(rowIdx);
114128             }
114129             //this.el.dom.setAttribute('aria-activedescendant', row.id);
114130         } else {
114131             me.removeRowCls(rowIdx, me.focusedItemCls);
114132         }
114133     },
114134
114135     /**
114136      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
114137      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
114138      * An HTMLElement template node, index of a template node, the id of a template node or the
114139      * record associated with the node.
114140      */
114141     focusRow: function(rowIdx) {
114142         var me         = this,
114143             row        = me.getNode(rowIdx),
114144             el         = me.el,
114145             adjustment = 0,
114146             panel      = me.ownerCt,
114147             rowRegion,
114148             elRegion,
114149             record;
114150
114151         if (row && el) {
114152             elRegion  = el.getRegion();
114153             rowRegion = Ext.fly(row).getRegion();
114154             // row is above
114155             if (rowRegion.top < elRegion.top) {
114156                 adjustment = rowRegion.top - elRegion.top;
114157             // row is below
114158             } else if (rowRegion.bottom > elRegion.bottom) {
114159                 adjustment = rowRegion.bottom - elRegion.bottom;
114160             }
114161             record = me.getRecord(row);
114162             rowIdx = me.store.indexOf(record);
114163
114164             if (adjustment) {
114165                 // scroll the grid itself, so that all gridview's update.
114166                 panel.scrollByDeltaY(adjustment);
114167             }
114168             me.fireEvent('rowfocus', record, row, rowIdx);
114169         }
114170     },
114171
114172     focusCell: function(position) {
114173         var me          = this,
114174             cell        = me.getCellByPosition(position),
114175             el          = me.el,
114176             adjustmentY = 0,
114177             adjustmentX = 0,
114178             elRegion    = el.getRegion(),
114179             panel       = me.ownerCt,
114180             cellRegion,
114181             record;
114182
114183         if (cell) {
114184             cellRegion = cell.getRegion();
114185             // cell is above
114186             if (cellRegion.top < elRegion.top) {
114187                 adjustmentY = cellRegion.top - elRegion.top;
114188             // cell is below
114189             } else if (cellRegion.bottom > elRegion.bottom) {
114190                 adjustmentY = cellRegion.bottom - elRegion.bottom;
114191             }
114192
114193             // cell is left
114194             if (cellRegion.left < elRegion.left) {
114195                 adjustmentX = cellRegion.left - elRegion.left;
114196             // cell is right
114197             } else if (cellRegion.right > elRegion.right) {
114198                 adjustmentX = cellRegion.right - elRegion.right;
114199             }
114200
114201             if (adjustmentY) {
114202                 // scroll the grid itself, so that all gridview's update.
114203                 panel.scrollByDeltaY(adjustmentY);
114204             }
114205             if (adjustmentX) {
114206                 panel.scrollByDeltaX(adjustmentX);
114207             }
114208             el.focus();
114209             me.fireEvent('cellfocus', record, cell, position);
114210         }
114211     },
114212
114213     /**
114214      * Scrolls by delta. This affects this individual view ONLY and does not
114215      * synchronize across views or scrollers.
114216      * @param {Number} delta
114217      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
114218      * @private
114219      */
114220     scrollByDelta: function(delta, dir) {
114221         dir = dir || 'scrollTop';
114222         var elDom = this.el.dom;
114223         elDom[dir] = (elDom[dir] += delta);
114224     },
114225
114226     onUpdate: function(ds, index) {
114227         this.callParent(arguments);
114228     },
114229
114230     /**
114231      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
114232      */
114233     saveScrollState: function() {
114234         if (this.rendered) {
114235             var dom = this.el.dom, 
114236                 state = this.scrollState;
114237             
114238             state.left = dom.scrollLeft;
114239             state.top = dom.scrollTop;
114240         }
114241     },
114242
114243     /**
114244      * Restores the scrollState.
114245      * Must be used in conjunction with saveScrollState
114246      * @private
114247      */
114248     restoreScrollState: function() {
114249         if (this.rendered) {
114250             var dom = this.el.dom, 
114251                 state = this.scrollState, 
114252                 headerEl = this.headerCt.el.dom;
114253             
114254             headerEl.scrollLeft = dom.scrollLeft = state.left;
114255             dom.scrollTop = state.top;
114256         }
114257     },
114258
114259     /**
114260      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
114261      * invalidates the scrollers.
114262      */
114263     refresh: function() {
114264         this.setNewTemplate();
114265         this.callParent(arguments);
114266     },
114267
114268     processItemEvent: function(record, row, rowIndex, e) {
114269         var me = this,
114270             cell = e.getTarget(me.cellSelector, row),
114271             cellIndex = cell ? cell.cellIndex : -1,
114272             map = me.statics().EventMap,
114273             selModel = me.getSelectionModel(),
114274             type = e.type,
114275             result;
114276
114277         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
114278             // CellModel, otherwise we can't tell which cell to invoke
114279             cell = me.getCellByPosition(selModel.getCurrentPosition());
114280             if (cell) {
114281                 cell = cell.dom;
114282                 cellIndex = cell.cellIndex;
114283             }
114284         }
114285
114286         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
114287
114288         if (result === false || me.callParent(arguments) === false) {
114289             return false;
114290         }
114291
114292         // Don't handle cellmouseenter and cellmouseleave events for now
114293         if (type == 'mouseover' || type == 'mouseout') {
114294             return true;
114295         }
114296
114297         return !(
114298             // We are adding cell and feature events
114299             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114300             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
114301             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114302             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
114303         );
114304     },
114305
114306     processSpecialEvent: function(e) {
114307         var me = this,
114308             map = me.statics().EventMap,
114309             features = me.features,
114310             ln = features.length,
114311             type = e.type,
114312             i, feature, prefix, featureTarget,
114313             beforeArgs, args,
114314             panel = me.ownerCt;
114315
114316         me.callParent(arguments);
114317
114318         if (type == 'mouseover' || type == 'mouseout') {
114319             return;
114320         }
114321
114322         for (i = 0; i < ln; i++) {
114323             feature = features[i];
114324             if (feature.hasFeatureEvent) {
114325                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
114326                 if (featureTarget) {
114327                     prefix = feature.eventPrefix;
114328                     // allows features to implement getFireEventArgs to change the
114329                     // fireEvent signature
114330                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
114331                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
114332
114333                     if (
114334                         // before view event
114335                         (me.fireEvent.apply(me, beforeArgs) === false) ||
114336                         // panel grid event
114337                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
114338                         // view event
114339                         (me.fireEvent.apply(me, args) === false) ||
114340                         // panel event
114341                         (panel.fireEvent.apply(panel, args) === false)
114342                     ) {
114343                         return false;
114344                     }
114345                 }
114346             }
114347         }
114348         return true;
114349     },
114350
114351     onCellMouseDown: Ext.emptyFn,
114352     onCellMouseUp: Ext.emptyFn,
114353     onCellClick: Ext.emptyFn,
114354     onCellDblClick: Ext.emptyFn,
114355     onCellContextMenu: Ext.emptyFn,
114356     onCellKeyDown: Ext.emptyFn,
114357     onBeforeCellMouseDown: Ext.emptyFn,
114358     onBeforeCellMouseUp: Ext.emptyFn,
114359     onBeforeCellClick: Ext.emptyFn,
114360     onBeforeCellDblClick: Ext.emptyFn,
114361     onBeforeCellContextMenu: Ext.emptyFn,
114362     onBeforeCellKeyDown: Ext.emptyFn,
114363
114364     /**
114365      * Expands a particular header to fit the max content width.
114366      * This will ONLY expand, not contract.
114367      * @private
114368      */
114369     expandToFit: function(header) {
114370         if (header) {
114371             var maxWidth = this.getMaxContentWidth(header);
114372             delete header.flex;
114373             header.setWidth(maxWidth);
114374         }
114375     },
114376
114377     /**
114378      * Returns the max contentWidth of the header's text and all cells
114379      * in the grid under this header.
114380      * @private
114381      */
114382     getMaxContentWidth: function(header) {
114383         var cellSelector = header.getCellInnerSelector(),
114384             cells        = this.el.query(cellSelector),
114385             i = 0,
114386             ln = cells.length,
114387             maxWidth = header.el.dom.scrollWidth,
114388             scrollWidth;
114389
114390         for (; i < ln; i++) {
114391             scrollWidth = cells[i].scrollWidth;
114392             if (scrollWidth > maxWidth) {
114393                 maxWidth = scrollWidth;
114394             }
114395         }
114396         return maxWidth;
114397     },
114398
114399     getPositionByEvent: function(e) {
114400         var me       = this,
114401             cellNode = e.getTarget(me.cellSelector),
114402             rowNode  = e.getTarget(me.itemSelector),
114403             record   = me.getRecord(rowNode),
114404             header   = me.getHeaderByCell(cellNode);
114405
114406         return me.getPosition(record, header);
114407     },
114408
114409     getHeaderByCell: function(cell) {
114410         if (cell) {
114411             var m = cell.className.match(this.cellRe);
114412             if (m && m[1]) {
114413                 return Ext.getCmp(m[1]);
114414             }
114415         }
114416         return false;
114417     },
114418
114419     /**
114420      * @param {Object} position The current row and column: an object containing the following properties:
114421      *
114422      * - row - The row index
114423      * - column - The column index
114424      *
114425      * @param {String} direction 'up', 'down', 'right' and 'left'
114426      * @param {Ext.EventObject} e event
114427      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
114428      * @param {Function} verifierFn A function to verify the validity of the calculated position.
114429      * When using this function, you must return true to allow the newPosition to be returned.
114430      * @param {Object} scope Scope to run the verifierFn in
114431      * @returns {Object} newPosition An object containing the following properties:
114432      *
114433      * - row - The row index
114434      * - column - The column index
114435      *
114436      * @private
114437      */
114438     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
114439         var me       = this,
114440             row      = pos.row,
114441             column   = pos.column,
114442             rowCount = me.store.getCount(),
114443             firstCol = me.getFirstVisibleColumnIndex(),
114444             lastCol  = me.getLastVisibleColumnIndex(),
114445             newPos   = {row: row, column: column},
114446             activeHeader = me.headerCt.getHeaderAtIndex(column);
114447
114448         // no active header or its currently hidden
114449         if (!activeHeader || activeHeader.hidden) {
114450             return false;
114451         }
114452
114453         e = e || {};
114454         direction = direction.toLowerCase();
114455         switch (direction) {
114456             case 'right':
114457                 // has the potential to wrap if its last
114458                 if (column === lastCol) {
114459                     // if bottom row and last column, deny right
114460                     if (preventWrap || row === rowCount - 1) {
114461                         return false;
114462                     }
114463                     if (!e.ctrlKey) {
114464                         // otherwise wrap to nextRow and firstCol
114465                         newPos.row = row + 1;
114466                         newPos.column = firstCol;
114467                     }
114468                 // go right
114469                 } else {
114470                     if (!e.ctrlKey) {
114471                         newPos.column = column + me.getRightGap(activeHeader);
114472                     } else {
114473                         newPos.column = lastCol;
114474                     }
114475                 }
114476                 break;
114477
114478             case 'left':
114479                 // has the potential to wrap
114480                 if (column === firstCol) {
114481                     // if top row and first column, deny left
114482                     if (preventWrap || row === 0) {
114483                         return false;
114484                     }
114485                     if (!e.ctrlKey) {
114486                         // otherwise wrap to prevRow and lastCol
114487                         newPos.row = row - 1;
114488                         newPos.column = lastCol;
114489                     }
114490                 // go left
114491                 } else {
114492                     if (!e.ctrlKey) {
114493                         newPos.column = column + me.getLeftGap(activeHeader);
114494                     } else {
114495                         newPos.column = firstCol;
114496                     }
114497                 }
114498                 break;
114499
114500             case 'up':
114501                 // if top row, deny up
114502                 if (row === 0) {
114503                     return false;
114504                 // go up
114505                 } else {
114506                     if (!e.ctrlKey) {
114507                         newPos.row = row - 1;
114508                     } else {
114509                         newPos.row = 0;
114510                     }
114511                 }
114512                 break;
114513
114514             case 'down':
114515                 // if bottom row, deny down
114516                 if (row === rowCount - 1) {
114517                     return false;
114518                 // go down
114519                 } else {
114520                     if (!e.ctrlKey) {
114521                         newPos.row = row + 1;
114522                     } else {
114523                         newPos.row = rowCount - 1;
114524                     }
114525                 }
114526                 break;
114527         }
114528
114529         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
114530             return false;
114531         } else {
114532             return newPos;
114533         }
114534     },
114535     getFirstVisibleColumnIndex: function() {
114536         var headerCt   = this.getHeaderCt(),
114537             allColumns = headerCt.getGridColumns(),
114538             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114539             firstHeader = visHeaders[0];
114540
114541         return headerCt.getHeaderIndex(firstHeader);
114542     },
114543
114544     getLastVisibleColumnIndex: function() {
114545         var headerCt   = this.getHeaderCt(),
114546             allColumns = headerCt.getGridColumns(),
114547             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114548             lastHeader = visHeaders[visHeaders.length - 1];
114549
114550         return headerCt.getHeaderIndex(lastHeader);
114551     },
114552
114553     getHeaderCt: function() {
114554         return this.headerCt;
114555     },
114556
114557     getPosition: function(record, header) {
114558         var me = this,
114559             store = me.store,
114560             gridCols = me.headerCt.getGridColumns();
114561
114562         return {
114563             row: store.indexOf(record),
114564             column: Ext.Array.indexOf(gridCols, header)
114565         };
114566     },
114567
114568     /**
114569      * Determines the 'gap' between the closest adjacent header to the right
114570      * that is not hidden.
114571      * @private
114572      */
114573     getRightGap: function(activeHeader) {
114574         var headerCt        = this.getHeaderCt(),
114575             headers         = headerCt.getGridColumns(),
114576             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114577             i               = activeHeaderIdx + 1,
114578             nextIdx;
114579
114580         for (; i <= headers.length; i++) {
114581             if (!headers[i].hidden) {
114582                 nextIdx = i;
114583                 break;
114584             }
114585         }
114586
114587         return nextIdx - activeHeaderIdx;
114588     },
114589
114590     beforeDestroy: function() {
114591         if (this.rendered) {
114592             this.el.removeAllListeners();
114593         }
114594         this.callParent(arguments);
114595     },
114596
114597     /**
114598      * Determines the 'gap' between the closest adjacent header to the left
114599      * that is not hidden.
114600      * @private
114601      */
114602     getLeftGap: function(activeHeader) {
114603         var headerCt        = this.getHeaderCt(),
114604             headers         = headerCt.getGridColumns(),
114605             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114606             i               = activeHeaderIdx - 1,
114607             prevIdx;
114608
114609         for (; i >= 0; i--) {
114610             if (!headers[i].hidden) {
114611                 prevIdx = i;
114612                 break;
114613             }
114614         }
114615
114616         return prevIdx - activeHeaderIdx;
114617     }
114618 });
114619 /**
114620  * @class Ext.grid.View
114621  * @extends Ext.view.Table
114622  *
114623  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
114624  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
114625  * option is passed to the grid:
114626  *
114627  *     Ext.create('Ext.grid.Panel', {
114628  *         // other options
114629  *         viewConfig: {
114630  *             stripeRows: false
114631  *         }
114632  *     });
114633  *
114634  * ## Drag Drop
114635  *
114636  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
114637  * when creating the view.
114638  *
114639  *     Ext.create('Ext.grid.Panel', {
114640  *         // other options
114641  *         viewConfig: {
114642  *             plugins: {
114643  *                 ddGroup: 'people-group',
114644  *                 ptype: 'gridviewdragdrop',
114645  *                 enableDrop: false
114646  *             }
114647  *         }
114648  *     });
114649  */
114650 Ext.define('Ext.grid.View', {
114651     extend: 'Ext.view.Table',
114652     alias: 'widget.gridview',
114653
114654     /**
114655      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
114656      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
114657      * the grid. A default CSS rule is provided which sets a background color, but you can override this
114658      * with a rule which either overrides the <b>background-color</b> style using the '!important'
114659      * modifier, or which uses a CSS selector of higher specificity.</p>
114660      */
114661     stripeRows: true,
114662
114663     invalidateScrollerOnRefresh: true,
114664
114665     /**
114666      * Scroll the GridView to the top by scrolling the scroller.
114667      * @private
114668      */
114669     scrollToTop : function(){
114670         if (this.rendered) {
114671             var section = this.ownerCt,
114672                 verticalScroller = section.verticalScroller;
114673
114674             if (verticalScroller) {
114675                 verticalScroller.scrollToTop();
114676             }
114677         }
114678     },
114679
114680     // after adding a row stripe rows from then on
114681     onAdd: function(ds, records, index) {
114682         this.callParent(arguments);
114683         this.doStripeRows(index);
114684     },
114685
114686     // after removing a row stripe rows from then on
114687     onRemove: function(ds, records, index) {
114688         this.callParent(arguments);
114689         this.doStripeRows(index);
114690     },
114691
114692     onUpdate: function(ds, record, operation) {
114693         var index = ds.indexOf(record);
114694         this.callParent(arguments);
114695         this.doStripeRows(index, index);
114696     },
114697
114698     /**
114699      * Stripe rows from a particular row index
114700      * @param {Number} startRow
114701      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
114702      * @private
114703      */
114704     doStripeRows: function(startRow, endRow) {
114705         // ensure stripeRows configuration is turned on
114706         if (this.stripeRows) {
114707             var rows   = this.getNodes(startRow, endRow),
114708                 rowsLn = rows.length,
114709                 i      = 0,
114710                 row;
114711
114712             for (; i < rowsLn; i++) {
114713                 row = rows[i];
114714                 // Remove prior applied row classes.
114715                 row.className = row.className.replace(this.rowClsRe, ' ');
114716                 startRow++;
114717                 // Every odd row will get an additional cls
114718                 if (startRow % 2 === 0) {
114719                     row.className += (' ' + this.altRowCls);
114720                 }
114721             }
114722         }
114723     },
114724
114725     refresh: function(firstPass) {
114726         this.callParent(arguments);
114727         this.doStripeRows(0);
114728         // TODO: Remove gridpanel dependency
114729         var g = this.up('gridpanel');
114730         if (g && this.invalidateScrollerOnRefresh) {
114731             g.invalidateScroller();
114732         }
114733     }
114734 });
114735
114736 /**
114737  * @author Aaron Conran
114738  * @docauthor Ed Spencer
114739  *
114740  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
114741  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
114742  *
114743  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
114744  *
114745  * ## Basic GridPanel
114746  *
114747  *     @example
114748  *     Ext.create('Ext.data.Store', {
114749  *         storeId:'simpsonsStore',
114750  *         fields:['name', 'email', 'phone'],
114751  *         data:{'items':[
114752  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
114753  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
114754  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
114755  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
114756  *         ]},
114757  *         proxy: {
114758  *             type: 'memory',
114759  *             reader: {
114760  *                 type: 'json',
114761  *                 root: 'items'
114762  *             }
114763  *         }
114764  *     });
114765  *
114766  *     Ext.create('Ext.grid.Panel', {
114767  *         title: 'Simpsons',
114768  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114769  *         columns: [
114770  *             { header: 'Name',  dataIndex: 'name' },
114771  *             { header: 'Email', dataIndex: 'email', flex: 1 },
114772  *             { header: 'Phone', dataIndex: 'phone' }
114773  *         ],
114774  *         height: 200,
114775  *         width: 400,
114776  *         renderTo: Ext.getBody()
114777  *     });
114778  *
114779  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
114780  * In most apps we would be placing the grid inside another container and wouldn't need to use the
114781  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
114782  * up and running.
114783  *
114784  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
114785  * and finally the grid rows under the headers.
114786  *
114787  * ## Configuring columns
114788  *
114789  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
114790  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
114791  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
114792  *
114793  *     columns: [
114794  *         {
114795  *             header: 'Name',
114796  *             dataIndex: 'name',
114797  *             sortable: false,
114798  *             hideable: false,
114799  *             flex: 1
114800  *         },
114801  *         {
114802  *             header: 'Email',
114803  *             dataIndex: 'email',
114804  *             hidden: true
114805  *         },
114806  *         {
114807  *             header: 'Phone',
114808  *             dataIndex: 'phone',
114809  *             width: 100
114810  *         }
114811  *     ]
114812  *
114813  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
114814  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
114815  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
114816  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
114817  *
114818  * ## Renderers
114819  *
114820  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
114821  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
114822  * we could define a renderer function for the email column to turn each email address into a mailto link:
114823  *
114824  *     columns: [
114825  *         {
114826  *             header: 'Email',
114827  *             dataIndex: 'email',
114828  *             renderer: function(value) {
114829  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
114830  *             }
114831  *         }
114832  *     ]
114833  *
114834  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
114835  *
114836  * ## Selection Models
114837  *
114838  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
114839  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
114840  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
114841  * CellSelectionModel, where individual cells are selected.
114842  *
114843  * Grids use a Row Selection Model by default, but this is easy to customise like so:
114844  *
114845  *     Ext.create('Ext.grid.Panel', {
114846  *         selType: 'cellmodel',
114847  *         store: ...
114848  *     });
114849  *
114850  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
114851  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
114852  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
114853  * conjunction with editing.
114854  *
114855  * ## Editing
114856  *
114857  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
114858  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
114859  * on both the name and the email columns:
114860  *
114861  *     Ext.create('Ext.grid.Panel', {
114862  *         title: 'Simpsons',
114863  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114864  *         columns: [
114865  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114866  *             { header: 'Email', dataIndex: 'email', flex: 1,
114867  *                 field: {
114868  *                     xtype: 'textfield',
114869  *                     allowBlank: false
114870  *                 }
114871  *             },
114872  *             { header: 'Phone', dataIndex: 'phone' }
114873  *         ],
114874  *         selType: 'cellmodel',
114875  *         plugins: [
114876  *             Ext.create('Ext.grid.plugin.CellEditing', {
114877  *                 clicksToEdit: 1
114878  *             })
114879  *         ],
114880  *         height: 200,
114881  *         width: 400,
114882  *         renderTo: Ext.getBody()
114883  *     });
114884  *
114885  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
114886  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
114887  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
114888  * editor slightly by passing allowBlank: false, which will provide inline validation.
114889  *
114890  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
114891  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
114892  * single click.
114893  *
114894  * ## Row Editing
114895  *
114896  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
114897  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
114898  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
114899  *
114900  *     Ext.create('Ext.grid.Panel', {
114901  *         title: 'Simpsons',
114902  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114903  *         columns: [
114904  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114905  *             { header: 'Email', dataIndex: 'email', flex:1,
114906  *                 field: {
114907  *                     xtype: 'textfield',
114908  *                     allowBlank: false
114909  *                 }
114910  *             },
114911  *             { header: 'Phone', dataIndex: 'phone' }
114912  *         ],
114913  *         selType: 'rowmodel',
114914  *         plugins: [
114915  *             Ext.create('Ext.grid.plugin.RowEditing', {
114916  *                 clicksToEdit: 1
114917  *             })
114918  *         ],
114919  *         height: 200,
114920  *         width: 400,
114921  *         renderTo: Ext.getBody()
114922  *     });
114923  *
114924  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
114925  * editor will appear and enable us to edit each of the columns we have specified an editor for.
114926  *
114927  * ## Sorting & Filtering
114928  *
114929  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
114930  * easy to set up a grid to be sorted from the start:
114931  *
114932  *     var myGrid = Ext.create('Ext.grid.Panel', {
114933  *         store: {
114934  *             fields: ['name', 'email', 'phone'],
114935  *             sorters: ['name', 'phone']
114936  *         },
114937  *         columns: [
114938  *             { text: 'Name',  dataIndex: 'name' },
114939  *             { text: 'Email', dataIndex: 'email' }
114940  *         ]
114941  *     });
114942  *
114943  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
114944  * more than one field at run time it's easy to do so by adding new sorters to the store:
114945  *
114946  *     myGrid.store.sort([
114947  *         { property: 'name',  direction: 'ASC' },
114948  *         { property: 'email', direction: 'DESC' }
114949  *     ]);
114950  *
114951  * See {@link Ext.data.Store} for examples of filtering.
114952  *
114953  * ## Grouping
114954  *
114955  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
114956  * group by the department that each employee works in. Here's how we might set that up:
114957  *
114958  *     @example
114959  *     var store = Ext.create('Ext.data.Store', {
114960  *         storeId:'employeeStore',
114961  *         fields:['name', 'senority', 'department'],
114962  *         groupField: 'department',
114963  *         data: {'employees':[
114964  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
114965  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
114966  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
114967  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
114968  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
114969  *         ]},
114970  *         proxy: {
114971  *             type: 'memory',
114972  *             reader: {
114973  *                 type: 'json',
114974  *                 root: 'employees'
114975  *             }
114976  *         }
114977  *     });
114978  *
114979  *     Ext.create('Ext.grid.Panel', {
114980  *         title: 'Employees',
114981  *         store: Ext.data.StoreManager.lookup('employeeStore'),
114982  *         columns: [
114983  *             { header: 'Name',     dataIndex: 'name' },
114984  *             { header: 'Senority', dataIndex: 'senority' }
114985  *         ],
114986  *         features: [{ftype:'grouping'}],
114987  *         width: 200,
114988  *         height: 275,
114989  *         renderTo: Ext.getBody()
114990  *     });
114991  *
114992  * ## Infinite Scrolling
114993  *
114994  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
114995  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
114996  * to a store with a pageSize specified.
114997  *
114998  *     var grid = Ext.create('Ext.grid.Panel', {
114999  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
115000  *         verticalScrollerType: 'paginggridscroller',
115001  *         // do not reset the scrollbar when the view refreshs
115002  *         invalidateScrollerOnRefresh: false,
115003  *         // infinite scrolling does not support selection
115004  *         disableSelection: true,
115005  *         // ...
115006  *     });
115007  *
115008  * ## Paging
115009  *
115010  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
115011  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
115012  *
115013  *     @example
115014  *     var itemsPerPage = 2;   // set the number of items you want per page
115015  *
115016  *     var store = Ext.create('Ext.data.Store', {
115017  *         id:'simpsonsStore',
115018  *         autoLoad: false,
115019  *         fields:['name', 'email', 'phone'],
115020  *         pageSize: itemsPerPage, // items per page
115021  *         proxy: {
115022  *             type: 'ajax',
115023  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
115024  *             reader: {
115025  *                 type: 'json',
115026  *                 root: 'items',
115027  *                 totalProperty: 'total'
115028  *             }
115029  *         }
115030  *     });
115031  *
115032  *     // specify segment of data you want to load using params
115033  *     store.load({
115034  *         params:{
115035  *             start:0,
115036  *             limit: itemsPerPage
115037  *         }
115038  *     });
115039  *
115040  *     Ext.create('Ext.grid.Panel', {
115041  *         title: 'Simpsons',
115042  *         store: store,
115043  *         columns: [
115044  *             {header: 'Name',  dataIndex: 'name'},
115045  *             {header: 'Email', dataIndex: 'email', flex:1},
115046  *             {header: 'Phone', dataIndex: 'phone'}
115047  *         ],
115048  *         width: 400,
115049  *         height: 125,
115050  *         dockedItems: [{
115051  *             xtype: 'pagingtoolbar',
115052  *             store: store,   // same store GridPanel is using
115053  *             dock: 'bottom',
115054  *             displayInfo: true
115055  *         }],
115056  *         renderTo: Ext.getBody()
115057  *     });
115058  */
115059 Ext.define('Ext.grid.Panel', {
115060     extend: 'Ext.panel.Table',
115061     requires: ['Ext.grid.View'],
115062     alias: ['widget.gridpanel', 'widget.grid'],
115063     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
115064     viewType: 'gridview',
115065
115066     lockable: false,
115067
115068     // Required for the Lockable Mixin. These are the configurations which will be copied to the
115069     // normal and locked sub tablepanels
115070     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
115071     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
115072
115073     /**
115074      * @cfg {Boolean} [columnLines=false] Adds column line styling
115075      */
115076
115077     initComponent: function() {
115078         var me = this;
115079
115080         if (me.columnLines) {
115081             me.setColumnLines(me.columnLines);
115082         }
115083
115084         me.callParent();
115085     },
115086
115087     setColumnLines: function(show) {
115088         var me = this,
115089             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
115090
115091         me[method]('with-col-lines');
115092     }
115093 });
115094
115095 // Currently has the following issues:
115096 // - Does not handle postEditValue
115097 // - Fields without editors need to sync with their values in Store
115098 // - starting to edit another record while already editing and dirty should probably prevent it
115099 // - aggregating validation messages
115100 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
115101 // - layout issues when changing sizes/width while hidden (layout bug)
115102
115103 /**
115104  * @class Ext.grid.RowEditor
115105  * @extends Ext.form.Panel
115106  *
115107  * Internal utility class used to provide row editing functionality. For developers, they should use
115108  * the RowEditing plugin to use this functionality with a grid.
115109  *
115110  * @ignore
115111  */
115112 Ext.define('Ext.grid.RowEditor', {
115113     extend: 'Ext.form.Panel',
115114     requires: [
115115         'Ext.tip.ToolTip',
115116         'Ext.util.HashMap',
115117         'Ext.util.KeyNav'
115118     ],
115119
115120     saveBtnText  : 'Update',
115121     cancelBtnText: 'Cancel',
115122     errorsText: 'Errors',
115123     dirtyText: 'You need to commit or cancel your changes',
115124
115125     lastScrollLeft: 0,
115126     lastScrollTop: 0,
115127
115128     border: false,
115129     
115130     // Change the hideMode to offsets so that we get accurate measurements when
115131     // the roweditor is hidden for laying out things like a TriggerField.
115132     hideMode: 'offsets',
115133
115134     initComponent: function() {
115135         var me = this,
115136             form;
115137
115138         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
115139
115140         me.layout = {
115141             type: 'hbox',
115142             align: 'middle'
115143         };
115144
115145         // Maintain field-to-column mapping
115146         // It's easy to get a field from a column, but not vice versa
115147         me.columns = Ext.create('Ext.util.HashMap');
115148         me.columns.getKey = function(columnHeader) {
115149             var f;
115150             if (columnHeader.getEditor) {
115151                 f = columnHeader.getEditor();
115152                 if (f) {
115153                     return f.id;
115154                 }
115155             }
115156             return columnHeader.id;
115157         };
115158         me.mon(me.columns, {
115159             add: me.onFieldAdd,
115160             remove: me.onFieldRemove,
115161             replace: me.onFieldReplace,
115162             scope: me
115163         });
115164
115165         me.callParent(arguments);
115166
115167         if (me.fields) {
115168             me.setField(me.fields);
115169             delete me.fields;
115170         }
115171
115172         form = me.getForm();
115173         form.trackResetOnLoad = true;
115174     },
115175
115176     onFieldChange: function() {
115177         var me = this,
115178             form = me.getForm(),
115179             valid = form.isValid();
115180         if (me.errorSummary && me.isVisible()) {
115181             me[valid ? 'hideToolTip' : 'showToolTip']();
115182         }
115183         if (me.floatingButtons) {
115184             me.floatingButtons.child('#update').setDisabled(!valid);
115185         }
115186         me.isValid = valid;
115187     },
115188
115189     afterRender: function() {
115190         var me = this,
115191             plugin = me.editingPlugin;
115192
115193         me.callParent(arguments);
115194         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
115195
115196         // Prevent from bubbling click events to the grid view
115197         me.mon(me.el, {
115198             click: Ext.emptyFn,
115199             stopPropagation: true
115200         });
115201
115202         me.el.swallowEvent([
115203             'keypress',
115204             'keydown'
115205         ]);
115206
115207         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
115208             enter: plugin.completeEdit,
115209             esc: plugin.onEscKey,
115210             scope: plugin
115211         });
115212
115213         me.mon(plugin.view, {
115214             beforerefresh: me.onBeforeViewRefresh,
115215             refresh: me.onViewRefresh,
115216             scope: me
115217         });
115218     },
115219
115220     onBeforeViewRefresh: function(view) {
115221         var me = this,
115222             viewDom = view.el.dom;
115223
115224         if (me.el.dom.parentNode === viewDom) {
115225             viewDom.removeChild(me.el.dom);
115226         }
115227     },
115228
115229     onViewRefresh: function(view) {
115230         var me = this,
115231             viewDom = view.el.dom,
115232             context = me.context,
115233             idx;
115234
115235         viewDom.appendChild(me.el.dom);
115236
115237         // Recover our row node after a view refresh
115238         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
115239             context.row = view.getNode(idx);
115240             me.reposition();
115241             if (me.tooltip && me.tooltip.isVisible()) {
115242                 me.tooltip.setTarget(context.row);
115243             }
115244         } else {
115245             me.editingPlugin.cancelEdit();
115246         }
115247     },
115248
115249     onCtScroll: function(e, target) {
115250         var me = this,
115251             scrollTop  = target.scrollTop,
115252             scrollLeft = target.scrollLeft;
115253
115254         if (scrollTop !== me.lastScrollTop) {
115255             me.lastScrollTop = scrollTop;
115256             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
115257                 me.repositionTip();
115258             }
115259         }
115260         if (scrollLeft !== me.lastScrollLeft) {
115261             me.lastScrollLeft = scrollLeft;
115262             me.reposition();
115263         }
115264     },
115265
115266     onColumnAdd: function(column) {
115267         this.setField(column);
115268     },
115269
115270     onColumnRemove: function(column) {
115271         this.columns.remove(column);
115272     },
115273
115274     onColumnResize: function(column, width) {
115275         column.getEditor().setWidth(width - 2);
115276         if (this.isVisible()) {
115277             this.reposition();
115278         }
115279     },
115280
115281     onColumnHide: function(column) {
115282         column.getEditor().hide();
115283         if (this.isVisible()) {
115284             this.reposition();
115285         }
115286     },
115287
115288     onColumnShow: function(column) {
115289         var field = column.getEditor();
115290         field.setWidth(column.getWidth() - 2).show();
115291         if (this.isVisible()) {
115292             this.reposition();
115293         }
115294     },
115295
115296     onColumnMove: function(column, fromIdx, toIdx) {
115297         var field = column.getEditor();
115298         if (this.items.indexOf(field) != toIdx) {
115299             this.move(fromIdx, toIdx);
115300         }
115301     },
115302
115303     onFieldAdd: function(map, fieldId, column) {
115304         var me = this,
115305             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
115306             field = column.getEditor({ xtype: 'displayfield' });
115307
115308         me.insert(colIdx, field);
115309     },
115310
115311     onFieldRemove: function(map, fieldId, column) {
115312         var me = this,
115313             field = column.getEditor(),
115314             fieldEl = field.el;
115315         me.remove(field, false);
115316         if (fieldEl) {
115317             fieldEl.remove();
115318         }
115319     },
115320
115321     onFieldReplace: function(map, fieldId, column, oldColumn) {
115322         var me = this;
115323         me.onFieldRemove(map, fieldId, oldColumn);
115324     },
115325
115326     clearFields: function() {
115327         var me = this,
115328             map = me.columns;
115329         map.each(function(fieldId) {
115330             map.removeAtKey(fieldId);
115331         });
115332     },
115333
115334     getFloatingButtons: function() {
115335         var me = this,
115336             cssPrefix = Ext.baseCSSPrefix,
115337             btnsCss = cssPrefix + 'grid-row-editor-buttons',
115338             plugin = me.editingPlugin,
115339             btns;
115340
115341         if (!me.floatingButtons) {
115342             btns = me.floatingButtons = Ext.create('Ext.Container', {
115343                 renderTpl: [
115344                     '<div class="{baseCls}-ml"></div>',
115345                     '<div class="{baseCls}-mr"></div>',
115346                     '<div class="{baseCls}-bl"></div>',
115347                     '<div class="{baseCls}-br"></div>',
115348                     '<div class="{baseCls}-bc"></div>'
115349                 ],
115350
115351                 renderTo: me.el,
115352                 baseCls: btnsCss,
115353                 layout: {
115354                     type: 'hbox',
115355                     align: 'middle'
115356                 },
115357                 defaults: {
115358                     margins: '0 1 0 1'
115359                 },
115360                 items: [{
115361                     itemId: 'update',
115362                     flex: 1,
115363                     xtype: 'button',
115364                     handler: plugin.completeEdit,
115365                     scope: plugin,
115366                     text: me.saveBtnText,
115367                     disabled: !me.isValid
115368                 }, {
115369                     flex: 1,
115370                     xtype: 'button',
115371                     handler: plugin.cancelEdit,
115372                     scope: plugin,
115373                     text: me.cancelBtnText
115374                 }]
115375             });
115376
115377             // Prevent from bubbling click events to the grid view
115378             me.mon(btns.el, {
115379                 // BrowserBug: Opera 11.01
115380                 //   causes the view to scroll when a button is focused from mousedown
115381                 mousedown: Ext.emptyFn,
115382                 click: Ext.emptyFn,
115383                 stopEvent: true
115384             });
115385         }
115386         return me.floatingButtons;
115387     },
115388
115389     reposition: function(animateConfig) {
115390         var me = this,
115391             context = me.context,
115392             row = context && Ext.get(context.row),
115393             btns = me.getFloatingButtons(),
115394             btnEl = btns.el,
115395             grid = me.editingPlugin.grid,
115396             viewEl = grid.view.el,
115397             scroller = grid.verticalScroller,
115398
115399             // always get data from ColumnModel as its what drives
115400             // the GridView's sizing
115401             mainBodyWidth = grid.headerCt.getFullWidth(),
115402             scrollerWidth = grid.getWidth(),
115403
115404             // use the minimum as the columns may not fill up the entire grid
115405             // width
115406             width = Math.min(mainBodyWidth, scrollerWidth),
115407             scrollLeft = grid.view.el.dom.scrollLeft,
115408             btnWidth = btns.getWidth(),
115409             left = (width - btnWidth) / 2 + scrollLeft,
115410             y, rowH, newHeight,
115411
115412             invalidateScroller = function() {
115413                 if (scroller) {
115414                     scroller.invalidate();
115415                     btnEl.scrollIntoView(viewEl, false);
115416                 }
115417                 if (animateConfig && animateConfig.callback) {
115418                     animateConfig.callback.call(animateConfig.scope || me);
115419                 }
115420             };
115421
115422         // need to set both top/left
115423         if (row && Ext.isElement(row.dom)) {
115424             // Bring our row into view if necessary, so a row editor that's already
115425             // visible and animated to the row will appear smooth
115426             row.scrollIntoView(viewEl, false);
115427
115428             // Get the y position of the row relative to its top-most static parent.
115429             // offsetTop will be relative to the table, and is incorrect
115430             // when mixed with certain grid features (e.g., grouping).
115431             y = row.getXY()[1] - 5;
115432             rowH = row.getHeight();
115433             newHeight = rowH + 10;
115434
115435             // IE doesn't set the height quite right.
115436             // This isn't a border-box issue, it even happens
115437             // in IE8 and IE7 quirks.
115438             // TODO: Test in IE9!
115439             if (Ext.isIE) {
115440                 newHeight += 2;
115441             }
115442
115443             // Set editor height to match the row height
115444             if (me.getHeight() != newHeight) {
115445                 me.setHeight(newHeight);
115446                 me.el.setLeft(0);
115447             }
115448
115449             if (animateConfig) {
115450                 var animObj = {
115451                     to: {
115452                         y: y
115453                     },
115454                     duration: animateConfig.duration || 125,
115455                     listeners: {
115456                         afteranimate: function() {
115457                             invalidateScroller();
115458                             y = row.getXY()[1] - 5;
115459                             me.el.setY(y);
115460                         }
115461                     }
115462                 };
115463                 me.animate(animObj);
115464             } else {
115465                 me.el.setY(y);
115466                 invalidateScroller();
115467             }
115468         }
115469         if (me.getWidth() != mainBodyWidth) {
115470             me.setWidth(mainBodyWidth);
115471         }
115472         btnEl.setLeft(left);
115473     },
115474
115475     getEditor: function(fieldInfo) {
115476         var me = this;
115477
115478         if (Ext.isNumber(fieldInfo)) {
115479             // Query only form fields. This just future-proofs us in case we add
115480             // other components to RowEditor later on.  Don't want to mess with
115481             // indices.
115482             return me.query('>[isFormField]')[fieldInfo];
115483         } else if (fieldInfo instanceof Ext.grid.column.Column) {
115484             return fieldInfo.getEditor();
115485         }
115486     },
115487
115488     removeField: function(field) {
115489         var me = this;
115490
115491         // Incase we pass a column instead, which is fine
115492         field = me.getEditor(field);
115493         me.mun(field, 'validitychange', me.onValidityChange, me);
115494
115495         // Remove field/column from our mapping, which will fire the event to
115496         // remove the field from our container
115497         me.columns.removeKey(field.id);
115498     },
115499
115500     setField: function(column) {
115501         var me = this,
115502             field;
115503
115504         if (Ext.isArray(column)) {
115505             Ext.Array.forEach(column, me.setField, me);
115506             return;
115507         }
115508
115509         // Get a default display field if necessary
115510         field = column.getEditor(null, {
115511             xtype: 'displayfield',
115512             // Default display fields will not return values. This is done because
115513             // the display field will pick up column renderers from the grid.
115514             getModelData: function() {
115515                 return null;
115516             }
115517         });
115518         field.margins = '0 0 0 2';
115519         field.setWidth(column.getDesiredWidth() - 2);
115520         me.mon(field, 'change', me.onFieldChange, me);
115521
115522         // Maintain mapping of fields-to-columns
115523         // This will fire events that maintain our container items
115524         me.columns.add(field.id, column);
115525         if (column.hidden) {
115526             me.onColumnHide(column);
115527         }
115528         if (me.isVisible() && me.context) {
115529             me.renderColumnData(field, me.context.record);
115530         }
115531     },
115532
115533     loadRecord: function(record) {
115534         var me = this,
115535             form = me.getForm();
115536         form.loadRecord(record);
115537         if (form.isValid()) {
115538             me.hideToolTip();
115539         } else {
115540             me.showToolTip();
115541         }
115542
115543         // render display fields so they honor the column renderer/template
115544         Ext.Array.forEach(me.query('>displayfield'), function(field) {
115545             me.renderColumnData(field, record);
115546         }, me);
115547     },
115548
115549     renderColumnData: function(field, record) {
115550         var me = this,
115551             grid = me.editingPlugin.grid,
115552             headerCt = grid.headerCt,
115553             view = grid.view,
115554             store = view.store,
115555             column = me.columns.get(field.id),
115556             value = record.get(column.dataIndex);
115557
115558         // honor our column's renderer (TemplateHeader sets renderer for us!)
115559         if (column.renderer) {
115560             var metaData = { tdCls: '', style: '' },
115561                 rowIdx = store.indexOf(record),
115562                 colIdx = headerCt.getHeaderIndex(column);
115563
115564             value = column.renderer.call(
115565                 column.scope || headerCt.ownerCt,
115566                 value,
115567                 metaData,
115568                 record,
115569                 rowIdx,
115570                 colIdx,
115571                 store,
115572                 view
115573             );
115574         }
115575
115576         field.setRawValue(value);
115577         field.resetOriginalValue();
115578     },
115579
115580     beforeEdit: function() {
115581         var me = this;
115582
115583         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
115584             me.showToolTip();
115585             return false;
115586         }
115587     },
115588
115589     /**
115590      * Start editing the specified grid at the specified position.
115591      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
115592      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
115593      */
115594     startEdit: function(record, columnHeader) {
115595         var me = this,
115596             grid = me.editingPlugin.grid,
115597             view = grid.getView(),
115598             store = grid.store,
115599             context = me.context = Ext.apply(me.editingPlugin.context, {
115600                 view: grid.getView(),
115601                 store: store
115602             });
115603
115604         // make sure our row is selected before editing
115605         context.grid.getSelectionModel().select(record);
115606
115607         // Reload the record data
115608         me.loadRecord(record);
115609
115610         if (!me.isVisible()) {
115611             me.show();
115612             me.focusContextCell();
115613         } else {
115614             me.reposition({
115615                 callback: this.focusContextCell
115616             });
115617         }
115618     },
115619
115620     // Focus the cell on start edit based upon the current context
115621     focusContextCell: function() {
115622         var field = this.getEditor(this.context.colIdx);
115623         if (field && field.focus) {
115624             field.focus();
115625         }
115626     },
115627
115628     cancelEdit: function() {
115629         var me = this,
115630             form = me.getForm();
115631
115632         me.hide();
115633         form.clearInvalid();
115634         form.reset();
115635     },
115636
115637     completeEdit: function() {
115638         var me = this,
115639             form = me.getForm();
115640
115641         if (!form.isValid()) {
115642             return;
115643         }
115644
115645         form.updateRecord(me.context.record);
115646         me.hide();
115647         return true;
115648     },
115649
115650     onShow: function() {
115651         var me = this;
115652         me.callParent(arguments);
115653         me.reposition();
115654     },
115655
115656     onHide: function() {
115657         var me = this;
115658         me.callParent(arguments);
115659         me.hideToolTip();
115660         me.invalidateScroller();
115661         if (me.context) {
115662             me.context.view.focus();
115663             me.context = null;
115664         }
115665     },
115666
115667     isDirty: function() {
115668         var me = this,
115669             form = me.getForm();
115670         return form.isDirty();
115671     },
115672
115673     getToolTip: function() {
115674         var me = this,
115675             tip;
115676
115677         if (!me.tooltip) {
115678             tip = me.tooltip = Ext.createWidget('tooltip', {
115679                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
115680                 title: me.errorsText,
115681                 autoHide: false,
115682                 closable: true,
115683                 closeAction: 'disable',
115684                 anchor: 'left'
115685             });
115686         }
115687         return me.tooltip;
115688     },
115689
115690     hideToolTip: function() {
115691         var me = this,
115692             tip = me.getToolTip();
115693         if (tip.rendered) {
115694             tip.disable();
115695         }
115696         me.hiddenTip = false;
115697     },
115698
115699     showToolTip: function() {
115700         var me = this,
115701             tip = me.getToolTip(),
115702             context = me.context,
115703             row = Ext.get(context.row),
115704             viewEl = context.grid.view.el;
115705
115706         tip.setTarget(row);
115707         tip.showAt([-10000, -10000]);
115708         tip.body.update(me.getErrors());
115709         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
115710         me.repositionTip();
115711         tip.doLayout();
115712         tip.enable();
115713     },
115714
115715     repositionTip: function() {
115716         var me = this,
115717             tip = me.getToolTip(),
115718             context = me.context,
115719             row = Ext.get(context.row),
115720             viewEl = context.grid.view.el,
115721             viewHeight = viewEl.getHeight(),
115722             viewTop = me.lastScrollTop,
115723             viewBottom = viewTop + viewHeight,
115724             rowHeight = row.getHeight(),
115725             rowTop = row.dom.offsetTop,
115726             rowBottom = rowTop + rowHeight;
115727
115728         if (rowBottom > viewTop && rowTop < viewBottom) {
115729             tip.show();
115730             me.hiddenTip = false;
115731         } else {
115732             tip.hide();
115733             me.hiddenTip = true;
115734         }
115735     },
115736
115737     getErrors: function() {
115738         var me = this,
115739             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
115740             errors = [];
115741
115742         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
115743             errors = errors.concat(
115744                 Ext.Array.map(field.getErrors(), function(e) {
115745                     return '<li>' + e + '</li>';
115746                 })
115747             );
115748         }, me);
115749
115750         return dirtyText + '<ul>' + errors.join('') + '</ul>';
115751     },
115752
115753     invalidateScroller: function() {
115754         var me = this,
115755             context = me.context,
115756             scroller = context.grid.verticalScroller;
115757
115758         if (scroller) {
115759             scroller.invalidate();
115760         }
115761     }
115762 });
115763 /**
115764  * @class Ext.grid.header.Container
115765  * @extends Ext.container.Container
115766  *
115767  * Container which holds headers and is docked at the top or bottom of a TablePanel.
115768  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
115769  * As headers are hidden, moved or resized the headercontainer is responsible for
115770  * triggering changes within the view.
115771  */
115772 Ext.define('Ext.grid.header.Container', {
115773     extend: 'Ext.container.Container',
115774     uses: [
115775         'Ext.grid.ColumnLayout',
115776         'Ext.grid.column.Column',
115777         'Ext.menu.Menu',
115778         'Ext.menu.CheckItem',
115779         'Ext.menu.Separator',
115780         'Ext.grid.plugin.HeaderResizer',
115781         'Ext.grid.plugin.HeaderReorderer'
115782     ],
115783     border: true,
115784
115785     alias: 'widget.headercontainer',
115786
115787     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
115788     dock: 'top',
115789
115790     /**
115791      * @cfg {Number} weight
115792      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
115793      * This is so that it has more priority over things like toolbars.
115794      */
115795     weight: 100,
115796     defaultType: 'gridcolumn',
115797     /**
115798      * @cfg {Number} defaultWidth
115799      * Width of the header if no width or flex is specified. Defaults to 100.
115800      */
115801     defaultWidth: 100,
115802
115803
115804     sortAscText: 'Sort Ascending',
115805     sortDescText: 'Sort Descending',
115806     sortClearText: 'Clear Sort',
115807     columnsText: 'Columns',
115808
115809     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
115810     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
115811     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
115812
115813     // private; will probably be removed by 4.0
115814     triStateSort: false,
115815
115816     ddLock: false,
115817
115818     dragging: false,
115819
115820     /**
115821      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
115822      * @type Boolean
115823      * @property isGroupHeader
115824      */
115825
115826     /**
115827      * @cfg {Boolean} sortable
115828      * Provides the default sortable state for all Headers within this HeaderContainer.
115829      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
115830      * shared across every header and therefore turning it off will remove the menu
115831      * items for every header.
115832      */
115833     sortable: true,
115834
115835     initComponent: function() {
115836         var me = this;
115837
115838         me.headerCounter = 0;
115839         me.plugins = me.plugins || [];
115840
115841         // TODO: Pass in configurations to turn on/off dynamic
115842         //       resizing and disable resizing all together
115843
115844         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
115845         // Nested Group Headers are themselves HeaderContainers
115846         if (!me.isHeader) {
115847             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
115848             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
115849             if (!me.enableColumnResize) {
115850                 me.resizer.disable();
115851             }
115852             if (!me.enableColumnMove) {
115853                 me.reorderer.disable();
115854             }
115855             me.plugins.push(me.reorderer, me.resizer);
115856         }
115857
115858         // Base headers do not need a box layout
115859         if (me.isHeader && !me.items) {
115860             me.layout = 'auto';
115861         }
115862         // HeaderContainer and Group header needs a gridcolumn layout.
115863         else {
115864             me.layout = {
115865                 type: 'gridcolumn',
115866                 availableSpaceOffset: me.availableSpaceOffset,
115867                 align: 'stretchmax',
115868                 resetStretch: true
115869             };
115870         }
115871         me.defaults = me.defaults || {};
115872         Ext.applyIf(me.defaults, {
115873             width: me.defaultWidth,
115874             triStateSort: me.triStateSort,
115875             sortable: me.sortable
115876         });
115877         me.callParent();
115878         me.addEvents(
115879             /**
115880              * @event columnresize
115881              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115882              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115883              * @param {Number} width
115884              */
115885             'columnresize',
115886
115887             /**
115888              * @event headerclick
115889              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115890              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115891              * @param {Ext.EventObject} e
115892              * @param {HTMLElement} t
115893              */
115894             'headerclick',
115895
115896             /**
115897              * @event headertriggerclick
115898              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115899              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115900              * @param {Ext.EventObject} e
115901              * @param {HTMLElement} t
115902              */
115903             'headertriggerclick',
115904
115905             /**
115906              * @event columnmove
115907              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115908              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115909              * @param {Number} fromIdx
115910              * @param {Number} toIdx
115911              */
115912             'columnmove',
115913             /**
115914              * @event columnhide
115915              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115916              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115917              */
115918             'columnhide',
115919             /**
115920              * @event columnshow
115921              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115922              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115923              */
115924             'columnshow',
115925             /**
115926              * @event sortchange
115927              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115928              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115929              * @param {String} direction
115930              */
115931             'sortchange',
115932             /**
115933              * @event menucreate
115934              * Fired immediately after the column header menu is created.
115935              * @param {Ext.grid.header.Container} ct This instance
115936              * @param {Ext.menu.Menu} menu The Menu that was created
115937              */
115938             'menucreate'
115939         );
115940     },
115941
115942     onDestroy: function() {
115943         Ext.destroy(this.resizer, this.reorderer);
115944         this.callParent();
115945     },
115946     
115947     applyDefaults: function(config){
115948         /*
115949          * Ensure header.Container defaults don't get applied to a RowNumberer 
115950          * if an xtype is supplied. This isn't an ideal solution however it's 
115951          * much more likely that a RowNumberer with no options will be created, 
115952          * wanting to use the defaults specified on the class as opposed to 
115953          * those setup on the Container.
115954          */
115955         if (config && !config.isComponent && config.xtype == 'rownumberer') {
115956             return config;
115957         }
115958         return this.callParent([config]);
115959     },
115960
115961     applyColumnsState: function(columns) {
115962         if (!columns || !columns.length) {
115963             return;
115964         }
115965
115966         var me = this,
115967             i = 0,
115968             index,
115969             col;
115970
115971         Ext.each(columns, function (columnState) {
115972             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
115973             if (col) {
115974                 index = me.items.indexOf(col);
115975                 if (i !== index) {
115976                     me.moveHeader(index, i);
115977                 }
115978
115979                 if (col.applyColumnState) {
115980                     col.applyColumnState(columnState);
115981                 }
115982                 ++i;
115983             }
115984         });
115985     },
115986
115987     getColumnsState: function () {
115988         var me = this,
115989             columns = [],
115990             state;
115991
115992         me.items.each(function (col) {
115993             state = col.getColumnState && col.getColumnState();
115994             if (state) {
115995                 columns.push(state);
115996             }
115997         });
115998
115999         return columns;
116000     },
116001
116002     // Invalidate column cache on add
116003     // We cannot refresh the View on every add because this method is called
116004     // when the HeaderDropZone moves Headers around, that will also refresh the view
116005     onAdd: function(c) {
116006         var me = this;
116007         if (!c.headerId) {
116008             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
116009         }
116010         me.callParent(arguments);
116011         me.purgeCache();
116012     },
116013
116014     // Invalidate column cache on remove
116015     // We cannot refresh the View on every remove because this method is called
116016     // when the HeaderDropZone moves Headers around, that will also refresh the view
116017     onRemove: function(c) {
116018         var me = this;
116019         me.callParent(arguments);
116020         me.purgeCache();
116021     },
116022
116023     afterRender: function() {
116024         this.callParent();
116025         var store   = this.up('[store]').store,
116026             sorters = store.sorters,
116027             first   = sorters.first(),
116028             hd;
116029
116030         if (first) {
116031             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
116032             if (hd) {
116033                 hd.setSortState(first.direction, false, true);
116034             }
116035         }
116036     },
116037
116038     afterLayout: function() {
116039         if (!this.isHeader) {
116040             var me = this,
116041                 topHeaders = me.query('>gridcolumn:not([hidden])'),
116042                 viewEl,
116043                 firstHeaderEl,
116044                 lastHeaderEl;
116045
116046             me.callParent(arguments);
116047
116048             if (topHeaders.length) {
116049                 firstHeaderEl = topHeaders[0].el;
116050                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
116051                     if (me.pastFirstHeaderEl) {
116052                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
116053                     }
116054                     firstHeaderEl.addCls(me.firstHeaderCls);
116055                     me.pastFirstHeaderEl = firstHeaderEl;
116056                 }
116057
116058                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
116059                 if (lastHeaderEl !== me.pastLastHeaderEl) {
116060                     if (me.pastLastHeaderEl) {
116061                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
116062                     }
116063                     lastHeaderEl.addCls(me.lastHeaderCls);
116064                     me.pastLastHeaderEl = lastHeaderEl;
116065                 }
116066             }
116067         }
116068
116069     },
116070
116071     onHeaderShow: function(header, preventLayout) {
116072         // Pass up to the GridSection
116073         var me = this,
116074             gridSection = me.ownerCt,
116075             menu = me.getMenu(),
116076             topItems, topItemsVisible,
116077             colCheckItem,
116078             itemToEnable,
116079             len, i;
116080
116081         if (menu) {
116082
116083             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116084             if (colCheckItem) {
116085                 colCheckItem.setChecked(true, true);
116086             }
116087
116088             // There's more than one header visible, and we've disabled some checked items... re-enable them
116089             topItems = menu.query('#columnItem>menucheckitem[checked]');
116090             topItemsVisible = topItems.length;
116091             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
116092                 if (topItemsVisible == 1) {
116093                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
116094                 }
116095                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
116096                     itemToEnable = me.disabledMenuItems[i];
116097                     if (!itemToEnable.isDestroyed) {
116098                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
116099                     }
116100                 }
116101                 if (topItemsVisible == 1) {
116102                     me.disabledMenuItems = topItems;
116103                 } else {
116104                     me.disabledMenuItems = [];
116105                 }
116106             }
116107         }
116108
116109         // Only update the grid UI when we are notified about base level Header shows;
116110         // Group header shows just cause a layout of the HeaderContainer
116111         if (!header.isGroupHeader) {
116112             if (me.view) {
116113                 me.view.onHeaderShow(me, header, true);
116114             }
116115             if (gridSection) {
116116                 gridSection.onHeaderShow(me, header);
116117             }
116118         }
116119         me.fireEvent('columnshow', me, header);
116120
116121         // The header's own hide suppresses cascading layouts, so lay the headers out now
116122         if (preventLayout !== true) {
116123             me.doLayout();
116124         }
116125     },
116126
116127     doComponentLayout: function(){
116128         var me = this;
116129         if (me.view && me.view.saveScrollState) {
116130             me.view.saveScrollState();
116131         }
116132         me.callParent(arguments);
116133         if (me.view && me.view.restoreScrollState) {
116134             me.view.restoreScrollState();
116135         }
116136     },
116137
116138     onHeaderHide: function(header, suppressLayout) {
116139         // Pass up to the GridSection
116140         var me = this,
116141             gridSection = me.ownerCt,
116142             menu = me.getMenu(),
116143             colCheckItem;
116144
116145         if (menu) {
116146
116147             // If the header was hidden programmatically, sync the Menu state
116148             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116149             if (colCheckItem) {
116150                 colCheckItem.setChecked(false, true);
116151             }
116152             me.setDisabledItems();
116153         }
116154
116155         // Only update the UI when we are notified about base level Header hides;
116156         if (!header.isGroupHeader) {
116157             if (me.view) {
116158                 me.view.onHeaderHide(me, header, true);
116159             }
116160             if (gridSection) {
116161                 gridSection.onHeaderHide(me, header);
116162             }
116163
116164             // The header's own hide suppresses cascading layouts, so lay the headers out now
116165             if (!suppressLayout) {
116166                 me.doLayout();
116167             }
116168         }
116169         me.fireEvent('columnhide', me, header);
116170     },
116171
116172     setDisabledItems: function(){
116173         var me = this,
116174             menu = me.getMenu(),
116175             i = 0,
116176             len,
116177             itemsToDisable,
116178             itemToDisable;
116179
116180         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
116181         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
116182         if ((itemsToDisable.length === 1)) {
116183             if (!me.disabledMenuItems) {
116184                 me.disabledMenuItems = [];
116185             }
116186
116187             // If down to only one column visible, also disable any descendant checkitems
116188             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
116189                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
116190             }
116191
116192             len = itemsToDisable.length;
116193             // Disable any further unchecking at any level.
116194             for (i = 0; i < len; i++) {
116195                 itemToDisable = itemsToDisable[i];
116196                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
116197
116198                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
116199                     // setting its correct disablement level.
116200                     itemToDisable.disabled = false;
116201                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
116202                     me.disabledMenuItems.push(itemToDisable);
116203                 }
116204             }
116205         }
116206     },
116207
116208     /**
116209      * Temporarily lock the headerCt. This makes it so that clicking on headers
116210      * don't trigger actions like sorting or opening of the header menu. This is
116211      * done because extraneous events may be fired on the headers after interacting
116212      * with a drag drop operation.
116213      * @private
116214      */
116215     tempLock: function() {
116216         this.ddLock = true;
116217         Ext.Function.defer(function() {
116218             this.ddLock = false;
116219         }, 200, this);
116220     },
116221
116222     onHeaderResize: function(header, w, suppressFocus) {
116223         this.tempLock();
116224         if (this.view && this.view.rendered) {
116225             this.view.onHeaderResize(header, w, suppressFocus);
116226         }
116227     },
116228
116229     onHeaderClick: function(header, e, t) {
116230         this.fireEvent("headerclick", this, header, e, t);
116231     },
116232
116233     onHeaderTriggerClick: function(header, e, t) {
116234         // generate and cache menu, provide ability to cancel/etc
116235         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
116236             this.showMenuBy(t, header);
116237         }
116238     },
116239
116240     showMenuBy: function(t, header) {
116241         var menu = this.getMenu(),
116242             ascItem  = menu.down('#ascItem'),
116243             descItem = menu.down('#descItem'),
116244             sortableMth;
116245
116246         menu.activeHeader = menu.ownerCt = header;
116247         menu.setFloatParent(header);
116248         // TODO: remove coupling to Header's titleContainer el
116249         header.titleContainer.addCls(this.headerOpenCls);
116250
116251         // enable or disable asc & desc menu items based on header being sortable
116252         sortableMth = header.sortable ? 'enable' : 'disable';
116253         if (ascItem) {
116254             ascItem[sortableMth]();
116255         }
116256         if (descItem) {
116257             descItem[sortableMth]();
116258         }
116259         menu.showBy(t);
116260     },
116261
116262     // remove the trigger open class when the menu is hidden
116263     onMenuDeactivate: function() {
116264         var menu = this.getMenu();
116265         // TODO: remove coupling to Header's titleContainer el
116266         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
116267     },
116268
116269     moveHeader: function(fromIdx, toIdx) {
116270
116271         // An automatically expiring lock
116272         this.tempLock();
116273         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
116274     },
116275
116276     purgeCache: function() {
116277         var me = this;
116278         // Delete column cache - column order has changed.
116279         delete me.gridDataColumns;
116280         delete me.hideableColumns;
116281
116282         // Menu changes when columns are moved. It will be recreated.
116283         if (me.menu) {
116284             me.menu.destroy();
116285             delete me.menu;
116286         }
116287     },
116288
116289     onHeaderMoved: function(header, fromIdx, toIdx) {
116290         var me = this,
116291             gridSection = me.ownerCt;
116292
116293         if (gridSection && gridSection.onHeaderMove) {
116294             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
116295         }
116296         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
116297     },
116298
116299     /**
116300      * Gets the menu (and will create it if it doesn't already exist)
116301      * @private
116302      */
116303     getMenu: function() {
116304         var me = this;
116305
116306         if (!me.menu) {
116307             me.menu = Ext.create('Ext.menu.Menu', {
116308                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
116309                 items: me.getMenuItems(),
116310                 listeners: {
116311                     deactivate: me.onMenuDeactivate,
116312                     scope: me
116313                 }
116314             });
116315             me.setDisabledItems();
116316             me.fireEvent('menucreate', me, me.menu);
116317         }
116318         return me.menu;
116319     },
116320
116321     /**
116322      * Returns an array of menu items to be placed into the shared menu
116323      * across all headers in this header container.
116324      * @returns {Array} menuItems
116325      */
116326     getMenuItems: function() {
116327         var me = this,
116328             menuItems = [],
116329             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
116330
116331         if (me.sortable) {
116332             menuItems = [{
116333                 itemId: 'ascItem',
116334                 text: me.sortAscText,
116335                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
116336                 handler: me.onSortAscClick,
116337                 scope: me
116338             },{
116339                 itemId: 'descItem',
116340                 text: me.sortDescText,
116341                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
116342                 handler: me.onSortDescClick,
116343                 scope: me
116344             }];
116345         }
116346         if (hideableColumns && hideableColumns.length) {
116347             menuItems.push('-', {
116348                 itemId: 'columnItem',
116349                 text: me.columnsText,
116350                 cls: Ext.baseCSSPrefix + 'cols-icon',
116351                 menu: hideableColumns
116352             });
116353         }
116354         return menuItems;
116355     },
116356
116357     // sort asc when clicking on item in menu
116358     onSortAscClick: function() {
116359         var menu = this.getMenu(),
116360             activeHeader = menu.activeHeader;
116361
116362         activeHeader.setSortState('ASC');
116363     },
116364
116365     // sort desc when clicking on item in menu
116366     onSortDescClick: function() {
116367         var menu = this.getMenu(),
116368             activeHeader = menu.activeHeader;
116369
116370         activeHeader.setSortState('DESC');
116371     },
116372
116373     /**
116374      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
116375      */
116376     getColumnMenu: function(headerContainer) {
116377         var menuItems = [],
116378             i = 0,
116379             item,
116380             items = headerContainer.query('>gridcolumn[hideable]'),
116381             itemsLn = items.length,
116382             menuItem;
116383
116384         for (; i < itemsLn; i++) {
116385             item = items[i];
116386             menuItem = Ext.create('Ext.menu.CheckItem', {
116387                 text: item.text,
116388                 checked: !item.hidden,
116389                 hideOnClick: false,
116390                 headerId: item.id,
116391                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
116392                 checkHandler: this.onColumnCheckChange,
116393                 scope: this
116394             });
116395             if (itemsLn === 1) {
116396                 menuItem.disabled = true;
116397             }
116398             menuItems.push(menuItem);
116399
116400             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
116401             // then the associated menu item must also be destroyed.
116402             item.on({
116403                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
116404             });
116405         }
116406         return menuItems;
116407     },
116408
116409     onColumnCheckChange: function(checkItem, checked) {
116410         var header = Ext.getCmp(checkItem.headerId);
116411         header[checked ? 'show' : 'hide']();
116412     },
116413
116414     /**
116415      * Get the columns used for generating a template via TableChunker.
116416      * Returns an array of all columns and their
116417      *  - dataIndex
116418      *  - align
116419      *  - width
116420      *  - id
116421      *  - columnId - used to create an identifying CSS class
116422      *  - cls The tdCls configuration from the Column object
116423      *  @private
116424      */
116425     getColumnsForTpl: function(flushCache) {
116426         var cols    = [],
116427             headers   = this.getGridColumns(flushCache),
116428             headersLn = headers.length,
116429             i = 0,
116430             header,
116431             width;
116432
116433         for (; i < headersLn; i++) {
116434             header = headers[i];
116435
116436             if (header.hidden || header.up('headercontainer[hidden=true]')) {
116437                 width = 0;
116438             } else {
116439                 width = header.getDesiredWidth();
116440                 // IE6 and IE7 bug.
116441                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
116442                 // We need to increment the passed with in this case.
116443                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
116444                     width += 1;
116445                 }
116446             }
116447             cols.push({
116448                 dataIndex: header.dataIndex,
116449                 align: header.align,
116450                 width: width,
116451                 id: header.id,
116452                 cls: header.tdCls,
116453                 columnId: header.getItemId()
116454             });
116455         }
116456         return cols;
116457     },
116458
116459     /**
116460      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
116461      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
116462      */
116463     getColumnCount: function() {
116464         return this.getGridColumns().length;
116465     },
116466
116467     /**
116468      * Gets the full width of all columns that are visible.
116469      */
116470     getFullWidth: function(flushCache) {
116471         var fullWidth = 0,
116472             headers     = this.getVisibleGridColumns(flushCache),
116473             headersLn   = headers.length,
116474             i         = 0;
116475
116476         for (; i < headersLn; i++) {
116477             if (!isNaN(headers[i].width)) {
116478                 // use headers getDesiredWidth if its there
116479                 if (headers[i].getDesiredWidth) {
116480                     fullWidth += headers[i].getDesiredWidth();
116481                 // if injected a diff cmp use getWidth
116482                 } else {
116483                     fullWidth += headers[i].getWidth();
116484                 }
116485             }
116486         }
116487         return fullWidth;
116488     },
116489
116490     // invoked internally by a header when not using triStateSorting
116491     clearOtherSortStates: function(activeHeader) {
116492         var headers   = this.getGridColumns(),
116493             headersLn = headers.length,
116494             i         = 0,
116495             oldSortState;
116496
116497         for (; i < headersLn; i++) {
116498             if (headers[i] !== activeHeader) {
116499                 oldSortState = headers[i].sortState;
116500                 // unset the sortstate and dont recurse
116501                 headers[i].setSortState(null, true);
116502                 //if (!silent && oldSortState !== null) {
116503                 //    this.fireEvent('sortchange', this, headers[i], null);
116504                 //}
116505             }
116506         }
116507     },
116508
116509     /**
116510      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
116511      * level, and does not return <i>grouped</i> headers which contain sub headers.
116512      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116513      * @returns {Array}
116514      */
116515     getVisibleGridColumns: function(refreshCache) {
116516         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
116517     },
116518
116519     /**
116520      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
116521      * level, and does not return <i>grouped</i> headers which contain sub headers.
116522      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116523      * @returns {Array}
116524      */
116525     getGridColumns: function(refreshCache) {
116526         var me = this,
116527             result = refreshCache ? null : me.gridDataColumns;
116528
116529         // Not already got the column cache, so collect the base columns
116530         if (!result) {
116531             me.gridDataColumns = result = [];
116532             me.cascade(function(c) {
116533                 if ((c !== me) && !c.isGroupHeader) {
116534                     result.push(c);
116535                 }
116536             });
116537         }
116538
116539         return result;
116540     },
116541
116542     /**
116543      * @private
116544      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
116545      * the header menu should be disabled.
116546      */
116547     getHideableColumns: function(refreshCache) {
116548         var me = this,
116549             result = refreshCache ? null : me.hideableColumns;
116550
116551         if (!result) {
116552             result = me.hideableColumns = me.query('[hideable]');
116553         }
116554         return result;
116555     },
116556
116557     /**
116558      * Get the index of a leaf level header regardless of what the nesting
116559      * structure is.
116560      */
116561     getHeaderIndex: function(header) {
116562         var columns = this.getGridColumns();
116563         return Ext.Array.indexOf(columns, header);
116564     },
116565
116566     /**
116567      * Get a leaf level header by index regardless of what the nesting
116568      * structure is.
116569      */
116570     getHeaderAtIndex: function(index) {
116571         var columns = this.getGridColumns();
116572         return columns[index];
116573     },
116574
116575     /**
116576      * Maps the record data to base it on the header id's.
116577      * This correlates to the markup/template generated by
116578      * TableChunker.
116579      */
116580     prepareData: function(data, rowIdx, record, view, panel) {
116581         var obj       = {},
116582             headers   = this.gridDataColumns || this.getGridColumns(),
116583             headersLn = headers.length,
116584             colIdx    = 0,
116585             header,
116586             headerId,
116587             renderer,
116588             value,
116589             metaData,
116590             store = panel.store;
116591
116592         for (; colIdx < headersLn; colIdx++) {
116593             metaData = {
116594                 tdCls: '',
116595                 style: ''
116596             };
116597             header = headers[colIdx];
116598             headerId = header.id;
116599             renderer = header.renderer;
116600             value = data[header.dataIndex];
116601
116602             // When specifying a renderer as a string, it always resolves
116603             // to Ext.util.Format
116604             if (typeof renderer === "string") {
116605                 header.renderer = renderer = Ext.util.Format[renderer];
116606             }
116607
116608             if (typeof renderer === "function") {
116609                 value = renderer.call(
116610                     header.scope || this.ownerCt,
116611                     value,
116612                     // metadata per cell passing an obj by reference so that
116613                     // it can be manipulated inside the renderer
116614                     metaData,
116615                     record,
116616                     rowIdx,
116617                     colIdx,
116618                     store,
116619                     view
116620                 );
116621             }
116622
116623
116624             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
116625             obj[headerId+'-tdCls'] = metaData.tdCls;
116626             obj[headerId+'-tdAttr'] = metaData.tdAttr;
116627             obj[headerId+'-style'] = metaData.style;
116628             if (value === undefined || value === null || value === '') {
116629                 value = '&#160;';
116630             }
116631             obj[headerId] = value;
116632         }
116633         return obj;
116634     },
116635
116636     expandToFit: function(header) {
116637         if (this.view) {
116638             this.view.expandToFit(header);
116639         }
116640     }
116641 });
116642
116643 /**
116644  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
116645  * both the grid header configuration as well as displaying data within the grid itself. If the
116646  * {@link #columns} configuration is specified, this column will become a column group and can
116647  * contain other columns inside. In general, this class will not be created directly, rather
116648  * an array of column configurations will be passed to the grid:
116649  *
116650  *     @example
116651  *     Ext.create('Ext.data.Store', {
116652  *         storeId:'employeeStore',
116653  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
116654  *         data:[
116655  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
116656  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
116657  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
116658  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
116659  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
116660  *         ]
116661  *     });
116662  *
116663  *     Ext.create('Ext.grid.Panel', {
116664  *         title: 'Column Demo',
116665  *         store: Ext.data.StoreManager.lookup('employeeStore'),
116666  *         columns: [
116667  *             {text: 'First Name',  dataIndex:'firstname'},
116668  *             {text: 'Last Name',  dataIndex:'lastname'},
116669  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
116670  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
116671  *         ],
116672  *         width: 400,
116673  *         renderTo: Ext.getBody()
116674  *     });
116675  *
116676  * # Convenience Subclasses
116677  *
116678  * There are several column subclasses that provide default rendering for various data types
116679  *
116680  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
116681  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
116682  *  - {@link Ext.grid.column.Date}: Renders for date values
116683  *  - {@link Ext.grid.column.Number}: Renders for numeric values
116684  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
116685  *
116686  * # Setting Sizes
116687  *
116688  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
116689  * be given an explicit width value or a flex configuration. If no width is specified the grid will
116690  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
116691  * the width of the child columns, so a width option should not be specified in that case.
116692  *
116693  * # Header Options
116694  *
116695  *  - {@link #text}: Sets the header text for the column
116696  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
116697  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
116698  *  - {@link #menuDisabled}: Disables the column header menu
116699  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
116700  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
116701  *
116702  * # Data Options
116703  *
116704  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
116705  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
116706  */
116707 Ext.define('Ext.grid.column.Column', {
116708     extend: 'Ext.grid.header.Container',
116709     alias: 'widget.gridcolumn',
116710     requires: ['Ext.util.KeyNav'],
116711     alternateClassName: 'Ext.grid.Column',
116712
116713     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
116714
116715     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
116716     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
116717
116718     handleWidth: 5,
116719
116720     sortState: null,
116721
116722     possibleSortStates: ['ASC', 'DESC'],
116723
116724     renderTpl:
116725         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
116726             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
116727                 '{text}' +
116728             '</span>' +
116729             '<tpl if="!values.menuDisabled">'+
116730                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
116731             '</tpl>' +
116732         '</div>',
116733
116734     /**
116735      * @cfg {Object[]} columns
116736      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
116737      * `columns` config.
116738      *
116739      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
116740      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
116741      */
116742
116743     /**
116744      * @cfg {String} dataIndex
116745      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
116746      * which to draw the column's value. **Required.**
116747      */
116748     dataIndex: null,
116749
116750     /**
116751      * @cfg {String} text
116752      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
116753      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
116754      */
116755     text: '&#160;',
116756
116757     /**
116758      * @cfg {Boolean} sortable
116759      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
116760      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
116761      */
116762     sortable: true,
116763
116764     /**
116765      * @cfg {Boolean} groupable
116766      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
116767      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
116768      * disable (but still show) the group option in the header menu for the column.
116769      */
116770
116771     /**
116772      * @cfg {Boolean} fixed
116773      * @deprecated.
116774      * True to prevent the column from being resizable.
116775      */
116776
116777     /**
116778      * @cfg {Boolean} resizable
116779      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
116780      */
116781
116782     /**
116783      * @cfg {Boolean} hideable
116784      * False to prevent the user from hiding this column. Defaults to true.
116785      */
116786     hideable: true,
116787
116788     /**
116789      * @cfg {Boolean} menuDisabled
116790      * True to disable the column header menu containing sort/hide options. Defaults to false.
116791      */
116792     menuDisabled: false,
116793
116794     /**
116795      * @cfg {Function} renderer
116796      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
116797      * before it is rendered. Example:
116798      *
116799      *     {
116800      *         renderer: function(value){
116801      *             if (value === 1) {
116802      *                 return '1 person';
116803      *             }
116804      *             return value + ' people';
116805      *         }
116806      *     }
116807      *
116808      * @cfg {Object} renderer.value The data value for the current cell
116809      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
116810      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
116811      * @cfg {Ext.data.Model} renderer.record The record for the current row
116812      * @cfg {Number} renderer.rowIndex The index of the current row
116813      * @cfg {Number} renderer.colIndex The index of the current column
116814      * @cfg {Ext.data.Store} renderer.store The data store
116815      * @cfg {Ext.view.View} renderer.view The current view
116816      * @cfg {String} renderer.return The HTML string to be rendered.
116817      */
116818     renderer: false,
116819
116820     /**
116821      * @cfg {String} align
116822      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
116823      */
116824     align: 'left',
116825
116826     /**
116827      * @cfg {Boolean} draggable
116828      * False to disable drag-drop reordering of this column. Defaults to true.
116829      */
116830     draggable: true,
116831
116832     // Header does not use the typical ComponentDraggable class and therefore we
116833     // override this with an emptyFn. It is controlled at the HeaderDragZone.
116834     initDraggable: Ext.emptyFn,
116835
116836     /**
116837      * @cfg {String} tdCls
116838      * A CSS class names to apply to the table cells for this column.
116839      */
116840
116841     /**
116842      * @cfg {Object/String} editor
116843      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
116844      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
116845      */
116846
116847     /**
116848      * @cfg {Object/String} field
116849      * Alias for {@link #editor}.
116850      * @deprecated 4.0.5 Use {@link #editor} instead.
116851      */
116852
116853     /**
116854      * @property {Ext.Element} triggerEl
116855      * Element that acts as button for column header dropdown menu.
116856      */
116857
116858     /**
116859      * @property {Ext.Element} textEl
116860      * Element that contains the text in column header.
116861      */
116862
116863     /**
116864      * @private
116865      * Set in this class to identify, at runtime, instances which are not instances of the
116866      * HeaderContainer base class, but are in fact, the subclass: Header.
116867      */
116868     isHeader: true,
116869
116870     initComponent: function() {
116871         var me = this,
116872             i,
116873             len,
116874             item;
116875
116876         if (Ext.isDefined(me.header)) {
116877             me.text = me.header;
116878             delete me.header;
116879         }
116880
116881         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
116882         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
116883         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
116884         if (me.flex) {
116885             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
116886         }
116887         // Non-flexed Headers may never be squeezed in the event of a shortfall so
116888         // always set their minWidth to their current width.
116889         else {
116890             me.minWidth = me.width;
116891         }
116892
116893         if (!me.triStateSort) {
116894             me.possibleSortStates.length = 2;
116895         }
116896
116897         // A group header; It contains items which are themselves Headers
116898         if (Ext.isDefined(me.columns)) {
116899             me.isGroupHeader = true;
116900
116901
116902             // The headers become child items
116903             me.items = me.columns;
116904             delete me.columns;
116905             delete me.flex;
116906             me.width = 0;
116907
116908             // Acquire initial width from sub headers
116909             for (i = 0, len = me.items.length; i < len; i++) {
116910                 item = me.items[i];
116911                 if (!item.hidden) {
116912                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
116913                 }
116914             }
116915             me.minWidth = me.width;
116916
116917             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
116918             me.sortable = false;
116919             me.resizable = false;
116920             me.align = 'center';
116921         }
116922
116923         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
116924
116925         // Initialize as a HeaderContainer
116926         me.callParent(arguments);
116927     },
116928
116929     onAdd: function(childHeader) {
116930         childHeader.isSubHeader = true;
116931         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
116932         this.callParent(arguments);
116933     },
116934
116935     onRemove: function(childHeader) {
116936         childHeader.isSubHeader = false;
116937         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
116938         this.callParent(arguments);
116939     },
116940
116941     initRenderData: function() {
116942         var me = this;
116943
116944         Ext.applyIf(me.renderData, {
116945             text: me.text,
116946             menuDisabled: me.menuDisabled
116947         });
116948         return me.callParent(arguments);
116949     },
116950
116951     applyColumnState: function (state) {
116952         var me = this,
116953             defined = Ext.isDefined;
116954             
116955         // apply any columns
116956         me.applyColumnsState(state.columns);
116957
116958         // Only state properties which were saved should be restored.
116959         // (Only user-changed properties were saved by getState)
116960         if (defined(state.hidden)) {
116961             me.hidden = state.hidden;
116962         }
116963         if (defined(state.locked)) {
116964             me.locked = state.locked;
116965         }
116966         if (defined(state.sortable)) {
116967             me.sortable = state.sortable;
116968         }
116969         if (defined(state.width)) {
116970             delete me.flex;
116971             me.width = state.width;
116972         } else if (defined(state.flex)) {
116973             delete me.width;
116974             me.flex = state.flex;
116975         }
116976     },
116977
116978     getColumnState: function () {
116979         var me = this,
116980             columns = [],
116981             state = {
116982                 id: me.headerId
116983             };
116984
116985         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
116986         
116987         if (me.isGroupHeader) {
116988             me.items.each(function(column){
116989                 columns.push(column.getColumnState());
116990             });
116991             if (columns.length) {
116992                 state.columns = columns;
116993             }
116994         } else if (me.isSubHeader && me.ownerCt.hidden) {
116995             // don't set hidden on the children so they can auto height
116996             delete me.hidden;
116997         }
116998
116999         if ('width' in state) {
117000             delete state.flex; // width wins
117001         }
117002         return state;
117003     },
117004
117005     /**
117006      * Sets the header text for this Column.
117007      * @param {String} text The header to display on this Column.
117008      */
117009     setText: function(text) {
117010         this.text = text;
117011         if (this.rendered) {
117012             this.textEl.update(text);
117013         }
117014     },
117015
117016     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
117017     // Group Headers are themselves HeaderContainers
117018     getOwnerHeaderCt: function() {
117019         return this.up(':not([isHeader])');
117020     },
117021
117022     /**
117023      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
117024      * is a group column, it returns `false`.
117025      * @return {Number}
117026      */
117027     getIndex: function() {
117028         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
117029     },
117030
117031     onRender: function() {
117032         var me = this,
117033             grid = me.up('tablepanel');
117034
117035         // Disable the menu if there's nothing to show in the menu, ie:
117036         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
117037         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
117038             me.menuDisabled = true;
117039         }
117040         me.callParent(arguments);
117041     },
117042
117043     afterRender: function() {
117044         var me = this,
117045             el = me.el;
117046
117047         me.callParent(arguments);
117048
117049         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
117050
117051         me.mon(el, {
117052             click:     me.onElClick,
117053             dblclick:  me.onElDblClick,
117054             scope:     me
117055         });
117056
117057         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
117058         // must be fixed when focus management will be implemented.
117059         if (!Ext.isIE8 || !Ext.isStrict) {
117060             me.mon(me.getFocusEl(), {
117061                 focus: me.onTitleMouseOver,
117062                 blur: me.onTitleMouseOut,
117063                 scope: me
117064             });
117065         }
117066
117067         me.mon(me.titleContainer, {
117068             mouseenter:  me.onTitleMouseOver,
117069             mouseleave:  me.onTitleMouseOut,
117070             scope:      me
117071         });
117072
117073         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
117074             enter: me.onEnterKey,
117075             down: me.onDownKey,
117076             scope: me
117077         });
117078     },
117079
117080     /**
117081      * Sets the width of this Column.
117082      * @param {Number} width New width.
117083      */
117084     setWidth: function(width, /* private - used internally */ doLayout) {
117085         var me = this,
117086             headerCt = me.ownerCt,
117087             siblings,
117088             len, i,
117089             oldWidth = me.getWidth(),
117090             groupWidth = 0,
117091             sibling;
117092
117093         if (width !== oldWidth) {
117094             me.oldWidth = oldWidth;
117095
117096             // Non-flexed Headers may never be squeezed in the event of a shortfall so
117097             // always set the minWidth to their current width.
117098             me.minWidth = me.width = width;
117099
117100             // Bubble size changes upwards to group headers
117101             if (headerCt.isGroupHeader) {
117102                 siblings = headerCt.items.items;
117103                 len = siblings.length;
117104
117105                 for (i = 0; i < len; i++) {
117106                     sibling = siblings[i];
117107                     if (!sibling.hidden) {
117108                         groupWidth += (sibling === me) ? width : sibling.getWidth();
117109                     }
117110                 }
117111                 headerCt.setWidth(groupWidth, doLayout);
117112             } else if (doLayout !== false) {
117113                 // Allow the owning Container to perform the sizing
117114                 headerCt.doLayout();
117115             }
117116         }
117117     },
117118
117119     afterComponentLayout: function(width, height) {
117120         var me = this,
117121             ownerHeaderCt = this.getOwnerHeaderCt();
117122
117123         me.callParent(arguments);
117124
117125         // Only changes at the base level inform the grid's HeaderContainer which will update the View
117126         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
117127         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
117128         if (width && !me.isGroupHeader && ownerHeaderCt) {
117129             ownerHeaderCt.onHeaderResize(me, width, true);
117130         }
117131         if (me.oldWidth && (width !== me.oldWidth)) {
117132             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
117133         }
117134         delete me.oldWidth;
117135     },
117136
117137     // private
117138     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
117139     // Total available header height must be passed to enable padding for inner elements to be calculated.
117140     setPadding: function(headerHeight) {
117141         var me = this,
117142             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
117143
117144         // Top title containing element must stretch to match height of sibling group headers
117145         if (!me.isGroupHeader) {
117146             if (me.titleContainer.getHeight() < headerHeight) {
117147                 me.titleContainer.dom.style.height = headerHeight + 'px';
117148             }
117149         }
117150         headerHeight = me.titleContainer.getViewSize().height;
117151
117152         // Vertically center the header text in potentially vertically stretched header
117153         if (lineHeight) {
117154             me.titleContainer.setStyle({
117155                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
117156             });
117157         }
117158
117159         // Only IE needs this
117160         if (Ext.isIE && me.triggerEl) {
117161             me.triggerEl.setHeight(headerHeight);
117162         }
117163     },
117164
117165     onDestroy: function() {
117166         var me = this;
117167         // force destroy on the textEl, IE reports a leak
117168         Ext.destroy(me.textEl, me.keyNav);
117169         delete me.keyNav;
117170         me.callParent(arguments);
117171     },
117172
117173     onTitleMouseOver: function() {
117174         this.titleContainer.addCls(this.hoverCls);
117175     },
117176
117177     onTitleMouseOut: function() {
117178         this.titleContainer.removeCls(this.hoverCls);
117179     },
117180
117181     onDownKey: function(e) {
117182         if (this.triggerEl) {
117183             this.onElClick(e, this.triggerEl.dom || this.el.dom);
117184         }
117185     },
117186
117187     onEnterKey: function(e) {
117188         this.onElClick(e, this.el.dom);
117189     },
117190
117191     /**
117192      * @private
117193      * Double click
117194      * @param e
117195      * @param t
117196      */
117197     onElDblClick: function(e, t) {
117198         var me = this,
117199             ownerCt = me.ownerCt;
117200         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
117201             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
117202         }
117203     },
117204
117205     onElClick: function(e, t) {
117206
117207         // The grid's docked HeaderContainer.
117208         var me = this,
117209             ownerHeaderCt = me.getOwnerHeaderCt();
117210
117211         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
117212             // Firefox doesn't check the current target in a within check.
117213             // Therefore we check the target directly and then within (ancestors)
117214             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
117215                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
117216             // if its not on the left hand edge, sort
117217             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
117218                 me.toggleSortState();
117219                 ownerHeaderCt.onHeaderClick(me, e, t);
117220             }
117221         }
117222     },
117223
117224     /**
117225      * @private
117226      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
117227      * @param {String} type Event type, eg 'click'
117228      * @param {Ext.view.Table} view TableView Component
117229      * @param {HTMLElement} cell Cell HtmlElement the event took place within
117230      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
117231      * @param {Number} cellIndex Cell index within the row
117232      * @param {Ext.EventObject} e Original event
117233      */
117234     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
117235         return this.fireEvent.apply(this, arguments);
117236     },
117237
117238     toggleSortState: function() {
117239         var me = this,
117240             idx,
117241             nextIdx;
117242
117243         if (me.sortable) {
117244             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
117245
117246             nextIdx = (idx + 1) % me.possibleSortStates.length;
117247             me.setSortState(me.possibleSortStates[nextIdx]);
117248         }
117249     },
117250
117251     doSort: function(state) {
117252         var ds = this.up('tablepanel').store;
117253         ds.sort({
117254             property: this.getSortParam(),
117255             direction: state
117256         });
117257     },
117258
117259     /**
117260      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
117261      * need to be overriden in most cases.
117262      * @return {String}
117263      */
117264     getSortParam: function() {
117265         return this.dataIndex;
117266     },
117267
117268     //setSortState: function(state, updateUI) {
117269     //setSortState: function(state, doSort) {
117270     setSortState: function(state, skipClear, initial) {
117271         var me = this,
117272             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
117273             ascCls = colSortClsPrefix + 'ASC',
117274             descCls = colSortClsPrefix + 'DESC',
117275             nullCls = colSortClsPrefix + 'null',
117276             ownerHeaderCt = me.getOwnerHeaderCt(),
117277             oldSortState = me.sortState;
117278
117279         if (oldSortState !== state && me.getSortParam()) {
117280             me.addCls(colSortClsPrefix + state);
117281             // don't trigger a sort on the first time, we just want to update the UI
117282             if (state && !initial) {
117283                 me.doSort(state);
117284             }
117285             switch (state) {
117286                 case 'DESC':
117287                     me.removeCls([ascCls, nullCls]);
117288                     break;
117289                 case 'ASC':
117290                     me.removeCls([descCls, nullCls]);
117291                     break;
117292                 case null:
117293                     me.removeCls([ascCls, descCls]);
117294                     break;
117295             }
117296             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
117297                 ownerHeaderCt.clearOtherSortStates(me);
117298             }
117299             me.sortState = state;
117300             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
117301         }
117302     },
117303
117304     hide: function() {
117305         var me = this,
117306             items,
117307             len, i,
117308             lb,
117309             newWidth = 0,
117310             ownerHeaderCt = me.getOwnerHeaderCt();
117311
117312         // Hiding means setting to zero width, so cache the width
117313         me.oldWidth = me.getWidth();
117314
117315         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
117316         if (me.isGroupHeader) {
117317             items = me.items.items;
117318             me.callParent(arguments);
117319             ownerHeaderCt.onHeaderHide(me);
117320             for (i = 0, len = items.length; i < len; i++) {
117321                 items[i].hidden = true;
117322                 ownerHeaderCt.onHeaderHide(items[i], true);
117323             }
117324             return;
117325         }
117326
117327         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117328         lb = me.ownerCt.componentLayout.layoutBusy;
117329         me.ownerCt.componentLayout.layoutBusy = true;
117330         me.callParent(arguments);
117331         me.ownerCt.componentLayout.layoutBusy = lb;
117332
117333         // Notify owning HeaderContainer
117334         ownerHeaderCt.onHeaderHide(me);
117335
117336         if (me.ownerCt.isGroupHeader) {
117337             // If we've just hidden the last header in a group, then hide the group
117338             items = me.ownerCt.query('>:not([hidden])');
117339             if (!items.length) {
117340                 me.ownerCt.hide();
117341             }
117342             // Size the group down to accommodate fewer sub headers
117343             else {
117344                 for (i = 0, len = items.length; i < len; i++) {
117345                     newWidth += items[i].getWidth();
117346                 }
117347                 me.ownerCt.minWidth = newWidth;
117348                 me.ownerCt.setWidth(newWidth);
117349             }
117350         }
117351     },
117352
117353     show: function() {
117354         var me = this,
117355             ownerCt = me.ownerCt,
117356             ownerCtCompLayout = ownerCt.componentLayout,
117357             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
117358             ownerCtLayout = ownerCt.layout,
117359             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
117360             items,
117361             len, i,
117362             item,
117363             newWidth = 0;
117364
117365         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117366
117367         // Suspend our owner's layouts (both component and container):
117368         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
117369
117370         me.callParent(arguments);
117371
117372         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
117373         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
117374
117375         // If a sub header, ensure that the group header is visible
117376         if (me.isSubHeader) {
117377             if (!ownerCt.isVisible()) {
117378                 ownerCt.show();
117379             }
117380         }
117381
117382         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
117383         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
117384             items = me.query('>*');
117385             for (i = 0, len = items.length; i < len; i++) {
117386                 item = items[i];
117387                 item.preventLayout = true;
117388                 item.show();
117389                 newWidth += item.getWidth();
117390                 delete item.preventLayout;
117391             }
117392             me.setWidth(newWidth);
117393         }
117394
117395         // Resize the owning group to accommodate
117396         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
117397             items = ownerCt.query('>:not([hidden])');
117398             for (i = 0, len = items.length; i < len; i++) {
117399                 newWidth += items[i].getWidth();
117400             }
117401             ownerCt.minWidth = newWidth;
117402             ownerCt.setWidth(newWidth);
117403         }
117404
117405         // Notify owning HeaderContainer
117406         ownerCt = me.getOwnerHeaderCt();
117407         if (ownerCt) {
117408             ownerCt.onHeaderShow(me, me.preventLayout);
117409         }
117410     },
117411
117412     getDesiredWidth: function() {
117413         var me = this;
117414         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
117415             // headers always have either a width or a flex
117416             // because HeaderContainer sets a defaults width
117417             // therefore we can ignore the natural width
117418             // we use the componentLayout's tracked width so that
117419             // we can calculate the desired width when rendered
117420             // but not visible because its being obscured by a layout
117421             return me.componentLayout.lastComponentSize.width;
117422         // Flexed but yet to be rendered this could be the case
117423         // where a HeaderContainer and Headers are simply used as data
117424         // structures and not rendered.
117425         }
117426         else if (me.flex) {
117427             // this is going to be wrong, the defaultWidth
117428             return me.width;
117429         }
117430         else {
117431             return me.width;
117432         }
117433     },
117434
117435     getCellSelector: function() {
117436         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
117437     },
117438
117439     getCellInnerSelector: function() {
117440         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
117441     },
117442
117443     isOnLeftEdge: function(e) {
117444         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
117445     },
117446
117447     isOnRightEdge: function(e) {
117448         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
117449     }
117450
117451     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
117452     // when the editing plugin is injected
117453
117454     /**
117455      * @method getEditor
117456      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
117457      * associated with the Header the method will return false. If the field has not been instantiated it will be
117458      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
117459      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
117460      * @param {Object} defaultField An object representing a default field to be created
117461      * @return {Ext.form.field.Field} field
117462      */
117463     /**
117464      * @method setEditor
117465      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
117466      * been enabled on the grid.
117467      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
117468      * assumed.
117469      */
117470 });
117471
117472 /**
117473  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
117474  * an automatic row numbering column.
117475  * 
117476  * Usage:
117477  *
117478  *     columns: [
117479  *         {xtype: 'rownumberer'},
117480  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
117481  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
117482  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
117483  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
117484  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
117485  *     ]
117486  *
117487  */
117488 Ext.define('Ext.grid.RowNumberer', {
117489     extend: 'Ext.grid.column.Column',
117490     alias: 'widget.rownumberer',
117491
117492     /**
117493      * @cfg {String} text
117494      * Any valid text or HTML fragment to display in the header cell for the row number column.
117495      */
117496     text: "&#160",
117497
117498     /**
117499      * @cfg {Number} width
117500      * The default width in pixels of the row number column.
117501      */
117502     width: 23,
117503
117504     /**
117505      * @cfg {Boolean} sortable
117506      * True if the row number column is sortable.
117507      * @hide
117508      */
117509     sortable: false,
117510
117511     align: 'right',
117512
117513     constructor : function(config){
117514         this.callParent(arguments);
117515         if (this.rowspan) {
117516             this.renderer = Ext.Function.bind(this.renderer, this);
117517         }
117518     },
117519
117520     // private
117521     resizable: false,
117522     hideable: false,
117523     menuDisabled: true,
117524     dataIndex: '',
117525     cls: Ext.baseCSSPrefix + 'row-numberer',
117526     rowspan: undefined,
117527
117528     // private
117529     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
117530         if (this.rowspan){
117531             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
117532         }
117533
117534         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
117535         return store.indexOfTotal(record) + 1;
117536     }
117537 });
117538
117539 /**
117540  * @class Ext.view.DropZone
117541  * @extends Ext.dd.DropZone
117542  * @private
117543  */
117544 Ext.define('Ext.view.DropZone', {
117545     extend: 'Ext.dd.DropZone',
117546
117547     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117548     indicatorCls: 'x-grid-drop-indicator',
117549
117550     constructor: function(config) {
117551         var me = this;
117552         Ext.apply(me, config);
117553
117554         // Create a ddGroup unless one has been configured.
117555         // User configuration of ddGroups allows users to specify which
117556         // DD instances can interact with each other. Using one
117557         // based on the id of the View would isolate it and mean it can only
117558         // interact with a DragZone on the same View also using a generated ID.
117559         if (!me.ddGroup) {
117560             me.ddGroup = 'view-dd-zone-' + me.view.id;
117561         }
117562
117563         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
117564         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
117565         // same element, so a DragZone on this same View must use the View's parent element as its element.
117566         me.callParent([me.view.el]);
117567     },
117568
117569 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
117570 //  its data does not become corrupted by processing mouse events.
117571     fireViewEvent: function() {
117572         var me = this,
117573             result;
117574
117575         me.lock();
117576         result = me.view.fireEvent.apply(me.view, arguments);
117577         me.unlock();
117578         return result;
117579     },
117580
117581     getTargetFromEvent : function(e) {
117582         var node = e.getTarget(this.view.getItemSelector()),
117583             mouseY, nodeList, testNode, i, len, box;
117584
117585 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
117586 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
117587         if (!node) {
117588             mouseY = e.getPageY();
117589             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
117590                 testNode = nodeList[i];
117591                 box = Ext.fly(testNode).getBox();
117592                 if (mouseY <= box.bottom) {
117593                     return testNode;
117594                 }
117595             }
117596         }
117597         return node;
117598     },
117599
117600     getIndicator: function() {
117601         var me = this;
117602
117603         if (!me.indicator) {
117604             me.indicator = Ext.createWidget('component', {
117605                 html: me.indicatorHtml,
117606                 cls: me.indicatorCls,
117607                 ownerCt: me.view,
117608                 floating: true,
117609                 shadow: false
117610             });
117611         }
117612         return me.indicator;
117613     },
117614
117615     getPosition: function(e, node) {
117616         var y      = e.getXY()[1],
117617             region = Ext.fly(node).getRegion(),
117618             pos;
117619
117620         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
117621             pos = "before";
117622         } else {
117623             pos = "after";
117624         }
117625         return pos;
117626     },
117627
117628     /**
117629      * @private Determines whether the record at the specified offset from the passed record
117630      * is in the drag payload.
117631      * @param records
117632      * @param record
117633      * @param offset
117634      * @returns {Boolean} True if the targeted record is in the drag payload
117635      */
117636     containsRecordAtOffset: function(records, record, offset) {
117637         if (!record) {
117638             return false;
117639         }
117640         var view = this.view,
117641             recordIndex = view.indexOf(record),
117642             nodeBefore = view.getNode(recordIndex + offset),
117643             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
117644
117645         return recordBefore && Ext.Array.contains(records, recordBefore);
117646     },
117647
117648     positionIndicator: function(node, data, e) {
117649         var me = this,
117650             view = me.view,
117651             pos = me.getPosition(e, node),
117652             overRecord = view.getRecord(node),
117653             draggingRecords = data.records,
117654             indicator, indicatorY;
117655
117656         if (!Ext.Array.contains(draggingRecords, overRecord) && (
117657             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
117658             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
117659         )) {
117660             me.valid = true;
117661
117662             if (me.overRecord != overRecord || me.currentPosition != pos) {
117663
117664                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
117665                 if (pos == 'after') {
117666                     indicatorY += Ext.fly(node).getHeight();
117667                 }
117668                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
117669
117670                 // Cache the overRecord and the 'before' or 'after' indicator.
117671                 me.overRecord = overRecord;
117672                 me.currentPosition = pos;
117673             }
117674         } else {
117675             me.invalidateDrop();
117676         }
117677     },
117678
117679     invalidateDrop: function() {
117680         if (this.valid) {
117681             this.valid = false;
117682             this.getIndicator().hide();
117683         }
117684     },
117685
117686     // The mouse is over a View node
117687     onNodeOver: function(node, dragZone, e, data) {
117688         var me = this;
117689
117690         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
117691             me.positionIndicator(node, data, e);
117692         }
117693         return me.valid ? me.dropAllowed : me.dropNotAllowed;
117694     },
117695
117696     // Moved out of the DropZone without dropping.
117697     // Remove drop position indicator
117698     notifyOut: function(node, dragZone, e, data) {
117699         var me = this;
117700
117701         me.callParent(arguments);
117702         delete me.overRecord;
117703         delete me.currentPosition;
117704         if (me.indicator) {
117705             me.indicator.hide();
117706         }
117707     },
117708
117709     // The mouse is past the end of all nodes (or there are no nodes)
117710     onContainerOver : function(dd, e, data) {
117711         var me = this,
117712             view = me.view,
117713             count = view.store.getCount();
117714
117715         // There are records, so position after the last one
117716         if (count) {
117717             me.positionIndicator(view.getNode(count - 1), data, e);
117718         }
117719
117720         // No records, position the indicator at the top
117721         else {
117722             delete me.overRecord;
117723             delete me.currentPosition;
117724             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
117725             me.valid = true;
117726         }
117727         return me.dropAllowed;
117728     },
117729
117730     onContainerDrop : function(dd, e, data) {
117731         return this.onNodeDrop(dd, null, e, data);
117732     },
117733
117734     onNodeDrop: function(node, dragZone, e, data) {
117735         var me = this,
117736             dropped = false,
117737
117738             // Create a closure to perform the operation which the event handler may use.
117739             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
117740             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
117741             // and complete the drop gesture at some point in the future by calling this function.
117742             processDrop = function () {
117743                 me.invalidateDrop();
117744                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
117745                 dropped = true;
117746                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
117747             },
117748             performOperation = false;
117749
117750         if (me.valid) {
117751             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
117752             if (performOperation !== false) {
117753                 // If the processDrop function was called in the event handler, do not do it again.
117754                 if (!dropped) {
117755                     processDrop();
117756                 }
117757             }
117758         }
117759         return performOperation;
117760     },
117761     
117762     destroy: function(){
117763         Ext.destroy(this.indicator);
117764         delete this.indicator;
117765         this.callParent();
117766     }
117767 });
117768
117769 Ext.define('Ext.grid.ViewDropZone', {
117770     extend: 'Ext.view.DropZone',
117771
117772     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117773     indicatorCls: 'x-grid-drop-indicator',
117774
117775     handleNodeDrop : function(data, record, position) {
117776         var view = this.view,
117777             store = view.getStore(),
117778             index, records, i, len;
117779
117780         // If the copy flag is set, create a copy of the Models with the same IDs
117781         if (data.copy) {
117782             records = data.records;
117783             data.records = [];
117784             for (i = 0, len = records.length; i < len; i++) {
117785                 data.records.push(records[i].copy(records[i].getId()));
117786             }
117787         } else {
117788             /*
117789              * Remove from the source store. We do this regardless of whether the store
117790              * is the same bacsue the store currently doesn't handle moving records
117791              * within the store. In the future it should be possible to do this.
117792              * Here was pass the isMove parameter if we're moving to the same view.
117793              */
117794             data.view.store.remove(data.records, data.view === view);
117795         }
117796
117797         index = store.indexOf(record);
117798
117799         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
117800         if (position !== 'before') {
117801             index++;
117802         }
117803         store.insert(index, data.records);
117804         view.getSelectionModel().select(data.records);
117805     }
117806 });
117807 /**
117808  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
117809  * handler for each icon.
117810  *
117811  *     @example
117812  *     Ext.create('Ext.data.Store', {
117813  *         storeId:'employeeStore',
117814  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
117815  *         data:[
117816  *             {firstname:"Michael", lastname:"Scott"},
117817  *             {firstname:"Dwight", lastname:"Schrute"},
117818  *             {firstname:"Jim", lastname:"Halpert"},
117819  *             {firstname:"Kevin", lastname:"Malone"},
117820  *             {firstname:"Angela", lastname:"Martin"}
117821  *         ]
117822  *     });
117823  *
117824  *     Ext.create('Ext.grid.Panel', {
117825  *         title: 'Action Column Demo',
117826  *         store: Ext.data.StoreManager.lookup('employeeStore'),
117827  *         columns: [
117828  *             {text: 'First Name',  dataIndex:'firstname'},
117829  *             {text: 'Last Name',  dataIndex:'lastname'},
117830  *             {
117831  *                 xtype:'actioncolumn',
117832  *                 width:50,
117833  *                 items: [{
117834  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
117835  *                     tooltip: 'Edit',
117836  *                     handler: function(grid, rowIndex, colIndex) {
117837  *                         var rec = grid.getStore().getAt(rowIndex);
117838  *                         alert("Edit " + rec.get('firstname'));
117839  *                     }
117840  *                 },{
117841  *                     icon: 'extjs/examples/restful/images/delete.png',
117842  *                     tooltip: 'Delete',
117843  *                     handler: function(grid, rowIndex, colIndex) {
117844  *                         var rec = grid.getStore().getAt(rowIndex);
117845  *                         alert("Terminate " + rec.get('firstname'));
117846  *                     }
117847  *                 }]
117848  *             }
117849  *         ],
117850  *         width: 250,
117851  *         renderTo: Ext.getBody()
117852  *     });
117853  *
117854  * The action column can be at any index in the columns array, and a grid can have any number of
117855  * action columns.
117856  */
117857 Ext.define('Ext.grid.column.Action', {
117858     extend: 'Ext.grid.column.Column',
117859     alias: ['widget.actioncolumn'],
117860     alternateClassName: 'Ext.grid.ActionColumn',
117861
117862     /**
117863      * @cfg {String} icon
117864      * The URL of an image to display as the clickable element in the column. Defaults to
117865      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
117866      */
117867     /**
117868      * @cfg {String} iconCls
117869      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
117870      * a `{@link #getClass}` function.
117871      */
117872     /**
117873      * @cfg {Function} handler
117874      * A function called when the icon is clicked.
117875      * @cfg {Ext.view.Table} handler.view The owning TableView.
117876      * @cfg {Number} handler.rowIndex The row index clicked on.
117877      * @cfg {Number} handler.colIndex The column index clicked on.
117878      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
117879      * @cfg {Event} handler.e The click event.
117880      */
117881     /**
117882      * @cfg {Object} scope
117883      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
117884      * Defaults to this Column.
117885      */
117886     /**
117887      * @cfg {String} tooltip
117888      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
117889      * have been initialized.
117890      */
117891     /* @cfg {Boolean} disabled
117892      * If true, the action will not respond to click events, and will be displayed semi-opaque.
117893      */
117894     /**
117895      * @cfg {Boolean} [stopSelection=true]
117896      * Prevent grid _row_ selection upon mousedown.
117897      */
117898     /**
117899      * @cfg {Function} getClass
117900      * A function which returns the CSS class to apply to the icon image.
117901      *
117902      * @cfg {Object} getClass.v The value of the column's configured field (if any).
117903      *
117904      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
117905      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
117906      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
117907      * element *within* the table cell (e.g. 'style="color:red;"').
117908      *
117909      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
117910      *
117911      * @cfg {Number} getClass.rowIndex The row index..
117912      *
117913      * @cfg {Number} getClass.colIndex The column index.
117914      *
117915      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
117916      */
117917     /**
117918      * @cfg {Object[]} items
117919      * An Array which may contain multiple icon definitions, each element of which may contain:
117920      *
117921      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
117922      *
117923      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
117924      * configure the item with a `getClass` function.
117925      *
117926      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
117927      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
117928      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
117929      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
117930      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
117931      * container element _within_ the table cell (e.g. 'style="color:red;"').
117932      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
117933      * @cfg {Number} items.getClass.rowIndex The row index..
117934      * @cfg {Number} items.getClass.colIndex The column index.
117935      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
117936      *
117937      * @cfg {Function} items.handler A function called when the icon is clicked.
117938      *
117939      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
117940      * are executed. Fallback defaults are this Column's configured scope, then this Column.
117941      *
117942      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
117943      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
117944      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
117945      */
117946     /**
117947      * @property {Array} items
117948      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
117949      * an `enable` and `disable` method added which will enable and disable the associated action, and
117950      * update the displayed icon accordingly.
117951      */
117952     header: '&#160;',
117953
117954     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
117955
117956     /**
117957      * @cfg {String} altText
117958      * The alt text to use for the image element.
117959      */
117960     altText: '',
117961
117962     sortable: false,
117963
117964     constructor: function(config) {
117965         var me = this,
117966             cfg = Ext.apply({}, config),
117967             items = cfg.items || [me],
117968             l = items.length,
117969             i,
117970             item;
117971
117972         // This is a Container. Delete the items config to be reinstated after construction.
117973         delete cfg.items;
117974         me.callParent([cfg]);
117975
117976         // Items is an array property of ActionColumns
117977         me.items = items;
117978
117979 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
117980 //      class name x-action-col-{n}
117981         me.renderer = function(v, meta) {
117982 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
117983             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
117984
117985             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
117986             for (i = 0; i < l; i++) {
117987                 item = items[i];
117988                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
117989                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
117990                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
117991                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
117992                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
117993                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
117994             }
117995             return v;
117996         };
117997     },
117998
117999     /**
118000      * Enables this ActionColumn's action at the specified index.
118001      */
118002     enableAction: function(index) {
118003         var me = this;
118004
118005         if (!index) {
118006             index = 0;
118007         } else if (!Ext.isNumber(index)) {
118008             index = Ext.Array.indexOf(me.items, index);
118009         }
118010         me.items[index].disabled = false;
118011         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
118012     },
118013
118014     /**
118015      * Disables this ActionColumn's action at the specified index.
118016      */
118017     disableAction: function(index) {
118018         var me = this;
118019
118020         if (!index) {
118021             index = 0;
118022         } else if (!Ext.isNumber(index)) {
118023             index = Ext.Array.indexOf(me.items, index);
118024         }
118025         me.items[index].disabled = true;
118026         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
118027     },
118028
118029     destroy: function() {
118030         delete this.items;
118031         delete this.renderer;
118032         return this.callParent(arguments);
118033     },
118034
118035     /**
118036      * @private
118037      * Process and refire events routed from the GridView's processEvent method.
118038      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
118039      * Returns the event handler's status to allow canceling of GridView's bubbling process.
118040      */
118041     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
118042         var me = this,
118043             match = e.getTarget().className.match(me.actionIdRe),
118044             item, fn;
118045             
118046         if (match) {
118047             item = me.items[parseInt(match[1], 10)];
118048             if (item) {
118049                 if (type == 'click') {
118050                     fn = item.handler || me.handler;
118051                     if (fn && !item.disabled) {
118052                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
118053                     }
118054                 } else if (type == 'mousedown' && item.stopSelection !== false) {
118055                     return false;
118056                 }
118057             }
118058         }
118059         return me.callParent(arguments);
118060     },
118061
118062     cascade: function(fn, scope) {
118063         fn.call(scope||this, this);
118064     },
118065
118066     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
118067     getRefItems: function() {
118068         return [];
118069     }
118070 });
118071 /**
118072  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
118073  * config option of {@link Ext.grid.column.Column} for more details.
118074  *
118075  *     @example
118076  *     Ext.create('Ext.data.Store', {
118077  *        storeId:'sampleStore',
118078  *        fields:[
118079  *            {name: 'framework', type: 'string'},
118080  *            {name: 'rocks', type: 'boolean'}
118081  *        ],
118082  *        data:{'items':[
118083  *            { 'framework': "Ext JS 4",     'rocks': true  },
118084  *            { 'framework': "Sencha Touch", 'rocks': true  },
118085  *            { 'framework': "Ext GWT",      'rocks': true  }, 
118086  *            { 'framework': "Other Guys",   'rocks': false } 
118087  *        ]},
118088  *        proxy: {
118089  *            type: 'memory',
118090  *            reader: {
118091  *                type: 'json',
118092  *                root: 'items'
118093  *            }
118094  *        }
118095  *     });
118096  *     
118097  *     Ext.create('Ext.grid.Panel', {
118098  *         title: 'Boolean Column Demo',
118099  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118100  *         columns: [
118101  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
118102  *             {
118103  *                 xtype: 'booleancolumn', 
118104  *                 text: 'Rocks',
118105  *                 trueText: 'Yes',
118106  *                 falseText: 'No', 
118107  *                 dataIndex: 'rocks'
118108  *             }
118109  *         ],
118110  *         height: 200,
118111  *         width: 400,
118112  *         renderTo: Ext.getBody()
118113  *     });
118114  */
118115 Ext.define('Ext.grid.column.Boolean', {
118116     extend: 'Ext.grid.column.Column',
118117     alias: ['widget.booleancolumn'],
118118     alternateClassName: 'Ext.grid.BooleanColumn',
118119
118120     /**
118121      * @cfg {String} trueText
118122      * The string returned by the renderer when the column value is not falsey.
118123      */
118124     trueText: 'true',
118125
118126     /**
118127      * @cfg {String} falseText
118128      * The string returned by the renderer when the column value is falsey (but not undefined).
118129      */
118130     falseText: 'false',
118131
118132     /**
118133      * @cfg {String} undefinedText
118134      * The string returned by the renderer when the column value is undefined.
118135      */
118136     undefinedText: '&#160;',
118137
118138     constructor: function(cfg){
118139         this.callParent(arguments);
118140         var trueText      = this.trueText,
118141             falseText     = this.falseText,
118142             undefinedText = this.undefinedText;
118143
118144         this.renderer = function(value){
118145             if(value === undefined){
118146                 return undefinedText;
118147             }
118148             if(!value || value === 'false'){
118149                 return falseText;
118150             }
118151             return trueText;
118152         };
118153     }
118154 });
118155 /**
118156  * A Column definition class which renders a passed date according to the default locale, or a configured
118157  * {@link #format}.
118158  *
118159  *     @example
118160  *     Ext.create('Ext.data.Store', {
118161  *         storeId:'sampleStore',
118162  *         fields:[
118163  *             { name: 'symbol', type: 'string' },
118164  *             { name: 'date',   type: 'date' },
118165  *             { name: 'change', type: 'number' },
118166  *             { name: 'volume', type: 'number' },
118167  *             { name: 'topday', type: 'date' }                        
118168  *         ],
118169  *         data:[
118170  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
118171  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
118172  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
118173  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
118174  *         ]
118175  *     });
118176  *     
118177  *     Ext.create('Ext.grid.Panel', {
118178  *         title: 'Date Column Demo',
118179  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118180  *         columns: [
118181  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
118182  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
118183  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118184  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
118185  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
118186  *         ],
118187  *         height: 200,
118188  *         width: 450,
118189  *         renderTo: Ext.getBody()
118190  *     });
118191  */
118192 Ext.define('Ext.grid.column.Date', {
118193     extend: 'Ext.grid.column.Column',
118194     alias: ['widget.datecolumn'],
118195     requires: ['Ext.Date'],
118196     alternateClassName: 'Ext.grid.DateColumn',
118197
118198     /**
118199      * @cfg {String} format
118200      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
118201      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
118202      * in a locale file.
118203      */
118204
118205     initComponent: function(){
118206         var me = this;
118207         
118208         me.callParent(arguments);
118209         if (!me.format) {
118210             me.format = Ext.Date.defaultFormat;
118211         }
118212         me.renderer = Ext.util.Format.dateRenderer(me.format);
118213     }
118214 });
118215 /**
118216  * A Column definition class which renders a numeric data field according to a {@link #format} string.
118217  *
118218  *     @example
118219  *     Ext.create('Ext.data.Store', {
118220  *        storeId:'sampleStore',
118221  *        fields:[
118222  *            { name: 'symbol', type: 'string' },
118223  *            { name: 'price',  type: 'number' },
118224  *            { name: 'change', type: 'number' },
118225  *            { name: 'volume', type: 'number' },            
118226  *        ],
118227  *        data:[
118228  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
118229  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
118230  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
118231  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
118232  *        ]
118233  *     });
118234  *     
118235  *     Ext.create('Ext.grid.Panel', {
118236  *         title: 'Number Column Demo',
118237  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118238  *         columns: [
118239  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
118240  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
118241  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118242  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
118243  *         ],
118244  *         height: 200,
118245  *         width: 400,
118246  *         renderTo: Ext.getBody()
118247  *     });
118248  */
118249 Ext.define('Ext.grid.column.Number', {
118250     extend: 'Ext.grid.column.Column',
118251     alias: ['widget.numbercolumn'],
118252     requires: ['Ext.util.Format'],
118253     alternateClassName: 'Ext.grid.NumberColumn',
118254
118255     /**
118256      * @cfg {String} format
118257      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
118258      */
118259     format : '0,000.00',
118260
118261     constructor: function(cfg) {
118262         this.callParent(arguments);
118263         this.renderer = Ext.util.Format.numberRenderer(this.format);
118264     }
118265 });
118266 /**
118267  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
118268  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
118269  * {@link Ext.XTemplate XTemplate}.
118270  * 
118271  *     @example
118272  *     Ext.create('Ext.data.Store', {
118273  *         storeId:'employeeStore',
118274  *         fields:['firstname', 'lastname', 'senority', 'department'],
118275  *         groupField: 'department',
118276  *         data:[
118277  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
118278  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
118279  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
118280  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
118281  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
118282  *         ]
118283  *     });
118284  *     
118285  *     Ext.create('Ext.grid.Panel', {
118286  *         title: 'Column Template Demo',
118287  *         store: Ext.data.StoreManager.lookup('employeeStore'),
118288  *         columns: [
118289  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
118290  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
118291  *         ],
118292  *         height: 200,
118293  *         width: 300,
118294  *         renderTo: Ext.getBody()
118295  *     });
118296  */
118297 Ext.define('Ext.grid.column.Template', {
118298     extend: 'Ext.grid.column.Column',
118299     alias: ['widget.templatecolumn'],
118300     requires: ['Ext.XTemplate'],
118301     alternateClassName: 'Ext.grid.TemplateColumn',
118302
118303     /**
118304      * @cfg {String/Ext.XTemplate} tpl
118305      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
118306      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
118307      * column's rendered value.
118308      */
118309
118310     constructor: function(cfg){
118311         var me = this,
118312             tpl;
118313             
118314         me.callParent(arguments);
118315         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
118316
118317         me.renderer = function(value, p, record) {
118318             var data = Ext.apply({}, record.data, record.getAssociatedData());
118319             return tpl.apply(data);
118320         };
118321     }
118322 });
118323
118324 /**
118325  * @class Ext.grid.feature.Feature
118326  * @extends Ext.util.Observable
118327  * 
118328  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
118329  * hooks that allows the developer to inject additional functionality at certain points throughout the 
118330  * grid creation cycle. This class provides the base template methods that are available to the developer,
118331  * it should be extended.
118332  * 
118333  * There are several built in features that extend this class, for example:
118334  *
118335  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
118336  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
118337  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
118338  * 
118339  * ## Using Features
118340  * A feature is added to the grid by specifying it an array of features in the configuration:
118341  * 
118342  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
118343  *     Ext.create('Ext.grid.Panel', {
118344  *         // other options
118345  *         features: [groupingFeature]
118346  *     });
118347  * 
118348  * @abstract
118349  */
118350 Ext.define('Ext.grid.feature.Feature', {
118351     extend: 'Ext.util.Observable',
118352     alias: 'feature.feature',
118353     
118354     isFeature: true,
118355     disabled: false,
118356     
118357     /**
118358      * @property {Boolean}
118359      * Most features will expose additional events, some may not and will
118360      * need to change this to false.
118361      */
118362     hasFeatureEvent: true,
118363     
118364     /**
118365      * @property {String}
118366      * Prefix to use when firing events on the view.
118367      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
118368      */
118369     eventPrefix: null,
118370     
118371     /**
118372      * @property {String}
118373      * Selector used to determine when to fire the event with the eventPrefix.
118374      */
118375     eventSelector: null,
118376     
118377     /**
118378      * @property {Ext.view.Table}
118379      * Reference to the TableView.
118380      */
118381     view: null,
118382     
118383     /**
118384      * @property {Ext.grid.Panel}
118385      * Reference to the grid panel
118386      */
118387     grid: null,
118388     
118389     /**
118390      * Most features will not modify the data returned to the view.
118391      * This is limited to one feature that manipulates the data per grid view.
118392      */
118393     collectData: false,
118394         
118395     getFeatureTpl: function() {
118396         return '';
118397     },
118398     
118399     /**
118400      * Abstract method to be overriden when a feature should add additional
118401      * arguments to its event signature. By default the event will fire:
118402      * - view - The underlying Ext.view.Table
118403      * - featureTarget - The matched element by the defined {@link #eventSelector}
118404      *
118405      * The method must also return the eventName as the first index of the array
118406      * to be passed to fireEvent.
118407      * @template
118408      */
118409     getFireEventArgs: function(eventName, view, featureTarget, e) {
118410         return [eventName, view, featureTarget, e];
118411     },
118412     
118413     /**
118414      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
118415      * @template
118416      */
118417     attachEvents: function() {
118418         
118419     },
118420     
118421     getFragmentTpl: function() {
118422         return;
118423     },
118424     
118425     /**
118426      * Allows a feature to mutate the metaRowTpl.
118427      * The array received as a single argument can be manipulated to add things
118428      * on the end/begining of a particular row.
118429      * @template
118430      */
118431     mutateMetaRowTpl: function(metaRowTplArray) {
118432         
118433     },
118434     
118435     /**
118436      * Allows a feature to inject member methods into the metaRowTpl. This is
118437      * important for embedding functionality which will become part of the proper
118438      * row tpl.
118439      * @template
118440      */
118441     getMetaRowTplFragments: function() {
118442         return {};
118443     },
118444
118445     getTableFragments: function() {
118446         return {};
118447     },
118448     
118449     /**
118450      * Provide additional data to the prepareData call within the grid view.
118451      * @param {Object} data The data for this particular record.
118452      * @param {Number} idx The row index for this record.
118453      * @param {Ext.data.Model} record The record instance
118454      * @param {Object} orig The original result from the prepareData call to massage.
118455      * @template
118456      */
118457     getAdditionalData: function(data, idx, record, orig) {
118458         return {};
118459     },
118460     
118461     /**
118462      * Enable a feature
118463      */
118464     enable: function() {
118465         this.disabled = false;
118466     },
118467     
118468     /**
118469      * Disable a feature
118470      */
118471     disable: function() {
118472         this.disabled = true;
118473     }
118474     
118475 });
118476 /**
118477  * @class Ext.grid.feature.AbstractSummary
118478  * @extends Ext.grid.feature.Feature
118479  * A small abstract class that contains the shared behaviour for any summary
118480  * calculations to be used in the grid.
118481  */
118482 Ext.define('Ext.grid.feature.AbstractSummary', {
118483     
118484     /* Begin Definitions */
118485    
118486     extend: 'Ext.grid.feature.Feature',
118487     
118488     alias: 'feature.abstractsummary',
118489    
118490     /* End Definitions */
118491    
118492    /**
118493     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
118494     */
118495     showSummaryRow: true,
118496     
118497     // @private
118498     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
118499     
118500     /**
118501      * Toggle whether or not to show the summary row.
118502      * @param {Boolean} visible True to show the summary row
118503      */
118504     toggleSummaryRow: function(visible){
118505         this.showSummaryRow = !!visible;
118506     },
118507     
118508     /**
118509      * Gets any fragments to be used in the tpl
118510      * @private
118511      * @return {Object} The fragments
118512      */
118513     getSummaryFragments: function(){
118514         var fragments = {};
118515         if (this.showSummaryRow) {
118516             Ext.apply(fragments, {
118517                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
118518             });
118519         }
118520         return fragments;
118521     },
118522     
118523     /**
118524      * Prints a summary row
118525      * @private
118526      * @param {Object} index The index in the template
118527      * @return {String} The value of the summary row
118528      */
118529     printSummaryRow: function(index){
118530         var inner = this.view.getTableChunker().metaRowTpl.join(''),
118531             prefix = Ext.baseCSSPrefix;
118532         
118533         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
118534         inner = inner.replace('{{id}}', '{gridSummaryValue}');
118535         inner = inner.replace(this.nestedIdRe, '{id$1}');  
118536         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
118537         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
118538         inner = Ext.create('Ext.XTemplate', inner, {
118539             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
118540         });
118541         
118542         return inner.applyTemplate({
118543             columns: this.getPrintData(index)
118544         });
118545     },
118546     
118547     /**
118548      * Gets the value for the column from the attached data.
118549      * @param {Ext.grid.column.Column} column The header
118550      * @param {Object} data The current data
118551      * @return {String} The value to be rendered
118552      */
118553     getColumnValue: function(column, summaryData){
118554         var comp     = Ext.getCmp(column.id),
118555             value    = summaryData[column.id],
118556             renderer = comp.summaryRenderer;
118557
118558         if (renderer) {
118559             value = renderer.call(
118560                 comp.scope || this,
118561                 value,
118562                 summaryData,
118563                 column.dataIndex
118564             );
118565         }
118566         return value;
118567     },
118568     
118569     /**
118570      * Get the summary data for a field.
118571      * @private
118572      * @param {Ext.data.Store} store The store to get the data from
118573      * @param {String/Function} type The type of aggregation. If a function is specified it will
118574      * be passed to the stores aggregate function.
118575      * @param {String} field The field to aggregate on
118576      * @param {Boolean} group True to aggregate in grouped mode 
118577      * @return {Number/String/Object} See the return type for the store functions.
118578      */
118579     getSummary: function(store, type, field, group){
118580         if (type) {
118581             if (Ext.isFunction(type)) {
118582                 return store.aggregate(type, null, group);
118583             }
118584             
118585             switch (type) {
118586                 case 'count':
118587                     return store.count(group);
118588                 case 'min':
118589                     return store.min(field, group);
118590                 case 'max':
118591                     return store.max(field, group);
118592                 case 'sum':
118593                     return store.sum(field, group);
118594                 case 'average':
118595                     return store.average(field, group);
118596                 default:
118597                     return group ? {} : '';
118598                     
118599             }
118600         }
118601     }
118602     
118603 });
118604
118605 /**
118606  * @class Ext.grid.feature.Chunking
118607  * @extends Ext.grid.feature.Feature
118608  */
118609 Ext.define('Ext.grid.feature.Chunking', {
118610     extend: 'Ext.grid.feature.Feature',
118611     alias: 'feature.chunking',
118612     
118613     chunkSize: 20,
118614     rowHeight: Ext.isIE ? 27 : 26,
118615     visibleChunk: 0,
118616     hasFeatureEvent: false,
118617     attachEvents: function() {
118618         var grid = this.view.up('gridpanel'),
118619             scroller = grid.down('gridscroller[dock=right]');
118620         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
118621         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
118622     },
118623     
118624     onBodyScroll: function(e, t) {
118625         var view = this.view,
118626             top  = t.scrollTop,
118627             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
118628         if (nextChunk !== this.visibleChunk) {
118629         
118630             this.visibleChunk = nextChunk;
118631             view.refresh();
118632             view.el.dom.scrollTop = top;
118633             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
118634             view.el.dom.scrollTop = top;
118635         }
118636     },
118637     
118638     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
118639         var o = {
118640             fullWidth: orig.fullWidth,
118641             chunks: []
118642         },
118643         //headerCt = this.view.headerCt,
118644         //colums = headerCt.getColumnsForTpl(),
118645         recordCount = orig.rows.length,
118646         start = 0,
118647         i = 0,
118648         visibleChunk = this.visibleChunk,
118649         chunk,
118650         rows,
118651         chunkLength;
118652
118653         for (; start < recordCount; start+=this.chunkSize, i++) {
118654             if (start+this.chunkSize > recordCount) {
118655                 chunkLength = recordCount - start;
118656             } else {
118657                 chunkLength = this.chunkSize;
118658             }
118659             
118660             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
118661                 rows = orig.rows.slice(start, start+this.chunkSize);
118662             } else {
118663                 rows = [];
118664             }
118665             o.chunks.push({
118666                 rows: rows,
118667                 fullWidth: fullWidth,
118668                 chunkHeight: chunkLength * this.rowHeight
118669             });
118670         }
118671         
118672         
118673         return o;
118674     },
118675     
118676     getTableFragments: function() {
118677         return {
118678             openTableWrap: function() {
118679                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
118680             },
118681             closeTableWrap: function() {
118682                 return '</div></tpl>';
118683             }
118684         };
118685     }
118686 });
118687
118688 /**
118689  * @class Ext.grid.feature.Grouping
118690  * @extends Ext.grid.feature.Feature
118691  * 
118692  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
118693  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
118694  * underneath. The groups can also be expanded and collapsed.
118695  * 
118696  * ## Extra Events
118697  * This feature adds several extra events that will be fired on the grid to interact with the groups:
118698  *
118699  *  - {@link #groupclick}
118700  *  - {@link #groupdblclick}
118701  *  - {@link #groupcontextmenu}
118702  *  - {@link #groupexpand}
118703  *  - {@link #groupcollapse}
118704  * 
118705  * ## Menu Augmentation
118706  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
118707  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
118708  * by thew user is {@link #enableNoGroups}.
118709  * 
118710  * ## Controlling Group Text
118711  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
118712  * the default display.
118713  * 
118714  * ## Example Usage
118715  * 
118716  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
118717  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
118718  *         startCollapsed: true // start all groups collapsed
118719  *     });
118720  * 
118721  * @ftype grouping
118722  * @author Nicolas Ferrero
118723  */
118724 Ext.define('Ext.grid.feature.Grouping', {
118725     extend: 'Ext.grid.feature.Feature',
118726     alias: 'feature.grouping',
118727
118728     eventPrefix: 'group',
118729     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
118730
118731     constructor: function() {
118732         var me = this;
118733         
118734         me.collapsedState = {};
118735         me.callParent(arguments);
118736     },
118737     
118738     /**
118739      * @event groupclick
118740      * @param {Ext.view.Table} view
118741      * @param {HTMLElement} node
118742      * @param {String} group The name of the group
118743      * @param {Ext.EventObject} e
118744      */
118745
118746     /**
118747      * @event groupdblclick
118748      * @param {Ext.view.Table} view
118749      * @param {HTMLElement} node
118750      * @param {String} group The name of the group
118751      * @param {Ext.EventObject} e
118752      */
118753
118754     /**
118755      * @event groupcontextmenu
118756      * @param {Ext.view.Table} view
118757      * @param {HTMLElement} node
118758      * @param {String} group The name of the group
118759      * @param {Ext.EventObject} e
118760      */
118761
118762     /**
118763      * @event groupcollapse
118764      * @param {Ext.view.Table} view
118765      * @param {HTMLElement} node
118766      * @param {String} group The name of the group
118767      * @param {Ext.EventObject} e
118768      */
118769
118770     /**
118771      * @event groupexpand
118772      * @param {Ext.view.Table} view
118773      * @param {HTMLElement} node
118774      * @param {String} group The name of the group
118775      * @param {Ext.EventObject} e
118776      */
118777
118778     /**
118779      * @cfg {String} groupHeaderTpl
118780      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
118781      * Defaults to 'Group: {name}'
118782      */
118783     groupHeaderTpl: 'Group: {name}',
118784
118785     /**
118786      * @cfg {Number} depthToIndent
118787      * Number of pixels to indent per grouping level
118788      */
118789     depthToIndent: 17,
118790
118791     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
118792     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
118793
118794     /**
118795      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
118796      */
118797     groupByText : 'Group By This Field',
118798     /**
118799      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
118800      */
118801     showGroupsText : 'Show in Groups',
118802
118803     /**
118804      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
118805      */
118806     hideGroupedHeader : false,
118807
118808     /**
118809      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
118810      */
118811     startCollapsed : false,
118812
118813     /**
118814      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
118815      */
118816     enableGroupingMenu : true,
118817
118818     /**
118819      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
118820      */
118821     enableNoGroups : true,
118822     
118823     enable: function() {
118824         var me    = this,
118825             view  = me.view,
118826             store = view.store,
118827             groupToggleMenuItem;
118828             
118829         me.lastGroupField = me.getGroupField();
118830
118831         if (me.lastGroupIndex) {
118832             store.group(me.lastGroupIndex);
118833         }
118834         me.callParent();
118835         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118836         groupToggleMenuItem.setChecked(true, true);
118837         me.refreshIf();
118838     },
118839
118840     disable: function() {
118841         var me    = this,
118842             view  = me.view,
118843             store = view.store,
118844             remote = store.remoteGroup,
118845             groupToggleMenuItem,
118846             lastGroup;
118847             
118848         lastGroup = store.groupers.first();
118849         if (lastGroup) {
118850             me.lastGroupIndex = lastGroup.property;
118851             me.block();
118852             store.clearGrouping();
118853             me.unblock();
118854         }
118855         
118856         me.callParent();
118857         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118858         groupToggleMenuItem.setChecked(true, true);
118859         groupToggleMenuItem.setChecked(false, true);
118860         if (!remote) {
118861             view.refresh();
118862         }
118863     },
118864     
118865     refreshIf: function() {
118866         if (this.blockRefresh !== true) {
118867             this.view.refresh();
118868         }    
118869     },
118870
118871     getFeatureTpl: function(values, parent, x, xcount) {
118872         var me = this;
118873         
118874         return [
118875             '<tpl if="typeof rows !== \'undefined\'">',
118876                 // group row tpl
118877                 '<tr class="' + Ext.baseCSSPrefix + 'grid-group-hd ' + (me.startCollapsed ? me.hdCollapsedCls : '') + ' {hdCollapsedCls}"><td class="' + Ext.baseCSSPrefix + 'grid-cell" colspan="' + parent.columns.length + '" {[this.indentByDepth(values)]}><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner"><div class="' + Ext.baseCSSPrefix + 'grid-group-title">{collapsed}' + me.groupHeaderTpl + '</div></div></td></tr>',
118878                 // this is the rowbody
118879                 '<tr id="{viewId}-gp-{name}" class="' + Ext.baseCSSPrefix + 'grid-group-body ' + (me.startCollapsed ? me.collapsedCls : '') + ' {collapsedCls}"><td colspan="' + parent.columns.length + '">{[this.recurse(values)]}</td></tr>',
118880             '</tpl>'
118881         ].join('');
118882     },
118883
118884     getFragmentTpl: function() {
118885         return {
118886             indentByDepth: this.indentByDepth,
118887             depthToIndent: this.depthToIndent
118888         };
118889     },
118890
118891     indentByDepth: function(values) {
118892         var depth = values.depth || 0;
118893         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
118894     },
118895
118896     // Containers holding these components are responsible for
118897     // destroying them, we are just deleting references.
118898     destroy: function() {
118899         var me = this;
118900         
118901         delete me.view;
118902         delete me.prunedHeader;
118903     },
118904
118905     // perhaps rename to afterViewRender
118906     attachEvents: function() {
118907         var me = this,
118908             view = me.view;
118909
118910         view.on({
118911             scope: me,
118912             groupclick: me.onGroupClick,
118913             rowfocus: me.onRowFocus
118914         });
118915         view.store.on('groupchange', me.onGroupChange, me);
118916
118917         me.pruneGroupedHeader();
118918
118919         if (me.enableGroupingMenu) {
118920             me.injectGroupingMenu();
118921         }
118922         me.lastGroupField = me.getGroupField();
118923         me.block();
118924         me.onGroupChange();
118925         me.unblock();
118926     },
118927     
118928     injectGroupingMenu: function() {
118929         var me       = this,
118930             view     = me.view,
118931             headerCt = view.headerCt;
118932         headerCt.showMenuBy = me.showMenuBy;
118933         headerCt.getMenuItems = me.getMenuItems();
118934     },
118935     
118936     showMenuBy: function(t, header) {
118937         var menu = this.getMenu(),
118938             groupMenuItem  = menu.down('#groupMenuItem'),
118939             groupableMth = header.groupable === false ?  'disable' : 'enable';
118940             
118941         groupMenuItem[groupableMth]();
118942         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
118943     },
118944     
118945     getMenuItems: function() {
118946         var me                 = this,
118947             groupByText        = me.groupByText,
118948             disabled           = me.disabled,
118949             showGroupsText     = me.showGroupsText,
118950             enableNoGroups     = me.enableNoGroups,
118951             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
118952             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
118953         
118954         // runs in the scope of headerCt
118955         return function() {
118956             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
118957             o.push('-', {
118958                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
118959                 itemId: 'groupMenuItem',
118960                 text: groupByText,
118961                 handler: groupMenuItemClick
118962             });
118963             if (enableNoGroups) {
118964                 o.push({
118965                     itemId: 'groupToggleMenuItem',
118966                     text: showGroupsText,
118967                     checked: !disabled,
118968                     checkHandler: groupToggleMenuItemClick
118969                 });
118970             }
118971             return o;
118972         };
118973     },
118974
118975
118976     /**
118977      * Group by the header the user has clicked on.
118978      * @private
118979      */
118980     onGroupMenuItemClick: function(menuItem, e) {
118981         var me = this,
118982             menu = menuItem.parentMenu,
118983             hdr  = menu.activeHeader,
118984             view = me.view,
118985             store = view.store,
118986             remote = store.remoteGroup;
118987
118988         delete me.lastGroupIndex;
118989         me.block();
118990         me.enable();
118991         store.group(hdr.dataIndex);
118992         me.pruneGroupedHeader();
118993         me.unblock();
118994         if (!remote) {
118995             view.refresh();
118996         }  
118997     },
118998     
118999     block: function(){
119000         this.blockRefresh = this.view.blockRefresh = true;
119001     },
119002     
119003     unblock: function(){
119004         this.blockRefresh = this.view.blockRefresh = false;
119005     },
119006
119007     /**
119008      * Turn on and off grouping via the menu
119009      * @private
119010      */
119011     onGroupToggleMenuItemClick: function(menuItem, checked) {
119012         this[checked ? 'enable' : 'disable']();
119013     },
119014
119015     /**
119016      * Prunes the grouped header from the header container
119017      * @private
119018      */
119019     pruneGroupedHeader: function() {
119020         var me         = this,
119021             view       = me.view,
119022             store      = view.store,
119023             groupField = me.getGroupField(),
119024             headerCt   = view.headerCt,
119025             header     = headerCt.down('header[dataIndex=' + groupField + ']');
119026
119027         if (header) {
119028             if (me.prunedHeader) {
119029                 me.prunedHeader.show();
119030             }
119031             me.prunedHeader = header;
119032             header.hide();
119033         }
119034     },
119035
119036     getGroupField: function(){
119037         var group = this.view.store.groupers.first();
119038         if (group) {
119039             return group.property;    
119040         }
119041         return ''; 
119042     },
119043
119044     /**
119045      * When a row gains focus, expand the groups above it
119046      * @private
119047      */
119048     onRowFocus: function(rowIdx) {
119049         var node    = this.view.getNode(rowIdx),
119050             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
119051
119052         if (groupBd) {
119053             // for multiple level groups, should expand every groupBd
119054             // above
119055             this.expand(groupBd);
119056         }
119057     },
119058
119059     /**
119060      * Expand a group by the groupBody
119061      * @param {Ext.Element} groupBd
119062      * @private
119063      */
119064     expand: function(groupBd) {
119065         var me = this,
119066             view = me.view,
119067             grid = view.up('gridpanel'),
119068             groupBdDom = Ext.getDom(groupBd);
119069             
119070         me.collapsedState[groupBdDom.id] = false;
119071
119072         groupBd.removeCls(me.collapsedCls);
119073         groupBd.prev().removeCls(me.hdCollapsedCls);
119074
119075         grid.determineScrollbars();
119076         grid.invalidateScroller();
119077         view.fireEvent('groupexpand');
119078     },
119079
119080     /**
119081      * Collapse a group by the groupBody
119082      * @param {Ext.Element} groupBd
119083      * @private
119084      */
119085     collapse: function(groupBd) {
119086         var me = this,
119087             view = me.view,
119088             grid = view.up('gridpanel'),
119089             groupBdDom = Ext.getDom(groupBd);
119090             
119091         me.collapsedState[groupBdDom.id] = true;
119092
119093         groupBd.addCls(me.collapsedCls);
119094         groupBd.prev().addCls(me.hdCollapsedCls);
119095
119096         grid.determineScrollbars();
119097         grid.invalidateScroller();
119098         view.fireEvent('groupcollapse');
119099     },
119100     
119101     onGroupChange: function(){
119102         var me = this,
119103             field = me.getGroupField(),
119104             menuItem;
119105             
119106         if (me.hideGroupedHeader) {
119107             if (me.lastGroupField) {
119108                 menuItem = me.getMenuItem(me.lastGroupField);
119109                 if (menuItem) {
119110                     menuItem.setChecked(true);
119111                 }
119112             }
119113             if (field) {
119114                 menuItem = me.getMenuItem(field);
119115                 if (menuItem) {
119116                     menuItem.setChecked(false);
119117                 }
119118             }
119119         }
119120         if (me.blockRefresh !== true) {
119121             me.view.refresh();
119122         }
119123         me.lastGroupField = field;
119124     },
119125     
119126     /**
119127      * Gets the related menu item for a dataIndex
119128      * @private
119129      * @return {Ext.grid.header.Container} The header
119130      */
119131     getMenuItem: function(dataIndex){
119132         var view = this.view,
119133             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
119134             menu = view.headerCt.getMenu();
119135             
119136         return menu.down('menuitem[headerId='+ header.id +']');
119137     },
119138
119139     /**
119140      * Toggle between expanded/collapsed state when clicking on
119141      * the group.
119142      * @private
119143      */
119144     onGroupClick: function(view, group, idx, foo, e) {
119145         var me = this,
119146             toggleCls = me.toggleCls,
119147             groupBd = Ext.fly(group.nextSibling, '_grouping');
119148
119149         if (groupBd.hasCls(me.collapsedCls)) {
119150             me.expand(groupBd);
119151         } else {
119152             me.collapse(groupBd);
119153         }
119154     },
119155
119156     // Injects isRow and closeRow into the metaRowTpl.
119157     getMetaRowTplFragments: function() {
119158         return {
119159             isRow: this.isRow,
119160             closeRow: this.closeRow
119161         };
119162     },
119163
119164     // injected into rowtpl and wrapped around metaRowTpl
119165     // becomes part of the standard tpl
119166     isRow: function() {
119167         return '<tpl if="typeof rows === \'undefined\'">';
119168     },
119169
119170     // injected into rowtpl and wrapped around metaRowTpl
119171     // becomes part of the standard tpl
119172     closeRow: function() {
119173         return '</tpl>';
119174     },
119175
119176     // isRow and closeRow are injected via getMetaRowTplFragments
119177     mutateMetaRowTpl: function(metaRowTpl) {
119178         metaRowTpl.unshift('{[this.isRow()]}');
119179         metaRowTpl.push('{[this.closeRow()]}');
119180     },
119181
119182     // injects an additional style attribute via tdAttrKey with the proper
119183     // amount of padding
119184     getAdditionalData: function(data, idx, record, orig) {
119185         var view = this.view,
119186             hCt  = view.headerCt,
119187             col  = hCt.items.getAt(0),
119188             o = {},
119189             tdAttrKey = col.id + '-tdAttr';
119190
119191         // maintain the current tdAttr that a user may ahve set.
119192         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
119193         o.collapsed = 'true';
119194         return o;
119195     },
119196
119197     // return matching preppedRecords
119198     getGroupRows: function(group, records, preppedRecords, fullWidth) {
119199         var me = this,
119200             children = group.children,
119201             rows = group.rows = [],
119202             view = me.view;
119203         group.viewId = view.id;
119204
119205         Ext.Array.each(records, function(record, idx) {
119206             if (Ext.Array.indexOf(children, record) != -1) {
119207                 rows.push(Ext.apply(preppedRecords[idx], {
119208                     depth: 1
119209                 }));
119210             }
119211         });
119212         delete group.children;
119213         group.fullWidth = fullWidth;
119214         if (me.collapsedState[view.id + '-gp-' + group.name]) {
119215             group.collapsedCls = me.collapsedCls;
119216             group.hdCollapsedCls = me.hdCollapsedCls;
119217         }
119218
119219         return group;
119220     },
119221
119222     // return the data in a grouped format.
119223     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
119224         var me    = this,
119225             store = me.view.store,
119226             groups;
119227             
119228         if (!me.disabled && store.isGrouped()) {
119229             groups = store.getGroups();
119230             Ext.Array.each(groups, function(group, idx){
119231                 me.getGroupRows(group, records, preppedRecords, fullWidth);
119232             }, me);
119233             return {
119234                 rows: groups,
119235                 fullWidth: fullWidth
119236             };
119237         }
119238         return o;
119239     },
119240     
119241     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
119242     // events that are fired on the view. Chose not to return the actual
119243     // group itself because of its expense and because developers can simply
119244     // grab the group via store.getGroups(groupName)
119245     getFireEventArgs: function(type, view, featureTarget, e) {
119246         var returnArray = [type, view, featureTarget],
119247             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
119248             groupBdId   = Ext.getDom(groupBd).id,
119249             prefix      = view.id + '-gp-',
119250             groupName   = groupBdId.substr(prefix.length);
119251         
119252         returnArray.push(groupName, e);
119253         
119254         return returnArray;
119255     }
119256 });
119257
119258 /**
119259  * @class Ext.grid.feature.GroupingSummary
119260  * @extends Ext.grid.feature.Grouping
119261  *
119262  * This feature adds an aggregate summary row at the bottom of each group that is provided
119263  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
119264  *
119265  * ## Calculation
119266  *
119267  * The summary value needs to be calculated for each column in the grid. This is controlled
119268  * by the summaryType option specified on the column. There are several built in summary types,
119269  * which can be specified as a string on the column configuration. These call underlying methods
119270  * on the store:
119271  *
119272  *  - {@link Ext.data.Store#count count}
119273  *  - {@link Ext.data.Store#sum sum}
119274  *  - {@link Ext.data.Store#min min}
119275  *  - {@link Ext.data.Store#max max}
119276  *  - {@link Ext.data.Store#average average}
119277  *
119278  * Alternatively, the summaryType can be a function definition. If this is the case,
119279  * the function is called with an array of records to calculate the summary value.
119280  *
119281  * ## Rendering
119282  *
119283  * Similar to a column, the summary also supports a summaryRenderer function. This
119284  * summaryRenderer is called before displaying a value. The function is optional, if
119285  * not specified the default calculated value is shown. The summaryRenderer is called with:
119286  *
119287  *  - value {Object} - The calculated value.
119288  *  - summaryData {Object} - Contains all raw summary values for the row.
119289  *  - field {String} - The name of the field we are calculating
119290  *
119291  * ## Example Usage
119292  *
119293  *     @example
119294  *     Ext.define('TestResult', {
119295  *         extend: 'Ext.data.Model',
119296  *         fields: ['student', 'subject', {
119297  *             name: 'mark',
119298  *             type: 'int'
119299  *         }]
119300  *     });
119301  *
119302  *     Ext.create('Ext.grid.Panel', {
119303  *         width: 200,
119304  *         height: 240,
119305  *         renderTo: document.body,
119306  *         features: [{
119307  *             groupHeaderTpl: 'Subject: {name}',
119308  *             ftype: 'groupingsummary'
119309  *         }],
119310  *         store: {
119311  *             model: 'TestResult',
119312  *             groupField: 'subject',
119313  *             data: [{
119314  *                 student: 'Student 1',
119315  *                 subject: 'Math',
119316  *                 mark: 84
119317  *             },{
119318  *                 student: 'Student 1',
119319  *                 subject: 'Science',
119320  *                 mark: 72
119321  *             },{
119322  *                 student: 'Student 2',
119323  *                 subject: 'Math',
119324  *                 mark: 96
119325  *             },{
119326  *                 student: 'Student 2',
119327  *                 subject: 'Science',
119328  *                 mark: 68
119329  *             }]
119330  *         },
119331  *         columns: [{
119332  *             dataIndex: 'student',
119333  *             text: 'Name',
119334  *             summaryType: 'count',
119335  *             summaryRenderer: function(value){
119336  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
119337  *             }
119338  *         }, {
119339  *             dataIndex: 'mark',
119340  *             text: 'Mark',
119341  *             summaryType: 'average'
119342  *         }]
119343  *     });
119344  */
119345 Ext.define('Ext.grid.feature.GroupingSummary', {
119346
119347     /* Begin Definitions */
119348
119349     extend: 'Ext.grid.feature.Grouping',
119350
119351     alias: 'feature.groupingsummary',
119352
119353     mixins: {
119354         summary: 'Ext.grid.feature.AbstractSummary'
119355     },
119356
119357     /* End Definitions */
119358
119359
119360    /**
119361     * Modifies the row template to include the summary row.
119362     * @private
119363     * @return {String} The modified template
119364     */
119365    getFeatureTpl: function() {
119366         var tpl = this.callParent(arguments);
119367
119368         if (this.showSummaryRow) {
119369             // lop off the end </tpl> so we can attach it
119370             tpl = tpl.replace('</tpl>', '');
119371             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
119372         }
119373         return tpl;
119374     },
119375
119376     /**
119377      * Gets any fragments needed for the template.
119378      * @private
119379      * @return {Object} The fragments
119380      */
119381     getFragmentTpl: function() {
119382         var me = this,
119383             fragments = me.callParent();
119384
119385         Ext.apply(fragments, me.getSummaryFragments());
119386         if (me.showSummaryRow) {
119387             // this gets called before render, so we'll setup the data here.
119388             me.summaryGroups = me.view.store.getGroups();
119389             me.summaryData = me.generateSummaryData();
119390         }
119391         return fragments;
119392     },
119393
119394     /**
119395      * Gets the data for printing a template row
119396      * @private
119397      * @param {Number} index The index in the template
119398      * @return {Array} The template values
119399      */
119400     getPrintData: function(index){
119401         var me = this,
119402             columns = me.view.headerCt.getColumnsForTpl(),
119403             i = 0,
119404             length = columns.length,
119405             data = [],
119406             name = me.summaryGroups[index - 1].name,
119407             active = me.summaryData[name],
119408             column;
119409
119410         for (; i < length; ++i) {
119411             column = columns[i];
119412             column.gridSummaryValue = this.getColumnValue(column, active);
119413             data.push(column);
119414         }
119415         return data;
119416     },
119417
119418     /**
119419      * Generates all of the summary data to be used when processing the template
119420      * @private
119421      * @return {Object} The summary data
119422      */
119423     generateSummaryData: function(){
119424         var me = this,
119425             data = {},
119426             remoteData = {},
119427             store = me.view.store,
119428             groupField = this.getGroupField(),
119429             reader = store.proxy.reader,
119430             groups = me.summaryGroups,
119431             columns = me.view.headerCt.getColumnsForTpl(),
119432             remote,
119433             i,
119434             length,
119435             fieldData,
119436             root,
119437             key,
119438             comp;
119439
119440         for (i = 0, length = groups.length; i < length; ++i) {
119441             data[groups[i].name] = {};
119442         }
119443
119444         /**
119445          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
119446          * summary objects. It allows to use server-side calculated summaries.
119447          */
119448         if (me.remoteRoot && reader.rawData) {
119449             // reset reader root and rebuild extractors to extract summaries data
119450             root = reader.root;
119451             reader.root = me.remoteRoot;
119452             reader.buildExtractors(true);
119453             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
119454                  remoteData[value[groupField]] = value;
119455             });
119456             // restore initial reader configuration
119457             reader.root = root;
119458             reader.buildExtractors(true);
119459         }
119460
119461         for (i = 0, length = columns.length; i < length; ++i) {
119462             comp = Ext.getCmp(columns[i].id);
119463             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
119464
119465             for (key in fieldData) {
119466                 if (fieldData.hasOwnProperty(key)) {
119467                     data[key][comp.id] = fieldData[key];
119468                 }
119469             }
119470
119471             for (key in remoteData) {
119472                 if (remoteData.hasOwnProperty(key)) {
119473                     remote = remoteData[key][comp.dataIndex];
119474                     if (remote !== undefined && data[key] !== undefined) {
119475                         data[key][comp.id] = remote;
119476                     }
119477                 }
119478             }
119479         }
119480         return data;
119481     }
119482 });
119483
119484 /**
119485  * @class Ext.grid.feature.RowBody
119486  * @extends Ext.grid.feature.Feature
119487  *
119488  * The rowbody feature enhances the grid's markup to have an additional
119489  * tr -> td -> div which spans the entire width of the original row.
119490  *
119491  * This is useful to to associate additional information with a particular
119492  * record in a grid.
119493  *
119494  * Rowbodies are initially hidden unless you override getAdditionalData.
119495  *
119496  * Will expose additional events on the gridview with the prefix of 'rowbody'.
119497  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
119498  *
119499  * @ftype rowbody
119500  */
119501 Ext.define('Ext.grid.feature.RowBody', {
119502     extend: 'Ext.grid.feature.Feature',
119503     alias: 'feature.rowbody',
119504     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
119505     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
119506     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
119507     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
119508
119509     eventPrefix: 'rowbody',
119510     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
119511     
119512     getRowBody: function(values) {
119513         return [
119514             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
119515                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
119516                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
119517                 '</td>',
119518             '</tr>'
119519         ].join('');
119520     },
119521     
119522     // injects getRowBody into the metaRowTpl.
119523     getMetaRowTplFragments: function() {
119524         return {
119525             getRowBody: this.getRowBody,
119526             rowBodyTrCls: this.rowBodyTrCls,
119527             rowBodyTdCls: this.rowBodyTdCls,
119528             rowBodyDivCls: this.rowBodyDivCls
119529         };
119530     },
119531
119532     mutateMetaRowTpl: function(metaRowTpl) {
119533         metaRowTpl.push('{[this.getRowBody(values)]}');
119534     },
119535
119536     /**
119537      * Provide additional data to the prepareData call within the grid view.
119538      * The rowbody feature adds 3 additional variables into the grid view's template.
119539      * These are rowBodyCls, rowBodyColspan, and rowBody.
119540      * @param {Object} data The data for this particular record.
119541      * @param {Number} idx The row index for this record.
119542      * @param {Ext.data.Model} record The record instance
119543      * @param {Object} orig The original result from the prepareData call to massage.
119544      */
119545     getAdditionalData: function(data, idx, record, orig) {
119546         var headerCt = this.view.headerCt,
119547             colspan  = headerCt.getColumnCount();
119548
119549         return {
119550             rowBody: "",
119551             rowBodyCls: this.rowBodyCls,
119552             rowBodyColspan: colspan
119553         };
119554     }
119555 });
119556 /**
119557  * @class Ext.grid.feature.RowWrap
119558  * @extends Ext.grid.feature.Feature
119559  * @private
119560  */
119561 Ext.define('Ext.grid.feature.RowWrap', {
119562     extend: 'Ext.grid.feature.Feature',
119563     alias: 'feature.rowwrap',
119564
119565     // turn off feature events.
119566     hasFeatureEvent: false,
119567     
119568     mutateMetaRowTpl: function(metaRowTpl) {        
119569         // Remove "x-grid-row" from the first row, note this could be wrong
119570         // if some other feature unshifted things in front.
119571         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
119572         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
119573         // 2
119574         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
119575         // 1
119576         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
119577         
119578         // 3
119579         metaRowTpl.push('</table>');
119580         // 4
119581         metaRowTpl.push('</div></td></tr>');
119582     },
119583     
119584     embedColSpan: function() {
119585         return '{colspan}';
119586     },
119587     
119588     embedFullWidth: function() {
119589         return '{fullWidth}';
119590     },
119591     
119592     getAdditionalData: function(data, idx, record, orig) {
119593         var headerCt = this.view.headerCt,
119594             colspan  = headerCt.getColumnCount(),
119595             fullWidth = headerCt.getFullWidth(),
119596             items    = headerCt.query('gridcolumn'),
119597             itemsLn  = items.length,
119598             i = 0,
119599             o = {
119600                 colspan: colspan,
119601                 fullWidth: fullWidth
119602             },
119603             id,
119604             tdClsKey,
119605             colResizerCls;
119606
119607         for (; i < itemsLn; i++) {
119608             id = items[i].id;
119609             tdClsKey = id + '-tdCls';
119610             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
119611             // give the inner td's the resizer class
119612             // while maintaining anything a user may have injected via a custom
119613             // renderer
119614             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
119615             // TODO: Unhackify the initial rendering width's
119616             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
119617             if (orig[id+'-tdAttr']) {
119618                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
119619             }
119620             
119621         }
119622
119623         return o;
119624     },
119625     
119626     getMetaRowTplFragments: function() {
119627         return {
119628             embedFullWidth: this.embedFullWidth,
119629             embedColSpan: this.embedColSpan
119630         };
119631     }
119632     
119633 });
119634 /**
119635  * @class Ext.grid.feature.Summary
119636  * @extends Ext.grid.feature.AbstractSummary
119637  * 
119638  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
119639  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
119640  * calculation and rendering.
119641  * 
119642  * ## Calculation
119643  * The summary value needs to be calculated for each column in the grid. This is controlled
119644  * by the summaryType option specified on the column. There are several built in summary types,
119645  * which can be specified as a string on the column configuration. These call underlying methods
119646  * on the store:
119647  *
119648  *  - {@link Ext.data.Store#count count}
119649  *  - {@link Ext.data.Store#sum sum}
119650  *  - {@link Ext.data.Store#min min}
119651  *  - {@link Ext.data.Store#max max}
119652  *  - {@link Ext.data.Store#average average}
119653  *
119654  * Alternatively, the summaryType can be a function definition. If this is the case,
119655  * the function is called with an array of records to calculate the summary value.
119656  * 
119657  * ## Rendering
119658  * Similar to a column, the summary also supports a summaryRenderer function. This
119659  * summaryRenderer is called before displaying a value. The function is optional, if
119660  * not specified the default calculated value is shown. The summaryRenderer is called with:
119661  *
119662  *  - value {Object} - The calculated value.
119663  *  - summaryData {Object} - Contains all raw summary values for the row.
119664  *  - field {String} - The name of the field we are calculating
119665  * 
119666  * ## Example Usage
119667  *
119668  *     @example
119669  *     Ext.define('TestResult', {
119670  *         extend: 'Ext.data.Model',
119671  *         fields: ['student', {
119672  *             name: 'mark',
119673  *             type: 'int'
119674  *         }]
119675  *     });
119676  *     
119677  *     Ext.create('Ext.grid.Panel', {
119678  *         width: 200,
119679  *         height: 140,
119680  *         renderTo: document.body,
119681  *         features: [{
119682  *             ftype: 'summary'
119683  *         }],
119684  *         store: {
119685  *             model: 'TestResult',
119686  *             data: [{
119687  *                 student: 'Student 1',
119688  *                 mark: 84
119689  *             },{
119690  *                 student: 'Student 2',
119691  *                 mark: 72
119692  *             },{
119693  *                 student: 'Student 3',
119694  *                 mark: 96
119695  *             },{
119696  *                 student: 'Student 4',
119697  *                 mark: 68
119698  *             }]
119699  *         },
119700  *         columns: [{
119701  *             dataIndex: 'student',
119702  *             text: 'Name',
119703  *             summaryType: 'count',
119704  *             summaryRenderer: function(value, summaryData, dataIndex) {
119705  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
119706  *             }
119707  *         }, {
119708  *             dataIndex: 'mark',
119709  *             text: 'Mark',
119710  *             summaryType: 'average'
119711  *         }]
119712  *     });
119713  */
119714 Ext.define('Ext.grid.feature.Summary', {
119715     
119716     /* Begin Definitions */
119717     
119718     extend: 'Ext.grid.feature.AbstractSummary',
119719     
119720     alias: 'feature.summary',
119721     
119722     /* End Definitions */
119723     
119724     /**
119725      * Gets any fragments needed for the template.
119726      * @private
119727      * @return {Object} The fragments
119728      */
119729     getFragmentTpl: function() {
119730         // this gets called before render, so we'll setup the data here.
119731         this.summaryData = this.generateSummaryData(); 
119732         return this.getSummaryFragments();
119733     },
119734     
119735     /**
119736      * Overrides the closeRows method on the template so we can include our own custom
119737      * footer.
119738      * @private
119739      * @return {Object} The custom fragments
119740      */
119741     getTableFragments: function(){
119742         if (this.showSummaryRow) {
119743             return {
119744                 closeRows: this.closeRows
119745             };
119746         }
119747     },
119748     
119749     /**
119750      * Provide our own custom footer for the grid.
119751      * @private
119752      * @return {String} The custom footer
119753      */
119754     closeRows: function() {
119755         return '</tpl>{[this.printSummaryRow()]}';
119756     },
119757     
119758     /**
119759      * Gets the data for printing a template row
119760      * @private
119761      * @param {Number} index The index in the template
119762      * @return {Array} The template values
119763      */
119764     getPrintData: function(index){
119765         var me = this,
119766             columns = me.view.headerCt.getColumnsForTpl(),
119767             i = 0,
119768             length = columns.length,
119769             data = [],
119770             active = me.summaryData,
119771             column;
119772             
119773         for (; i < length; ++i) {
119774             column = columns[i];
119775             column.gridSummaryValue = this.getColumnValue(column, active);
119776             data.push(column);
119777         }
119778         return data;
119779     },
119780     
119781     /**
119782      * Generates all of the summary data to be used when processing the template
119783      * @private
119784      * @return {Object} The summary data
119785      */
119786     generateSummaryData: function(){
119787         var me = this,
119788             data = {},
119789             store = me.view.store,
119790             columns = me.view.headerCt.getColumnsForTpl(),
119791             i = 0,
119792             length = columns.length,
119793             fieldData,
119794             key,
119795             comp;
119796             
119797         for (i = 0, length = columns.length; i < length; ++i) {
119798             comp = Ext.getCmp(columns[i].id);
119799             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
119800         }
119801         return data;
119802     }
119803 });
119804 /**
119805  * @class Ext.grid.header.DragZone
119806  * @extends Ext.dd.DragZone
119807  * @private
119808  */
119809 Ext.define('Ext.grid.header.DragZone', {
119810     extend: 'Ext.dd.DragZone',
119811     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119812     maxProxyWidth: 120,
119813
119814     constructor: function(headerCt) {
119815         this.headerCt = headerCt;
119816         this.ddGroup =  this.getDDGroup();
119817         this.callParent([headerCt.el]);
119818         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
119819     },
119820
119821     getDDGroup: function() {
119822         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119823     },
119824
119825     getDragData: function(e) {
119826         var header = e.getTarget('.'+this.colHeaderCls),
119827             headerCmp;
119828
119829         if (header) {
119830             headerCmp = Ext.getCmp(header.id);
119831             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
119832                 var ddel = document.createElement('div');
119833                 ddel.innerHTML = Ext.getCmp(header.id).text;
119834                 return {
119835                     ddel: ddel,
119836                     header: headerCmp
119837                 };
119838             }
119839         }
119840         return false;
119841     },
119842
119843     onBeforeDrag: function() {
119844         return !(this.headerCt.dragging || this.disabled);
119845     },
119846
119847     onInitDrag: function() {
119848         this.headerCt.dragging = true;
119849         this.callParent(arguments);
119850     },
119851
119852     onDragDrop: function() {
119853         this.headerCt.dragging = false;
119854         this.callParent(arguments);
119855     },
119856
119857     afterRepair: function() {
119858         this.callParent();
119859         this.headerCt.dragging = false;
119860     },
119861
119862     getRepairXY: function() {
119863         return this.dragData.header.el.getXY();
119864     },
119865     
119866     disable: function() {
119867         this.disabled = true;
119868     },
119869     
119870     enable: function() {
119871         this.disabled = false;
119872     }
119873 });
119874
119875 /**
119876  * @class Ext.grid.header.DropZone
119877  * @extends Ext.dd.DropZone
119878  * @private
119879  */
119880 Ext.define('Ext.grid.header.DropZone', {
119881     extend: 'Ext.dd.DropZone',
119882     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119883     proxyOffsets: [-4, -9],
119884
119885     constructor: function(headerCt){
119886         this.headerCt = headerCt;
119887         this.ddGroup = this.getDDGroup();
119888         this.callParent([headerCt.el]);
119889     },
119890
119891     getDDGroup: function() {
119892         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119893     },
119894
119895     getTargetFromEvent : function(e){
119896         return e.getTarget('.' + this.colHeaderCls);
119897     },
119898
119899     getTopIndicator: function() {
119900         if (!this.topIndicator) {
119901             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
119902                 cls: "col-move-top",
119903                 html: "&#160;"
119904             }, true);
119905         }
119906         return this.topIndicator;
119907     },
119908
119909     getBottomIndicator: function() {
119910         if (!this.bottomIndicator) {
119911             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
119912                 cls: "col-move-bottom",
119913                 html: "&#160;"
119914             }, true);
119915         }
119916         return this.bottomIndicator;
119917     },
119918
119919     getLocation: function(e, t) {
119920         var x      = e.getXY()[0],
119921             region = Ext.fly(t).getRegion(),
119922             pos, header;
119923
119924         if ((region.right - x) <= (region.right - region.left) / 2) {
119925             pos = "after";
119926         } else {
119927             pos = "before";
119928         }
119929         return {
119930             pos: pos,
119931             header: Ext.getCmp(t.id),
119932             node: t
119933         };
119934     },
119935
119936     positionIndicator: function(draggedHeader, node, e){
119937         var location = this.getLocation(e, node),
119938             header = location.header,
119939             pos    = location.pos,
119940             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
119941             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
119942             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
119943             topXY, bottomXY, headerCtEl, minX, maxX;
119944
119945         // Cannot drag beyond non-draggable start column
119946         if (!header.draggable && header.getIndex() == 0) {
119947             return false;
119948         }
119949
119950         this.lastLocation = location;
119951
119952         if ((draggedHeader !== header) &&
119953             ((pos === "before" && nextHd !== header) ||
119954             (pos === "after" && prevHd !== header)) &&
119955             !header.isDescendantOf(draggedHeader)) {
119956
119957             // As we move in between different DropZones that are in the same
119958             // group (such as the case when in a locked grid), invalidateDrop
119959             // on the other dropZones.
119960             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
119961                 ln = allDropZones.length,
119962                 i  = 0,
119963                 dropZone;
119964
119965             for (; i < ln; i++) {
119966                 dropZone = allDropZones[i];
119967                 if (dropZone !== this && dropZone.invalidateDrop) {
119968                     dropZone.invalidateDrop();
119969                 }
119970             }
119971
119972
119973             this.valid = true;
119974             topIndicator = this.getTopIndicator();
119975             bottomIndicator = this.getBottomIndicator();
119976             if (pos === 'before') {
119977                 topAnchor = 'tl';
119978                 bottomAnchor = 'bl';
119979             } else {
119980                 topAnchor = 'tr';
119981                 bottomAnchor = 'br';
119982             }
119983             topXY = header.el.getAnchorXY(topAnchor);
119984             bottomXY = header.el.getAnchorXY(bottomAnchor);
119985
119986             // constrain the indicators to the viewable section
119987             headerCtEl = this.headerCt.el;
119988             minX = headerCtEl.getLeft();
119989             maxX = headerCtEl.getRight();
119990
119991             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
119992             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
119993
119994             // adjust by offsets, this is to center the arrows so that they point
119995             // at the split point
119996             topXY[0] -= 4;
119997             topXY[1] -= 9;
119998             bottomXY[0] -= 4;
119999
120000             // position and show indicators
120001             topIndicator.setXY(topXY);
120002             bottomIndicator.setXY(bottomXY);
120003             topIndicator.show();
120004             bottomIndicator.show();
120005         // invalidate drop operation and hide indicators
120006         } else {
120007             this.invalidateDrop();
120008         }
120009     },
120010
120011     invalidateDrop: function() {
120012         this.valid = false;
120013         this.hideIndicators();
120014     },
120015
120016     onNodeOver: function(node, dragZone, e, data) {
120017         if (data.header.el.dom !== node) {
120018             this.positionIndicator(data.header, node, e);
120019         }
120020         return this.valid ? this.dropAllowed : this.dropNotAllowed;
120021     },
120022
120023     hideIndicators: function() {
120024         this.getTopIndicator().hide();
120025         this.getBottomIndicator().hide();
120026     },
120027
120028     onNodeOut: function() {
120029         this.hideIndicators();
120030     },
120031
120032     onNodeDrop: function(node, dragZone, e, data) {
120033         if (this.valid) {
120034             this.invalidateDrop();
120035             var hd = data.header,
120036                 lastLocation = this.lastLocation,
120037                 fromCt  = hd.ownerCt,
120038                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
120039                 toCt    = lastLocation.header.ownerCt,
120040                 toIdx   = toCt.items.indexOf(lastLocation.header),
120041                 headerCt = this.headerCt,
120042                 groupCt,
120043                 scrollerOwner;
120044
120045             if (lastLocation.pos === 'after') {
120046                 toIdx++;
120047             }
120048
120049             // If we are dragging in between two HeaderContainers that have had the lockable
120050             // mixin injected we will lock/unlock headers in between sections. Note that lockable
120051             // does NOT currently support grouped headers.
120052             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
120053                 scrollerOwner = fromCt.up('[scrollerOwner]');
120054                 scrollerOwner.lock(hd, toIdx);
120055             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
120056                 scrollerOwner = fromCt.up('[scrollerOwner]');
120057                 scrollerOwner.unlock(hd, toIdx);
120058             } else {
120059                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
120060                 // in between the same container.
120061                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
120062                     toIdx--;
120063                 }
120064
120065                 // Remove dragged header from where it was without destroying it or relaying its Container
120066                 if (fromCt !== toCt) {
120067                     fromCt.suspendLayout = true;
120068                     fromCt.remove(hd, false);
120069                     fromCt.suspendLayout = false;
120070                 }
120071
120072                 // Dragged the last header out of the fromCt group... The fromCt group must die
120073                 if (fromCt.isGroupHeader) {
120074                     if (!fromCt.items.getCount()) {
120075                         groupCt = fromCt.ownerCt;
120076                         groupCt.suspendLayout = true;
120077                         groupCt.remove(fromCt, false);
120078                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
120079                         groupCt.suspendLayout = false;
120080                     } else {
120081                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
120082                         fromCt.setWidth(fromCt.minWidth);
120083                     }
120084                 }
120085
120086                 // Move dragged header into its drop position
120087                 toCt.suspendLayout = true;
120088                 if (fromCt === toCt) {
120089                     toCt.move(fromIdx, toIdx);
120090                 } else {
120091                     toCt.insert(toIdx, hd);
120092                 }
120093                 toCt.suspendLayout = false;
120094
120095                 // Group headers acquire the aggregate width of their child headers
120096                 // Therefore a child header may not flex; it must contribute a fixed width.
120097                 // But we restore the flex value when moving back into the main header container
120098                 if (toCt.isGroupHeader) {
120099                     hd.savedFlex = hd.flex;
120100                     delete hd.flex;
120101                     hd.width = hd.getWidth();
120102                     // When there was previously a flex, we need to ensure we don't count for the
120103                     // border twice.
120104                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
120105                     toCt.setWidth(toCt.minWidth);
120106                 } else {
120107                     if (hd.savedFlex) {
120108                         hd.flex = hd.savedFlex;
120109                         delete hd.width;
120110                     }
120111                 }
120112
120113
120114                 // Refresh columns cache in case we remove an emptied group column
120115                 headerCt.purgeCache();
120116                 headerCt.doLayout();
120117                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
120118                 // Emptied group header can only be destroyed after the header and grid have been refreshed
120119                 if (!fromCt.items.getCount()) {
120120                     fromCt.destroy();
120121                 }
120122             }
120123         }
120124     }
120125 });
120126
120127 /**
120128  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
120129  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
120130  * in the {@link Ext.grid.column.Column column configuration}.
120131  *
120132  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
120133  * {@link Ext.grid.plugin.RowEditing}.
120134  */
120135 Ext.define('Ext.grid.plugin.Editing', {
120136     alias: 'editing.editing',
120137
120138     requires: [
120139         'Ext.grid.column.Column',
120140         'Ext.util.KeyNav'
120141     ],
120142
120143     mixins: {
120144         observable: 'Ext.util.Observable'
120145     },
120146
120147     /**
120148      * @cfg {Number} clicksToEdit
120149      * The number of clicks on a grid required to display the editor.
120150      */
120151     clicksToEdit: 2,
120152
120153     // private
120154     defaultFieldXType: 'textfield',
120155
120156     // cell, row, form
120157     editStyle: '',
120158
120159     constructor: function(config) {
120160         var me = this;
120161         Ext.apply(me, config);
120162
120163         me.addEvents(
120164             // Doc'ed in separate editing plugins
120165             'beforeedit',
120166
120167             // Doc'ed in separate editing plugins
120168             'edit',
120169
120170             // Doc'ed in separate editing plugins
120171             'validateedit'
120172         );
120173         me.mixins.observable.constructor.call(me);
120174         // TODO: Deprecated, remove in 5.0
120175         me.relayEvents(me, ['afteredit'], 'after');
120176     },
120177
120178     // private
120179     init: function(grid) {
120180         var me = this;
120181
120182         me.grid = grid;
120183         me.view = grid.view;
120184         me.initEvents();
120185         me.mon(grid, 'reconfigure', me.onReconfigure, me);
120186         me.onReconfigure();
120187
120188         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
120189         // Marks the grid as editable, so that the SelectionModel
120190         // can make appropriate decisions during navigation
120191         grid.isEditable = true;
120192         grid.editingPlugin = grid.view.editingPlugin = me;
120193     },
120194
120195     /**
120196      * Fires after the grid is reconfigured
120197      * @private
120198      */
120199     onReconfigure: function(){
120200         this.initFieldAccessors(this.view.getGridColumns());
120201     },
120202
120203     /**
120204      * @private
120205      * AbstractComponent calls destroy on all its plugins at destroy time.
120206      */
120207     destroy: function() {
120208         var me = this,
120209             grid = me.grid,
120210             headerCt = grid.headerCt,
120211             events = grid.events;
120212
120213         Ext.destroy(me.keyNav);
120214         me.removeFieldAccessors(grid.getView().getGridColumns());
120215
120216         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
120217         me.clearListeners();
120218
120219         delete me.grid.editingPlugin;
120220         delete me.grid.view.editingPlugin;
120221         delete me.grid;
120222         delete me.view;
120223         delete me.editor;
120224         delete me.keyNav;
120225     },
120226
120227     // private
120228     getEditStyle: function() {
120229         return this.editStyle;
120230     },
120231
120232     // private
120233     initFieldAccessors: function(column) {
120234         var me = this;
120235
120236         if (Ext.isArray(column)) {
120237             Ext.Array.forEach(column, me.initFieldAccessors, me);
120238             return;
120239         }
120240
120241         // Augment the Header class to have a getEditor and setEditor method
120242         // Important: Only if the header does not have its own implementation.
120243         Ext.applyIf(column, {
120244             getEditor: function(record, defaultField) {
120245                 return me.getColumnField(this, defaultField);
120246             },
120247
120248             setEditor: function(field) {
120249                 me.setColumnField(this, field);
120250             }
120251         });
120252     },
120253
120254     // private
120255     removeFieldAccessors: function(column) {
120256         var me = this;
120257
120258         if (Ext.isArray(column)) {
120259             Ext.Array.forEach(column, me.removeFieldAccessors, me);
120260             return;
120261         }
120262
120263         delete column.getEditor;
120264         delete column.setEditor;
120265     },
120266
120267     // private
120268     // remaps to the public API of Ext.grid.column.Column.getEditor
120269     getColumnField: function(columnHeader, defaultField) {
120270         var field = columnHeader.field;
120271
120272         if (!field && columnHeader.editor) {
120273             field = columnHeader.editor;
120274             delete columnHeader.editor;
120275         }
120276
120277         if (!field && defaultField) {
120278             field = defaultField;
120279         }
120280
120281         if (field) {
120282             if (Ext.isString(field)) {
120283                 field = { xtype: field };
120284             }
120285             if (Ext.isObject(field) && !field.isFormField) {
120286                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120287                 columnHeader.field = field;
120288             }
120289
120290             Ext.apply(field, {
120291                 name: columnHeader.dataIndex
120292             });
120293
120294             return field;
120295         }
120296     },
120297
120298     // private
120299     // remaps to the public API of Ext.grid.column.Column.setEditor
120300     setColumnField: function(column, field) {
120301         if (Ext.isObject(field) && !field.isFormField) {
120302             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120303         }
120304         column.field = field;
120305     },
120306
120307     // private
120308     initEvents: function() {
120309         var me = this;
120310         me.initEditTriggers();
120311         me.initCancelTriggers();
120312     },
120313
120314     // @abstract
120315     initCancelTriggers: Ext.emptyFn,
120316
120317     // private
120318     initEditTriggers: function() {
120319         var me = this,
120320             view = me.view,
120321             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
120322
120323         // Start editing
120324         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
120325         view.on('render', function() {
120326             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
120327                 enter: me.onEnterKey,
120328                 esc: me.onEscKey,
120329                 scope: me
120330             });
120331         }, me, { single: true });
120332     },
120333
120334     // private
120335     onEnterKey: function(e) {
120336         var me = this,
120337             grid = me.grid,
120338             selModel = grid.getSelectionModel(),
120339             record,
120340             columnHeader = grid.headerCt.getHeaderAtIndex(0);
120341
120342         // Calculate editing start position from SelectionModel
120343         // CellSelectionModel
120344         if (selModel.getCurrentPosition) {
120345             pos = selModel.getCurrentPosition();
120346             record = grid.store.getAt(pos.row);
120347             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
120348         }
120349         // RowSelectionModel
120350         else {
120351             record = selModel.getLastSelected();
120352         }
120353         me.startEdit(record, columnHeader);
120354     },
120355
120356     // private
120357     onEscKey: function(e) {
120358         this.cancelEdit();
120359     },
120360
120361     // private
120362     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
120363         this.startEdit(record, view.getHeaderAtIndex(colIdx));
120364     },
120365
120366     /**
120367      * @private
120368      * @template
120369      * Template method called before editing begins.
120370      * @param {Object} context The current editing context
120371      * @return {Boolean} Return false to cancel the editing process
120372      */
120373     beforeEdit: Ext.emptyFn,
120374
120375     /**
120376      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120377      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
120378      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
120379      */
120380     startEdit: function(record, columnHeader) {
120381         var me = this,
120382             context = me.getEditingContext(record, columnHeader);
120383
120384         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120385             return false;
120386         }
120387
120388         me.context = context;
120389         me.editing = true;
120390     },
120391
120392     /**
120393      * @private
120394      * Collects all information necessary for any subclasses to perform their editing functions.
120395      * @param record
120396      * @param columnHeader
120397      * @returns {Object} The editing context based upon the passed record and column
120398      */
120399     getEditingContext: function(record, columnHeader) {
120400         var me = this,
120401             grid = me.grid,
120402             store = grid.store,
120403             rowIdx,
120404             colIdx,
120405             view = grid.getView(),
120406             value;
120407
120408         // If they'd passed numeric row, column indices, look them up.
120409         if (Ext.isNumber(record)) {
120410             rowIdx = record;
120411             record = store.getAt(rowIdx);
120412         } else {
120413             rowIdx = store.indexOf(record);
120414         }
120415         if (Ext.isNumber(columnHeader)) {
120416             colIdx = columnHeader;
120417             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
120418         } else {
120419             colIdx = columnHeader.getIndex();
120420         }
120421
120422         value = record.get(columnHeader.dataIndex);
120423         return {
120424             grid: grid,
120425             record: record,
120426             field: columnHeader.dataIndex,
120427             value: value,
120428             row: view.getNode(rowIdx),
120429             column: columnHeader,
120430             rowIdx: rowIdx,
120431             colIdx: colIdx
120432         };
120433     },
120434
120435     /**
120436      * Cancels any active edit that is in progress.
120437      */
120438     cancelEdit: function() {
120439         this.editing = false;
120440     },
120441
120442     /**
120443      * Completes the edit if there is an active edit in progress.
120444      */
120445     completeEdit: function() {
120446         var me = this;
120447
120448         if (me.editing && me.validateEdit()) {
120449             me.fireEvent('edit', me.context);
120450         }
120451
120452         delete me.context;
120453         me.editing = false;
120454     },
120455
120456     // @abstract
120457     validateEdit: function() {
120458         var me = this,
120459             context = me.context;
120460
120461         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
120462     }
120463 });
120464
120465 /**
120466  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
120467  * cell will be editable at a time. The field that will be used for the editor is defined at the
120468  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
120469  *
120470  * If an editor is not specified for a particular column then that cell will not be editable and it will
120471  * be skipped when activated via the mouse or the keyboard.
120472  *
120473  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
120474  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
120475  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
120476  *
120477  *     @example
120478  *     Ext.create('Ext.data.Store', {
120479  *         storeId:'simpsonsStore',
120480  *         fields:['name', 'email', 'phone'],
120481  *         data:{'items':[
120482  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
120483  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
120484  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
120485  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
120486  *         ]},
120487  *         proxy: {
120488  *             type: 'memory',
120489  *             reader: {
120490  *                 type: 'json',
120491  *                 root: 'items'
120492  *             }
120493  *         }
120494  *     });
120495  *
120496  *     Ext.create('Ext.grid.Panel', {
120497  *         title: 'Simpsons',
120498  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
120499  *         columns: [
120500  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
120501  *             {header: 'Email', dataIndex: 'email', flex:1,
120502  *                 editor: {
120503  *                     xtype: 'textfield',
120504  *                     allowBlank: false
120505  *                 }
120506  *             },
120507  *             {header: 'Phone', dataIndex: 'phone'}
120508  *         ],
120509  *         selType: 'cellmodel',
120510  *         plugins: [
120511  *             Ext.create('Ext.grid.plugin.CellEditing', {
120512  *                 clicksToEdit: 1
120513  *             })
120514  *         ],
120515  *         height: 200,
120516  *         width: 400,
120517  *         renderTo: Ext.getBody()
120518  *     });
120519  */
120520 Ext.define('Ext.grid.plugin.CellEditing', {
120521     alias: 'plugin.cellediting',
120522     extend: 'Ext.grid.plugin.Editing',
120523     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
120524
120525     constructor: function() {
120526         /**
120527          * @event beforeedit
120528          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
120529          *
120530          * @param {Object} e An edit event with the following properties:
120531          *
120532          * - grid - The grid
120533          * - record - The record being edited
120534          * - field - The field name being edited
120535          * - value - The value for the field being edited.
120536          * - row - The grid table row
120537          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120538          * - rowIdx - The row index that is being edited
120539          * - colIdx - The column index that is being edited
120540          * - cancel - Set this to true to cancel the edit or return false from your handler.
120541          */
120542         /**
120543          * @event edit
120544          * Fires after a cell is edited. Usage example:
120545          *
120546          *     grid.on('edit', function(editor, e) {
120547          *         // commit the changes right after editing finished
120548          *         e.record.commit();
120549          *     };
120550          *
120551          * @param {Ext.grid.plugin.Editing} editor
120552          * @param {Object} e An edit event with the following properties:
120553          *
120554          * - grid - The grid
120555          * - record - The record that was edited
120556          * - field - The field name that was edited
120557          * - value - The value being set
120558          * - originalValue - The original value for the field, before the edit.
120559          * - row - The grid table row
120560          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
120561          * - rowIdx - The row index that was edited
120562          * - colIdx - The column index that was edited
120563          */
120564         /**
120565          * @event validateedit
120566          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
120567          * cancel the change.
120568          *
120569          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
120570          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
120571          * example) and then setting the field's new value in the Record directly:
120572          *
120573          *     grid.on('validateedit', function(editor, e) {
120574          *       var myTargetRow = 6;
120575          *
120576          *       if (e.row == myTargetRow) {
120577          *         e.cancel = true;
120578          *         e.record.data[e.field] = e.value;
120579          *       }
120580          *     });
120581          *
120582          * @param {Ext.grid.plugin.Editing} editor
120583          * @param {Object} e An edit event with the following properties:
120584          *
120585          * - grid - The grid
120586          * - record - The record being edited
120587          * - field - The field name being edited
120588          * - value - The value being set
120589          * - originalValue - The original value for the field, before the edit.
120590          * - row - The grid table row
120591          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120592          * - rowIdx - The row index that is being edited
120593          * - colIdx - The column index that is being edited
120594          * - cancel - Set this to true to cancel the edit or return false from your handler.
120595          */
120596         this.callParent(arguments);
120597         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
120598             return editor.editorId;
120599         });
120600         this.editTask = Ext.create('Ext.util.DelayedTask');
120601     },
120602     
120603     onReconfigure: function(){
120604         this.editors.clear();
120605         this.callParent();    
120606     },
120607
120608     /**
120609      * @private
120610      * AbstractComponent calls destroy on all its plugins at destroy time.
120611      */
120612     destroy: function() {
120613         var me = this;
120614         me.editTask.cancel();
120615         me.editors.each(Ext.destroy, Ext);
120616         me.editors.clear();
120617         me.callParent(arguments);
120618     },
120619     
120620     onBodyScroll: function() {
120621         var ed = this.getActiveEditor();
120622         if (ed && ed.field) {
120623             if (ed.field.triggerBlur) {
120624                 ed.field.triggerBlur();
120625             } else {
120626                 ed.field.blur();
120627             }
120628         }
120629     },
120630
120631     // private
120632     // Template method called from base class's initEvents
120633     initCancelTriggers: function() {
120634         var me   = this,
120635             grid = me.grid,
120636             view = grid.view;
120637             
120638         view.addElListener('mousewheel', me.cancelEdit, me);
120639         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
120640         me.mon(grid, {
120641             columnresize: me.cancelEdit,
120642             columnmove: me.cancelEdit,
120643             scope: me
120644         });
120645     },
120646
120647     /**
120648      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120649      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
120650      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
120651      */
120652     startEdit: function(record, columnHeader) {
120653         var me = this,
120654             value = record.get(columnHeader.dataIndex),
120655             context = me.getEditingContext(record, columnHeader),
120656             ed;
120657
120658         record = context.record;
120659         columnHeader = context.column;
120660
120661         // Complete the edit now, before getting the editor's target
120662         // cell DOM element. Completing the edit causes a view refresh.
120663         me.completeEdit();
120664
120665         context.originalValue = context.value = value;
120666         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120667             return false;
120668         }
120669         
120670         // See if the field is editable for the requested record
120671         if (columnHeader && !columnHeader.getEditor(record)) {
120672             return false;
120673         }
120674         
120675         ed = me.getEditor(record, columnHeader);
120676         if (ed) {
120677             me.context = context;
120678             me.setActiveEditor(ed);
120679             me.setActiveRecord(record);
120680             me.setActiveColumn(columnHeader);
120681
120682             // Defer, so we have some time between view scroll to sync up the editor
120683             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
120684         } else {
120685             // BrowserBug: WebKit & IE refuse to focus the element, rather
120686             // it will focus it and then immediately focus the body. This
120687             // temporary hack works for Webkit and IE6. IE7 and 8 are still
120688             // broken
120689             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
120690         }
120691     },
120692
120693     completeEdit: function() {
120694         var activeEd = this.getActiveEditor();
120695         if (activeEd) {
120696             activeEd.completeEdit();
120697         }
120698     },
120699
120700     // internal getters/setters
120701     setActiveEditor: function(ed) {
120702         this.activeEditor = ed;
120703     },
120704
120705     getActiveEditor: function() {
120706         return this.activeEditor;
120707     },
120708
120709     setActiveColumn: function(column) {
120710         this.activeColumn = column;
120711     },
120712
120713     getActiveColumn: function() {
120714         return this.activeColumn;
120715     },
120716
120717     setActiveRecord: function(record) {
120718         this.activeRecord = record;
120719     },
120720
120721     getActiveRecord: function() {
120722         return this.activeRecord;
120723     },
120724
120725     getEditor: function(record, column) {
120726         var me = this,
120727             editors = me.editors,
120728             editorId = column.getItemId(),
120729             editor = editors.getByKey(editorId);
120730
120731         if (editor) {
120732             return editor;
120733         } else {
120734             editor = column.getEditor(record);
120735             if (!editor) {
120736                 return false;
120737             }
120738
120739             // Allow them to specify a CellEditor in the Column
120740             if (!(editor instanceof Ext.grid.CellEditor)) {
120741                 editor = Ext.create('Ext.grid.CellEditor', {
120742                     editorId: editorId,
120743                     field: editor
120744                 });
120745             }
120746             editor.parentEl = me.grid.getEditorParent();
120747             // editor.parentEl should be set here.
120748             editor.on({
120749                 scope: me,
120750                 specialkey: me.onSpecialKey,
120751                 complete: me.onEditComplete,
120752                 canceledit: me.cancelEdit
120753             });
120754             editors.add(editor);
120755             return editor;
120756         }
120757     },
120758     
120759     // inherit docs
120760     setColumnField: function(column, field) {
120761         var ed = this.editors.getByKey(column.getItemId());
120762         Ext.destroy(ed, column.field);
120763         this.editors.removeAtKey(column.getItemId());
120764         this.callParent(arguments);
120765     },
120766
120767     /**
120768      * Gets the cell (td) for a particular record and column.
120769      * @param {Ext.data.Model} record
120770      * @param {Ext.grid.column.Column} column
120771      * @private
120772      */
120773     getCell: function(record, column) {
120774         return this.grid.getView().getCell(record, column);
120775     },
120776
120777     onSpecialKey: function(ed, field, e) {
120778         var grid = this.grid,
120779             sm;
120780         if (e.getKey() === e.TAB) {
120781             e.stopEvent();
120782             sm = grid.getSelectionModel();
120783             if (sm.onEditorTab) {
120784                 sm.onEditorTab(this, e);
120785             }
120786         }
120787     },
120788
120789     onEditComplete : function(ed, value, startValue) {
120790         var me = this,
120791             grid = me.grid,
120792             sm = grid.getSelectionModel(),
120793             activeColumn = me.getActiveColumn(),
120794             dataIndex;
120795
120796         if (activeColumn) {
120797             dataIndex = activeColumn.dataIndex;
120798
120799             me.setActiveEditor(null);
120800             me.setActiveColumn(null);
120801             me.setActiveRecord(null);
120802             delete sm.wasEditing;
120803     
120804             if (!me.validateEdit()) {
120805                 return;
120806             }
120807             // Only update the record if the new value is different than the
120808             // startValue, when the view refreshes its el will gain focus
120809             if (value !== startValue) {
120810                 me.context.record.set(dataIndex, value);
120811             // Restore focus back to the view's element.
120812             } else {
120813                 grid.getView().getEl(activeColumn).focus();
120814             }
120815             me.context.value = value;
120816             me.fireEvent('edit', me, me.context);
120817         }
120818     },
120819
120820     /**
120821      * Cancels any active editing.
120822      */
120823     cancelEdit: function() {
120824         var me = this,
120825             activeEd = me.getActiveEditor(),
120826             viewEl = me.grid.getView().getEl(me.getActiveColumn());
120827
120828         me.setActiveEditor(null);
120829         me.setActiveColumn(null);
120830         me.setActiveRecord(null);
120831         if (activeEd) {
120832             activeEd.cancelEdit();
120833             viewEl.focus();
120834         }
120835     },
120836
120837     /**
120838      * Starts editing by position (row/column)
120839      * @param {Object} position A position with keys of row and column.
120840      */
120841     startEditByPosition: function(position) {
120842         var me = this,
120843             grid = me.grid,
120844             sm = grid.getSelectionModel(),
120845             editRecord = grid.store.getAt(position.row),
120846             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
120847
120848         if (sm.selectByPosition) {
120849             sm.selectByPosition(position);
120850         }
120851         me.startEdit(editRecord, editColumnHeader);
120852     }
120853 });
120854 /**
120855  * This plugin provides drag and/or drop functionality for a GridView.
120856  *
120857  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
120858  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
120859  * methods with the following properties:
120860  *
120861  * - `copy` : Boolean
120862  *
120863  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
120864  *   the control key was pressed when the drag operation was begun.
120865  *
120866  * - `view` : GridView
120867  *
120868  *   The source GridView from which the drag originated.
120869  *
120870  * - `ddel` : HtmlElement
120871  *
120872  *   The drag proxy element which moves with the mouse
120873  *
120874  * - `item` : HtmlElement
120875  *
120876  *   The GridView node upon which the mousedown event was registered.
120877  *
120878  * - `records` : Array
120879  *
120880  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120881  *
120882  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
120883  * members of the same ddGroup which processes such data objects.
120884  *
120885  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
120886  * beforedrop}` and `{@link #drop drop}`
120887  *
120888  *     @example
120889  *     Ext.create('Ext.data.Store', {
120890  *         storeId:'simpsonsStore',
120891  *         fields:['name'],
120892  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
120893  *         proxy: {
120894  *             type: 'memory',
120895  *             reader: 'array'
120896  *         }
120897  *     });
120898  *
120899  *     Ext.create('Ext.grid.Panel', {
120900  *         store: 'simpsonsStore',
120901  *         columns: [
120902  *             {header: 'Name',  dataIndex: 'name', flex: true}
120903  *         ],
120904  *         viewConfig: {
120905  *             plugins: {
120906  *                 ptype: 'gridviewdragdrop',
120907  *                 dragText: 'Drag and drop to reorganize'
120908  *             }
120909  *         },
120910  *         height: 200,
120911  *         width: 400,
120912  *         renderTo: Ext.getBody()
120913  *     });
120914  */
120915 Ext.define('Ext.grid.plugin.DragDrop', {
120916     extend: 'Ext.AbstractPlugin',
120917     alias: 'plugin.gridviewdragdrop',
120918
120919     uses: [
120920         'Ext.view.DragZone',
120921         'Ext.grid.ViewDropZone'
120922     ],
120923
120924     /**
120925      * @event beforedrop
120926      * **This event is fired through the GridView. Add listeners to the GridView object**
120927      *
120928      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
120929      *
120930      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120931      *
120932      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
120933      * back to the point from which the drag began.
120934      *
120935      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
120936      * was valid, and that the repair operation should not take place.
120937      *
120938      * Any other return value continues with the data transfer operation.
120939      *
120940      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120941      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120942      *
120943      * - copy : Boolean
120944      *
120945      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120946      *   the control key was pressed when the drag operation was begun
120947      *
120948      * - view : GridView
120949      *
120950      *   The source GridView from which the drag originated.
120951      *
120952      * - ddel : HtmlElement
120953      *
120954      *   The drag proxy element which moves with the mouse
120955      *
120956      * - item : HtmlElement
120957      *
120958      *   The GridView node upon which the mousedown event was registered.
120959      *
120960      * - records : Array
120961      *
120962      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120963      *
120964      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
120965      *
120966      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
120967      * of the node.
120968      *
120969      * @param {Function} dropFunction
120970      *
120971      * A function to call to complete the data transfer operation and either move or copy Model instances from the
120972      * source View's Store to the destination View's Store.
120973      *
120974      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
120975      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
120976      *
120977      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
120978      */
120979
120980     /**
120981      * @event drop
120982      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
120983      * has been completed and the data has been moved or copied.
120984      *
120985      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120986      *
120987      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120988      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120989      *
120990      * - copy : Boolean
120991      *
120992      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120993      *   the control key was pressed when the drag operation was begun
120994      *
120995      * - view : GridView
120996      *
120997      *   The source GridView from which the drag originated.
120998      *
120999      * - ddel : HtmlElement
121000      *
121001      *   The drag proxy element which moves with the mouse
121002      *
121003      * - item : HtmlElement
121004      *
121005      *   The GridView node upon which the mousedown event was registered.
121006      *
121007      * - records : Array
121008      *
121009      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121010      *
121011      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
121012      *
121013      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
121014      * of the node.
121015      */
121016
121017     dragText : '{0} selected row{1}',
121018
121019     /**
121020      * @cfg {String} ddGroup
121021      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
121022      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
121023      */
121024     ddGroup : "GridDD",
121025
121026     /**
121027      * @cfg {String} dragGroup
121028      * The ddGroup to which the DragZone will belong.
121029      *
121030      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
121031      * Drag/DropZones which are members of the same ddGroup.
121032      */
121033
121034     /**
121035      * @cfg {String} dropGroup
121036      * The ddGroup to which the DropZone will belong.
121037      *
121038      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
121039      * Drag/DropZones which are members of the same ddGroup.
121040      */
121041
121042     /**
121043      * @cfg {Boolean} enableDrop
121044      * False to disallow the View from accepting drop gestures.
121045      */
121046     enableDrop: true,
121047
121048     /**
121049      * @cfg {Boolean} enableDrag
121050      * False to disallow dragging items from the View.
121051      */
121052     enableDrag: true,
121053
121054     init : function(view) {
121055         view.on('render', this.onViewRender, this, {single: true});
121056     },
121057
121058     /**
121059      * @private
121060      * AbstractComponent calls destroy on all its plugins at destroy time.
121061      */
121062     destroy: function() {
121063         Ext.destroy(this.dragZone, this.dropZone);
121064     },
121065
121066     enable: function() {
121067         var me = this;
121068         if (me.dragZone) {
121069             me.dragZone.unlock();
121070         }
121071         if (me.dropZone) {
121072             me.dropZone.unlock();
121073         }
121074         me.callParent();
121075     },
121076
121077     disable: function() {
121078         var me = this;
121079         if (me.dragZone) {
121080             me.dragZone.lock();
121081         }
121082         if (me.dropZone) {
121083             me.dropZone.lock();
121084         }
121085         me.callParent();
121086     },
121087
121088     onViewRender : function(view) {
121089         var me = this;
121090
121091         if (me.enableDrag) {
121092             me.dragZone = Ext.create('Ext.view.DragZone', {
121093                 view: view,
121094                 ddGroup: me.dragGroup || me.ddGroup,
121095                 dragText: me.dragText
121096             });
121097         }
121098
121099         if (me.enableDrop) {
121100             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
121101                 view: view,
121102                 ddGroup: me.dropGroup || me.ddGroup
121103             });
121104         }
121105     }
121106 });
121107 /**
121108  * @class Ext.grid.plugin.HeaderReorderer
121109  * @extends Ext.util.Observable
121110  * @private
121111  */
121112 Ext.define('Ext.grid.plugin.HeaderReorderer', {
121113     extend: 'Ext.util.Observable',
121114     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
121115     alias: 'plugin.gridheaderreorderer',
121116
121117     init: function(headerCt) {
121118         this.headerCt = headerCt;
121119         headerCt.on('render', this.onHeaderCtRender, this);
121120     },
121121
121122     /**
121123      * @private
121124      * AbstractComponent calls destroy on all its plugins at destroy time.
121125      */
121126     destroy: function() {
121127         Ext.destroy(this.dragZone, this.dropZone);
121128     },
121129
121130     onHeaderCtRender: function() {
121131         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
121132         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
121133         if (this.disabled) {
121134             this.dragZone.disable();
121135         }
121136     },
121137     
121138     enable: function() {
121139         this.disabled = false;
121140         if (this.dragZone) {
121141             this.dragZone.enable();
121142         }
121143     },
121144     
121145     disable: function() {
121146         this.disabled = true;
121147         if (this.dragZone) {
121148             this.dragZone.disable();
121149         }
121150     }
121151 });
121152 /**
121153  * @class Ext.grid.plugin.HeaderResizer
121154  * @extends Ext.util.Observable
121155  *
121156  * Plugin to add header resizing functionality to a HeaderContainer.
121157  * Always resizing header to the left of the splitter you are resizing.
121158  */
121159 Ext.define('Ext.grid.plugin.HeaderResizer', {
121160     extend: 'Ext.util.Observable',
121161     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
121162     alias: 'plugin.gridheaderresizer',
121163
121164     disabled: false,
121165
121166     /**
121167      * @cfg {Boolean} dynamic
121168      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
121169      */
121170     configs: {
121171         dynamic: true
121172     },
121173
121174     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
121175
121176     minColWidth: 40,
121177     maxColWidth: 1000,
121178     wResizeCursor: 'col-resize',
121179     eResizeCursor: 'col-resize',
121180     // not using w and e resize bc we are only ever resizing one
121181     // column
121182     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
121183     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
121184
121185     init: function(headerCt) {
121186         this.headerCt = headerCt;
121187         headerCt.on('render', this.afterHeaderRender, this, {single: true});
121188     },
121189
121190     /**
121191      * @private
121192      * AbstractComponent calls destroy on all its plugins at destroy time.
121193      */
121194     destroy: function() {
121195         if (this.tracker) {
121196             this.tracker.destroy();
121197         }
121198     },
121199
121200     afterHeaderRender: function() {
121201         var headerCt = this.headerCt,
121202             el = headerCt.el;
121203
121204         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
121205
121206         this.tracker = Ext.create('Ext.dd.DragTracker', {
121207             disabled: this.disabled,
121208             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
121209             onStart: Ext.Function.bind(this.onStart, this),
121210             onDrag: Ext.Function.bind(this.onDrag, this),
121211             onEnd: Ext.Function.bind(this.onEnd, this),
121212             tolerance: 3,
121213             autoStart: 300,
121214             el: el
121215         });
121216     },
121217
121218     // As we mouse over individual headers, change the cursor to indicate
121219     // that resizing is available, and cache the resize target header for use
121220     // if/when they mousedown.
121221     onHeaderCtMouseMove: function(e, t) {
121222         if (this.headerCt.dragging) {
121223             if (this.activeHd) {
121224                 this.activeHd.el.dom.style.cursor = '';
121225                 delete this.activeHd;
121226             }
121227         } else {
121228             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
121229                 overHeader, resizeHeader;
121230
121231             if (headerEl){
121232                 overHeader = Ext.getCmp(headerEl.id);
121233
121234                 // On left edge, go back to the previous non-hidden header.
121235                 if (overHeader.isOnLeftEdge(e)) {
121236                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
121237
121238                 }
121239                 // Else, if on the right edge, we're resizing the column we are over
121240                 else if (overHeader.isOnRightEdge(e)) {
121241                     resizeHeader = overHeader;
121242                 }
121243                 // Between the edges: we are not resizing
121244                 else {
121245                     resizeHeader = null;
121246                 }
121247
121248                 // We *are* resizing
121249                 if (resizeHeader) {
121250                     // If we're attempting to resize a group header, that cannot be resized,
121251                     // so find its last visible leaf header; Group headers are sized
121252                     // by the size of their child headers.
121253                     if (resizeHeader.isGroupHeader) {
121254                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
121255                     }
121256
121257                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
121258                     // check whether the resizablwe property is set to false.
121259                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
121260                         this.activeHd = resizeHeader;
121261                         overHeader.el.dom.style.cursor = this.eResizeCursor;
121262                     }
121263                 // reset
121264                 } else {
121265                     overHeader.el.dom.style.cursor = '';
121266                     delete this.activeHd;
121267                 }
121268             }
121269         }
121270     },
121271
121272     // only start when there is an activeHd
121273     onBeforeStart : function(e){
121274         var t = e.getTarget();
121275         // cache the activeHd because it will be cleared.
121276         this.dragHd = this.activeHd;
121277
121278         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
121279             //this.headerCt.dragging = true;
121280             this.tracker.constrainTo = this.getConstrainRegion();
121281             return true;
121282         } else {
121283             this.headerCt.dragging = false;
121284             return false;
121285         }
121286     },
121287
121288     // get the region to constrain to, takes into account max and min col widths
121289     getConstrainRegion: function() {
121290         var dragHdEl = this.dragHd.el,
121291             region   = Ext.util.Region.getRegion(dragHdEl);
121292
121293         return region.adjust(
121294             0,
121295             this.maxColWidth - dragHdEl.getWidth(),
121296             0,
121297             this.minColWidth
121298         );
121299     },
121300
121301     // initialize the left and right hand side markers around
121302     // the header that we are resizing
121303     onStart: function(e){
121304         var me       = this,
121305             dragHd   = me.dragHd,
121306             dragHdEl = dragHd.el,
121307             width    = dragHdEl.getWidth(),
121308             headerCt = me.headerCt,
121309             t        = e.getTarget();
121310
121311         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
121312             headerCt.dragging = true;
121313         }
121314
121315         me.origWidth = width;
121316
121317         // setup marker proxies
121318         if (!me.dynamic) {
121319             var xy           = dragHdEl.getXY(),
121320                 gridSection  = headerCt.up('[scrollerOwner]'),
121321                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
121322                 firstSection = dragHct.up(),
121323                 lhsMarker    = gridSection.getLhsMarker(),
121324                 rhsMarker    = gridSection.getRhsMarker(),
121325                 el           = rhsMarker.parent(),
121326                 offsetLeft   = el.getLeft(true),
121327                 offsetTop    = el.getTop(true),
121328                 topLeft      = el.translatePoints(xy),
121329                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
121330                 top = topLeft.top - offsetTop;
121331
121332             lhsMarker.setTop(top);
121333             rhsMarker.setTop(top);
121334             lhsMarker.setHeight(markerHeight);
121335             rhsMarker.setHeight(markerHeight);
121336             lhsMarker.setLeft(topLeft.left - offsetLeft);
121337             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
121338         }
121339     },
121340
121341     // synchronize the rhsMarker with the mouse movement
121342     onDrag: function(e){
121343         if (!this.dynamic) {
121344             var xy          = this.tracker.getXY('point'),
121345                 gridSection = this.headerCt.up('[scrollerOwner]'),
121346                 rhsMarker   = gridSection.getRhsMarker(),
121347                 el          = rhsMarker.parent(),
121348                 topLeft     = el.translatePoints(xy),
121349                 offsetLeft  = el.getLeft(true);
121350
121351             rhsMarker.setLeft(topLeft.left - offsetLeft);
121352         // Resize as user interacts
121353         } else {
121354             this.doResize();
121355         }
121356     },
121357
121358     onEnd: function(e){
121359         this.headerCt.dragging = false;
121360         if (this.dragHd) {
121361             if (!this.dynamic) {
121362                 var dragHd      = this.dragHd,
121363                     gridSection = this.headerCt.up('[scrollerOwner]'),
121364                     lhsMarker   = gridSection.getLhsMarker(),
121365                     rhsMarker   = gridSection.getRhsMarker(),
121366                     currWidth   = dragHd.getWidth(),
121367                     offset      = this.tracker.getOffset('point'),
121368                     offscreen   = -9999;
121369
121370                 // hide markers
121371                 lhsMarker.setLeft(offscreen);
121372                 rhsMarker.setLeft(offscreen);
121373             }
121374             this.doResize();
121375         }
121376     },
121377
121378     doResize: function() {
121379         if (this.dragHd) {
121380             var dragHd = this.dragHd,
121381                 nextHd,
121382                 offset = this.tracker.getOffset('point');
121383
121384             // resize the dragHd
121385             if (dragHd.flex) {
121386                 delete dragHd.flex;
121387             }
121388
121389             this.headerCt.suspendLayout = true;
121390             dragHd.setWidth(this.origWidth + offset[0], false);
121391
121392             // In the case of forceFit, change the following Header width.
121393             // Then apply the two width changes by laying out the owning HeaderContainer
121394             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
121395             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
121396             if (this.headerCt.forceFit) {
121397                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
121398                 if (nextHd) {
121399                     delete nextHd.flex;
121400                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
121401                 }
121402             }
121403             this.headerCt.suspendLayout = false;
121404             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
121405         }
121406     },
121407
121408     disable: function() {
121409         this.disabled = true;
121410         if (this.tracker) {
121411             this.tracker.disable();
121412         }
121413     },
121414
121415     enable: function() {
121416         this.disabled = false;
121417         if (this.tracker) {
121418             this.tracker.enable();
121419         }
121420     }
121421 });
121422 /**
121423  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
121424  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
121425  * for editing. There is a button to save or cancel all changes for the edit.
121426  *
121427  * The field that will be used for the editor is defined at the
121428  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
121429  * If an editor is not specified for a particular column then that column won't be editable and the value of
121430  * the column will be displayed.
121431  *
121432  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
121433  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
121434  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
121435  *
121436  *     @example
121437  *     Ext.create('Ext.data.Store', {
121438  *         storeId:'simpsonsStore',
121439  *         fields:['name', 'email', 'phone'],
121440  *         data: [
121441  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
121442  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
121443  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
121444  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
121445  *         ]
121446  *     });
121447  *
121448  *     Ext.create('Ext.grid.Panel', {
121449  *         title: 'Simpsons',
121450  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
121451  *         columns: [
121452  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
121453  *             {header: 'Email', dataIndex: 'email', flex:1,
121454  *                 editor: {
121455  *                     xtype: 'textfield',
121456  *                     allowBlank: false
121457  *                 }
121458  *             },
121459  *             {header: 'Phone', dataIndex: 'phone'}
121460  *         ],
121461  *         selType: 'rowmodel',
121462  *         plugins: [
121463  *             Ext.create('Ext.grid.plugin.RowEditing', {
121464  *                 clicksToEdit: 1
121465  *             })
121466  *         ],
121467  *         height: 200,
121468  *         width: 400,
121469  *         renderTo: Ext.getBody()
121470  *     });
121471  */
121472 Ext.define('Ext.grid.plugin.RowEditing', {
121473     extend: 'Ext.grid.plugin.Editing',
121474     alias: 'plugin.rowediting',
121475
121476     requires: [
121477         'Ext.grid.RowEditor'
121478     ],
121479
121480     editStyle: 'row',
121481
121482     /**
121483      * @cfg {Boolean} autoCancel
121484      * True to automatically cancel any pending changes when the row editor begins editing a new row.
121485      * False to force the user to explicitly cancel the pending changes. Defaults to true.
121486      */
121487     autoCancel: true,
121488
121489     /**
121490      * @cfg {Number} clicksToMoveEditor
121491      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
121492      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
121493      */
121494
121495     /**
121496      * @cfg {Boolean} errorSummary
121497      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
121498      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
121499      */
121500     errorSummary: true,
121501
121502     /**
121503      * @event beforeedit
121504      * Fires before row editing is triggered.
121505      *
121506      * @param {Ext.grid.plugin.Editing} editor
121507      * @param {Object} e An edit event with the following properties:
121508      *
121509      * - grid - The grid this editor is on
121510      * - view - The grid view
121511      * - store - The grid store
121512      * - record - The record being edited
121513      * - row - The grid table row
121514      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121515      * - rowIdx - The row index that is being edited
121516      * - colIdx - The column index that initiated the edit
121517      * - cancel - Set this to true to cancel the edit or return false from your handler.
121518      */
121519     
121520     /**
121521      * @event canceledit
121522      * Fires when the user has started editing a row but then cancelled the edit
121523      * @param {Object} grid The grid
121524      */
121525     
121526     /**
121527      * @event edit
121528      * Fires after a row is edited. Usage example:
121529      *
121530      *     grid.on('edit', function(editor, e) {
121531      *         // commit the changes right after editing finished
121532      *         e.record.commit();
121533      *     };
121534      *
121535      * @param {Ext.grid.plugin.Editing} editor
121536      * @param {Object} e An edit event with the following properties:
121537      *
121538      * - grid - The grid this editor is on
121539      * - view - The grid view
121540      * - store - The grid store
121541      * - record - The record being edited
121542      * - row - The grid table row
121543      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121544      * - rowIdx - The row index that is being edited
121545      * - colIdx - The column index that initiated the edit
121546      */
121547     /**
121548      * @event validateedit
121549      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
121550      * edit event object has the following properties
121551      *
121552      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
121553      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
121554      * and then setting the field's new value in the Record directly:
121555      *
121556      *     grid.on('validateedit', function(editor, e) {
121557      *       var myTargetRow = 6;
121558      *
121559      *       if (e.rowIdx == myTargetRow) {
121560      *         e.cancel = true;
121561      *         e.record.data[e.field] = e.value;
121562      *       }
121563      *     });
121564      *
121565      * @param {Ext.grid.plugin.Editing} editor
121566      * @param {Object} e An edit event with the following properties:
121567      *
121568      * - grid - The grid this editor is on
121569      * - view - The grid view
121570      * - store - The grid store
121571      * - record - The record being edited
121572      * - row - The grid table row
121573      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121574      * - rowIdx - The row index that is being edited
121575      * - colIdx - The column index that initiated the edit
121576      * - cancel - Set this to true to cancel the edit or return false from your handler.
121577      */
121578
121579     constructor: function() {
121580         var me = this;
121581         me.callParent(arguments);
121582
121583         if (!me.clicksToMoveEditor) {
121584             me.clicksToMoveEditor = me.clicksToEdit;
121585         }
121586
121587         me.autoCancel = !!me.autoCancel;
121588     },
121589
121590     /**
121591      * @private
121592      * AbstractComponent calls destroy on all its plugins at destroy time.
121593      */
121594     destroy: function() {
121595         var me = this;
121596         Ext.destroy(me.editor);
121597         me.callParent(arguments);
121598     },
121599
121600     /**
121601      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121602      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
121603      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
121604      */
121605     startEdit: function(record, columnHeader) {
121606         var me = this,
121607             editor = me.getEditor();
121608
121609         if (me.callParent(arguments) === false) {
121610             return false;
121611         }
121612
121613         // Fire off our editor
121614         if (editor.beforeEdit() !== false) {
121615             editor.startEdit(me.context.record, me.context.column);
121616         }
121617     },
121618
121619     // private
121620     cancelEdit: function() {
121621         var me = this;
121622
121623         if (me.editing) {
121624             me.getEditor().cancelEdit();
121625             me.callParent(arguments);
121626             
121627             me.fireEvent('canceledit', me.context);
121628         }
121629     },
121630
121631     // private
121632     completeEdit: function() {
121633         var me = this;
121634
121635         if (me.editing && me.validateEdit()) {
121636             me.editing = false;
121637             me.fireEvent('edit', me.context);
121638         }
121639     },
121640
121641     // private
121642     validateEdit: function() {
121643         var me             = this,
121644             editor         = me.editor,
121645             context        = me.context,
121646             record         = context.record,
121647             newValues      = {},
121648             originalValues = {},
121649             name;
121650
121651         editor.items.each(function(item) {
121652             name = item.name;
121653
121654             newValues[name]      = item.getValue();
121655             originalValues[name] = record.get(name);
121656         });
121657
121658         Ext.apply(context, {
121659             newValues      : newValues,
121660             originalValues : originalValues
121661         });
121662
121663         return me.callParent(arguments) && me.getEditor().completeEdit();
121664     },
121665
121666     // private
121667     getEditor: function() {
121668         var me = this;
121669
121670         if (!me.editor) {
121671             me.editor = me.initEditor();
121672         }
121673         return me.editor;
121674     },
121675
121676     // private
121677     initEditor: function() {
121678         var me = this,
121679             grid = me.grid,
121680             view = me.view,
121681             headerCt = grid.headerCt;
121682
121683         return Ext.create('Ext.grid.RowEditor', {
121684             autoCancel: me.autoCancel,
121685             errorSummary: me.errorSummary,
121686             fields: headerCt.getGridColumns(),
121687             hidden: true,
121688
121689             // keep a reference..
121690             editingPlugin: me,
121691             renderTo: view.el
121692         });
121693     },
121694
121695     // private
121696     initEditTriggers: function() {
121697         var me = this,
121698             grid = me.grid,
121699             view = me.view,
121700             headerCt = grid.headerCt,
121701             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
121702
121703         me.callParent(arguments);
121704
121705         if (me.clicksToMoveEditor !== me.clicksToEdit) {
121706             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
121707         }
121708
121709         view.on('render', function() {
121710             // Column events
121711             me.mon(headerCt, {
121712                 add: me.onColumnAdd,
121713                 remove: me.onColumnRemove,
121714                 columnresize: me.onColumnResize,
121715                 columnhide: me.onColumnHide,
121716                 columnshow: me.onColumnShow,
121717                 columnmove: me.onColumnMove,
121718                 scope: me
121719             });
121720         }, me, { single: true });
121721     },
121722
121723     startEditByClick: function() {
121724         var me = this;
121725         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
121726             me.callParent(arguments);
121727         }
121728     },
121729
121730     moveEditorByClick: function() {
121731         var me = this;
121732         if (me.editing) {
121733             me.superclass.startEditByClick.apply(me, arguments);
121734         }
121735     },
121736
121737     // private
121738     onColumnAdd: function(ct, column) {
121739         if (column.isHeader) {
121740             var me = this,
121741                 editor;
121742
121743             me.initFieldAccessors(column);
121744             editor = me.getEditor();
121745
121746             if (editor && editor.onColumnAdd) {
121747                 editor.onColumnAdd(column);
121748             }
121749         }
121750     },
121751
121752     // private
121753     onColumnRemove: function(ct, column) {
121754         if (column.isHeader) {
121755             var me = this,
121756                 editor = me.getEditor();
121757
121758             if (editor && editor.onColumnRemove) {
121759                 editor.onColumnRemove(column);
121760             }
121761             me.removeFieldAccessors(column);
121762         }
121763     },
121764
121765     // private
121766     onColumnResize: function(ct, column, width) {
121767         if (column.isHeader) {
121768             var me = this,
121769                 editor = me.getEditor();
121770
121771             if (editor && editor.onColumnResize) {
121772                 editor.onColumnResize(column, width);
121773             }
121774         }
121775     },
121776
121777     // private
121778     onColumnHide: function(ct, column) {
121779         // no isHeader check here since its already a columnhide event.
121780         var me = this,
121781             editor = me.getEditor();
121782
121783         if (editor && editor.onColumnHide) {
121784             editor.onColumnHide(column);
121785         }
121786     },
121787
121788     // private
121789     onColumnShow: function(ct, column) {
121790         // no isHeader check here since its already a columnshow event.
121791         var me = this,
121792             editor = me.getEditor();
121793
121794         if (editor && editor.onColumnShow) {
121795             editor.onColumnShow(column);
121796         }
121797     },
121798
121799     // private
121800     onColumnMove: function(ct, column, fromIdx, toIdx) {
121801         // no isHeader check here since its already a columnmove event.
121802         var me = this,
121803             editor = me.getEditor();
121804
121805         if (editor && editor.onColumnMove) {
121806             editor.onColumnMove(column, fromIdx, toIdx);
121807         }
121808     },
121809
121810     // private
121811     setColumnField: function(column, field) {
121812         var me = this;
121813         me.callParent(arguments);
121814         me.getEditor().setField(column.field, column);
121815     }
121816 });
121817
121818 /**
121819  * @class Ext.grid.property.Grid
121820  * @extends Ext.grid.Panel
121821  *
121822  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
121823  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
121824  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
121825  *
121826  *     @example
121827  *     Ext.create('Ext.grid.property.Grid', {
121828  *         title: 'Properties Grid',
121829  *         width: 300,
121830  *         renderTo: Ext.getBody(),
121831  *         source: {
121832  *             "(name)": "My Object",
121833  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
121834  *             "Available": false,
121835  *             "Version": .01,
121836  *             "Description": "A test object"
121837  *         }
121838  *     });
121839  */
121840 Ext.define('Ext.grid.property.Grid', {
121841
121842     extend: 'Ext.grid.Panel',
121843
121844     alias: 'widget.propertygrid',
121845
121846     alternateClassName: 'Ext.grid.PropertyGrid',
121847
121848     uses: [
121849        'Ext.grid.plugin.CellEditing',
121850        'Ext.grid.property.Store',
121851        'Ext.grid.property.HeaderContainer',
121852        'Ext.XTemplate',
121853        'Ext.grid.CellEditor',
121854        'Ext.form.field.Date',
121855        'Ext.form.field.Text',
121856        'Ext.form.field.Number'
121857     ],
121858
121859    /**
121860     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
121861     * If specified, the display name will be shown in the name column instead of the property name.
121862     */
121863
121864     /**
121865     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121866     */
121867
121868     /**
121869     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
121870     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
121871     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121872     * associated with a custom input control by specifying a custom editor.  The name of the editor
121873     * type should correspond with the name of the property that will use the editor.  Example usage:
121874     * <pre><code>
121875 var grid = new Ext.grid.property.Grid({
121876
121877     // Custom editors for certain property names
121878     customEditors: {
121879         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
121880     },
121881
121882     // Displayed name for property names in the source
121883     propertyNames: {
121884         evtStart: 'Start Time'
121885     },
121886
121887     // Data object containing properties to edit
121888     source: {
121889         evtStart: '10:00 AM'
121890     }
121891 });
121892 </code></pre>
121893     */
121894
121895     /**
121896     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121897     */
121898
121899     /**
121900     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
121901     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
121902     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121903     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
121904     * that it will render.  Example usage:
121905     * <pre><code>
121906 var grid = Ext.create('Ext.grid.property.Grid', {
121907     customRenderers: {
121908         Available: function(v){
121909             if (v) {
121910                 return '<span style="color: green;">Yes</span>';
121911             } else {
121912                 return '<span style="color: red;">No</span>';
121913             }
121914         }
121915     },
121916     source: {
121917         Available: true
121918     }
121919 });
121920 </code></pre>
121921     */
121922
121923     /**
121924      * @cfg {String} valueField
121925      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
121926      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121927      */
121928     valueField: 'value',
121929
121930     /**
121931      * @cfg {String} nameField
121932      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
121933      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121934      */
121935     nameField: 'name',
121936
121937     /**
121938      * @cfg {Number} nameColumnWidth
121939      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
121940      */
121941
121942     // private config overrides
121943     enableColumnMove: false,
121944     columnLines: true,
121945     stripeRows: false,
121946     trackMouseOver: false,
121947     clicksToEdit: 1,
121948     enableHdMenu: false,
121949
121950     // private
121951     initComponent : function(){
121952         var me = this;
121953
121954         me.addCls(Ext.baseCSSPrefix + 'property-grid');
121955         me.plugins = me.plugins || [];
121956
121957         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
121958         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
121959             clicksToEdit: me.clicksToEdit,
121960
121961             // Inject a startEdit which always edits the value column
121962             startEdit: function(record, column) {
121963                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
121964                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
121965             }
121966         }));
121967
121968         me.selModel = {
121969             selType: 'cellmodel',
121970             onCellSelect: function(position) {
121971                 if (position.column != 1) {
121972                     position.column = 1;
121973                 }
121974                 return this.self.prototype.onCellSelect.call(this, position);
121975             }
121976         };
121977         me.customRenderers = me.customRenderers || {};
121978         me.customEditors = me.customEditors || {};
121979
121980         // Create a property.Store from the source object unless configured with a store
121981         if (!me.store) {
121982             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
121983         }
121984
121985         me.store.sort('name', 'ASC');
121986         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
121987
121988         me.addEvents(
121989             /**
121990              * @event beforepropertychange
121991              * Fires before a property value changes.  Handlers can return false to cancel the property change
121992              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
121993              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
121994              * as the {@link #source} config property).
121995              * @param {String} recordId The record's id in the data store
121996              * @param {Object} value The current edited property value
121997              * @param {Object} oldValue The original property value prior to editing
121998              */
121999             'beforepropertychange',
122000             /**
122001              * @event propertychange
122002              * Fires after a property value has changed.
122003              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
122004              * as the {@link #source} config property).
122005              * @param {String} recordId The record's id in the data store
122006              * @param {Object} value The current edited property value
122007              * @param {Object} oldValue The original property value prior to editing
122008              */
122009             'propertychange'
122010         );
122011         me.callParent();
122012
122013         // Inject a custom implementation of walkCells which only goes up or down
122014         me.getView().walkCells = this.walkCells;
122015
122016         // Set up our default editor set for the 4 atomic data types
122017         me.editors = {
122018             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
122019             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
122020             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
122021             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
122022                 editable: false,
122023                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
122024             })})
122025         };
122026
122027         // Track changes to the data so we can fire our events.
122028         me.store.on('update', me.onUpdate, me);
122029     },
122030
122031     // private
122032     onUpdate : function(store, record, operation) {
122033         var me = this,
122034             v, oldValue;
122035
122036         if (operation == Ext.data.Model.EDIT) {
122037             v = record.get(me.valueField);
122038             oldValue = record.modified.value;
122039             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
122040                 if (me.source) {
122041                     me.source[record.getId()] = v;
122042                 }
122043                 record.commit();
122044                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
122045             } else {
122046                 record.reject();
122047             }
122048         }
122049     },
122050
122051     // Custom implementation of walkCells which only goes up and down.
122052     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
122053         if (direction == 'left') {
122054             direction = 'up';
122055         } else if (direction == 'right') {
122056             direction = 'down';
122057         }
122058         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
122059         if (!pos.column) {
122060             pos.column = 1;
122061         }
122062         return pos;
122063     },
122064
122065     // private
122066     // returns the correct editor type for the property type, or a custom one keyed by the property name
122067     getCellEditor : function(record, column) {
122068         var me = this,
122069             propName = record.get(me.nameField),
122070             val = record.get(me.valueField),
122071             editor = me.customEditors[propName];
122072
122073         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
122074         // If it's not even a Field, just a config object, instantiate it before wrapping it.
122075         if (editor) {
122076             if (!(editor instanceof Ext.grid.CellEditor)) {
122077                 if (!(editor instanceof Ext.form.field.Base)) {
122078                     editor = Ext.ComponentManager.create(editor, 'textfield');
122079                 }
122080                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
122081             }
122082         } else if (Ext.isDate(val)) {
122083             editor = me.editors.date;
122084         } else if (Ext.isNumber(val)) {
122085             editor = me.editors.number;
122086         } else if (Ext.isBoolean(val)) {
122087             editor = me.editors['boolean'];
122088         } else {
122089             editor = me.editors.string;
122090         }
122091
122092         // Give the editor a unique ID because the CellEditing plugin caches them
122093         editor.editorId = propName;
122094         return editor;
122095     },
122096
122097     beforeDestroy: function() {
122098         var me = this;
122099         me.callParent();
122100         me.destroyEditors(me.editors);
122101         me.destroyEditors(me.customEditors);
122102         delete me.source;
122103     },
122104
122105     destroyEditors: function (editors) {
122106         for (var ed in editors) {
122107             if (editors.hasOwnProperty(ed)) {
122108                 Ext.destroy(editors[ed]);
122109             }
122110         }
122111     },
122112
122113     /**
122114      * Sets the source data object containing the property data.  The data object can contain one or more name/value
122115      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
122116      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
122117      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
122118      * existing data.  See also the {@link #source} config value.  Example usage:
122119      * <pre><code>
122120 grid.setSource({
122121     "(name)": "My Object",
122122     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
122123     "Available": false,  // boolean type
122124     "Version": .01,      // decimal type
122125     "Description": "A test object"
122126 });
122127 </code></pre>
122128      * @param {Object} source The data object
122129      */
122130     setSource: function(source) {
122131         this.source = source;
122132         this.propStore.setSource(source);
122133     },
122134
122135     /**
122136      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
122137      * format of the data object.
122138      * @return {Object} The data object
122139      */
122140     getSource: function() {
122141         return this.propStore.getSource();
122142     },
122143
122144     /**
122145      * Sets the value of a property.
122146      * @param {String} prop The name of the property to set
122147      * @param {Object} value The value to test
122148      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
122149      */
122150     setProperty: function(prop, value, create) {
122151         this.propStore.setValue(prop, value, create);
122152     },
122153
122154     /**
122155      * Removes a property from the grid.
122156      * @param {String} prop The name of the property to remove
122157      */
122158     removeProperty: function(prop) {
122159         this.propStore.remove(prop);
122160     }
122161
122162     /**
122163      * @cfg store
122164      * @hide
122165      */
122166     /**
122167      * @cfg colModel
122168      * @hide
122169      */
122170     /**
122171      * @cfg cm
122172      * @hide
122173      */
122174     /**
122175      * @cfg columns
122176      * @hide
122177      */
122178 });
122179 /**
122180  * @class Ext.grid.property.HeaderContainer
122181  * @extends Ext.grid.header.Container
122182  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
122183  */
122184 Ext.define('Ext.grid.property.HeaderContainer', {
122185
122186     extend: 'Ext.grid.header.Container',
122187
122188     alternateClassName: 'Ext.grid.PropertyColumnModel',
122189     
122190     nameWidth: 115,
122191
122192     // private - strings used for locale support
122193     nameText : 'Name',
122194     valueText : 'Value',
122195     dateFormat : 'm/j/Y',
122196     trueText: 'true',
122197     falseText: 'false',
122198
122199     // private
122200     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
122201
122202     /**
122203      * Creates new HeaderContainer.
122204      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
122205      * @param {Object} source The source data config object
122206      */
122207     constructor : function(grid, store) {
122208         var me = this;
122209         
122210         me.grid = grid;
122211         me.store = store;
122212         me.callParent([{
122213             items: [{
122214                 header: me.nameText,
122215                 width: grid.nameColumnWidth || me.nameWidth,
122216                 sortable: true,
122217                 dataIndex: grid.nameField,
122218                 renderer: Ext.Function.bind(me.renderProp, me),
122219                 itemId: grid.nameField,
122220                 menuDisabled :true,
122221                 tdCls: me.nameColumnCls
122222             }, {
122223                 header: me.valueText,
122224                 renderer: Ext.Function.bind(me.renderCell, me),
122225                 getEditor: Ext.Function.bind(me.getCellEditor, me),
122226                 flex: 1,
122227                 fixed: true,
122228                 dataIndex: grid.valueField,
122229                 itemId: grid.valueField,
122230                 menuDisabled: true
122231             }]
122232         }]);
122233     },
122234     
122235     getCellEditor: function(record){
122236         return this.grid.getCellEditor(record, this);
122237     },
122238
122239     // private
122240     // Render a property name cell
122241     renderProp : function(v) {
122242         return this.getPropertyName(v);
122243     },
122244
122245     // private
122246     // Render a property value cell
122247     renderCell : function(val, meta, rec) {
122248         var me = this,
122249             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
122250             result = val;
122251
122252         if (renderer) {
122253             return renderer.apply(me, arguments);
122254         }
122255         if (Ext.isDate(val)) {
122256             result = me.renderDate(val);
122257         } else if (Ext.isBoolean(val)) {
122258             result = me.renderBool(val);
122259         }
122260         return Ext.util.Format.htmlEncode(result);
122261     },
122262
122263     // private
122264     renderDate : Ext.util.Format.date,
122265
122266     // private
122267     renderBool : function(bVal) {
122268         return this[bVal ? 'trueText' : 'falseText'];
122269     },
122270
122271     // private
122272     // Renders custom property names instead of raw names if defined in the Grid
122273     getPropertyName : function(name) {
122274         var pn = this.grid.propertyNames;
122275         return pn && pn[name] ? pn[name] : name;
122276     }
122277 });
122278 /**
122279  * @class Ext.grid.property.Property
122280  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
122281  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
122282  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
122283  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
122284  * can also be created explicitly as shown below.  Example usage:
122285  * <pre><code>
122286 var rec = new Ext.grid.property.Property({
122287     name: 'birthday',
122288     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
122289 });
122290 // Add record to an already populated grid
122291 grid.store.addSorted(rec);
122292 </code></pre>
122293  * @constructor
122294  * @param {Object} config A data object in the format:<pre><code>
122295 {
122296     name: [name],
122297     value: [value]
122298 }</code></pre>
122299  * The specified value's type
122300  * will be read automatically by the grid to determine the type of editor to use when displaying it.
122301  */
122302 Ext.define('Ext.grid.property.Property', {
122303     extend: 'Ext.data.Model',
122304
122305     alternateClassName: 'Ext.PropGridProperty',
122306
122307     fields: [{
122308         name: 'name',
122309         type: 'string'
122310     }, {
122311         name: 'value'
122312     }],
122313     idProperty: 'name'
122314 });
122315 /**
122316  * @class Ext.grid.property.Store
122317  * @extends Ext.data.Store
122318  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
122319  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
122320  * used by the {@link Ext.data.Store} base class.
122321  */
122322 Ext.define('Ext.grid.property.Store', {
122323
122324     extend: 'Ext.data.Store',
122325
122326     alternateClassName: 'Ext.grid.PropertyStore',
122327
122328     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
122329
122330     /**
122331      * Creates new property store.
122332      * @param {Ext.grid.Panel} grid The grid this store will be bound to
122333      * @param {Object} source The source data config object
122334      */
122335     constructor : function(grid, source){
122336         var me = this;
122337         
122338         me.grid = grid;
122339         me.source = source;
122340         me.callParent([{
122341             data: source,
122342             model: Ext.grid.property.Property,
122343             proxy: me.getProxy()
122344         }]);
122345     },
122346
122347     // Return a singleton, customized Proxy object which configures itself with a custom Reader
122348     getProxy: function() {
122349         if (!this.proxy) {
122350             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
122351                 model: Ext.grid.property.Property,
122352                 reader: this.getReader()
122353             });
122354         }
122355         return this.proxy;
122356     },
122357
122358     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
122359     getReader: function() {
122360         if (!this.reader) {
122361             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
122362                 model: Ext.grid.property.Property,
122363
122364                 buildExtractors: Ext.emptyFn,
122365
122366                 read: function(dataObject) {
122367                     return this.readRecords(dataObject);
122368                 },
122369
122370                 readRecords: function(dataObject) {
122371                     var val,
122372                         propName,
122373                         result = {
122374                             records: [],
122375                             success: true
122376                         };
122377
122378                     for (propName in dataObject) {
122379                         if (dataObject.hasOwnProperty(propName)) {
122380                             val = dataObject[propName];
122381                             if (this.isEditableValue(val)) {
122382                                 result.records.push(new Ext.grid.property.Property({
122383                                     name: propName,
122384                                     value: val
122385                                 }, propName));
122386                             }
122387                         }
122388                     }
122389                     result.total = result.count = result.records.length;
122390                     return Ext.create('Ext.data.ResultSet', result);
122391                 },
122392
122393                 // private
122394                 isEditableValue: function(val){
122395                     return Ext.isPrimitive(val) || Ext.isDate(val);
122396                 }
122397             });
122398         }
122399         return this.reader;
122400     },
122401
122402     // protected - should only be called by the grid.  Use grid.setSource instead.
122403     setSource : function(dataObject) {
122404         var me = this;
122405
122406         me.source = dataObject;
122407         me.suspendEvents();
122408         me.removeAll();
122409         me.proxy.data = dataObject;
122410         me.load();
122411         me.resumeEvents();
122412         me.fireEvent('datachanged', me);
122413     },
122414
122415     // private
122416     getProperty : function(row) {
122417        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
122418     },
122419
122420     // private
122421     setValue : function(prop, value, create){
122422         var me = this,
122423             rec = me.getRec(prop);
122424             
122425         if (rec) {
122426             rec.set('value', value);
122427             me.source[prop] = value;
122428         } else if (create) {
122429             // only create if specified.
122430             me.source[prop] = value;
122431             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
122432             me.add(rec);
122433         }
122434     },
122435
122436     // private
122437     remove : function(prop) {
122438         var rec = this.getRec(prop);
122439         if (rec) {
122440             this.callParent([rec]);
122441             delete this.source[prop];
122442         }
122443     },
122444
122445     // private
122446     getRec : function(prop) {
122447         return this.getById(prop);
122448     },
122449
122450     // protected - should only be called by the grid.  Use grid.getSource instead.
122451     getSource : function() {
122452         return this.source;
122453     }
122454 });
122455 /**
122456  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
122457  * Component size.
122458  * @class Ext.layout.component.Body
122459  * @extends Ext.layout.component.Component
122460  * @private
122461  */
122462
122463 Ext.define('Ext.layout.component.Body', {
122464
122465     /* Begin Definitions */
122466
122467     alias: ['layout.body'],
122468
122469     extend: 'Ext.layout.component.Component',
122470
122471     uses: ['Ext.layout.container.Container'],
122472
122473     /* End Definitions */
122474
122475     type: 'body',
122476     
122477     onLayout: function(width, height) {
122478         var me = this,
122479             owner = me.owner;
122480
122481         // Size the Component's encapsulating element according to the dimensions
122482         me.setTargetSize(width, height);
122483
122484         // Size the Component's body element according to the content box of the encapsulating element
122485         me.setBodySize.apply(me, arguments);
122486
122487         // We need to bind to the owner whenever we do not have a user set height or width.
122488         if (owner && owner.layout && owner.layout.isLayout) {
122489             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
122490                 owner.layout.bindToOwnerCtComponent = true;
122491             }
122492             else {
122493                 owner.layout.bindToOwnerCtComponent = false;
122494             }
122495         }
122496         
122497         me.callParent(arguments);
122498     },
122499
122500     /**
122501      * @private
122502      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
122503      */
122504     setBodySize: function(width, height) {
122505         var me = this,
122506             owner = me.owner,
122507             frameSize = owner.frameSize,
122508             isNumber = Ext.isNumber;
122509
122510         if (isNumber(width)) {
122511             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
122512         }
122513         if (isNumber(height)) {
122514             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
122515         }
122516
122517         me.setElementSize(owner.body, width, height);
122518     }
122519 });
122520 /**
122521  * Component layout for Ext.form.FieldSet components
122522  * @class Ext.layout.component.FieldSet
122523  * @extends Ext.layout.component.Body
122524  * @private
122525  */
122526 Ext.define('Ext.layout.component.FieldSet', {
122527     extend: 'Ext.layout.component.Body',
122528     alias: ['layout.fieldset'],
122529
122530     type: 'fieldset',
122531
122532     doContainerLayout: function() {
122533         // Prevent layout/rendering of children if the fieldset is collapsed
122534         if (!this.owner.collapsed) {
122535             this.callParent();
122536         }
122537     }
122538 });
122539 /**
122540  * Component layout for tabs
122541  * @class Ext.layout.component.Tab
122542  * @extends Ext.layout.component.Button
122543  * @private
122544  */
122545 Ext.define('Ext.layout.component.Tab', {
122546
122547     alias: ['layout.tab'],
122548
122549     extend: 'Ext.layout.component.Button',
122550
122551     //type: 'button',
122552
122553     beforeLayout: function() {
122554         var me = this, dirty = me.lastClosable !== me.owner.closable;
122555
122556         if (dirty) {
122557             delete me.adjWidth;
122558         }
122559
122560         return this.callParent(arguments) || dirty;
122561     },
122562
122563     onLayout: function () {
122564         var me = this;
122565
122566         me.callParent(arguments);
122567
122568         me.lastClosable = me.owner.closable;
122569     }
122570 });
122571 /**
122572  * @private
122573  * @class Ext.layout.component.field.File
122574  * @extends Ext.layout.component.field.Field
122575  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
122576  * the file picker trigger button.
122577  * @private
122578  */
122579
122580 Ext.define('Ext.layout.component.field.File', {
122581     alias: ['layout.filefield'],
122582     extend: 'Ext.layout.component.field.Field',
122583
122584     type: 'filefield',
122585
122586     sizeBodyContents: function(width, height) {
122587         var me = this,
122588             owner = me.owner;
122589
122590         if (!owner.buttonOnly) {
122591             // Decrease the field's width by the width of the button and the configured buttonMargin.
122592             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
122593             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
122594         }
122595     }
122596 });
122597 /**
122598  * @class Ext.layout.component.field.Slider
122599  * @extends Ext.layout.component.field.Field
122600  * @private
122601  */
122602
122603 Ext.define('Ext.layout.component.field.Slider', {
122604
122605     /* Begin Definitions */
122606
122607     alias: ['layout.sliderfield'],
122608
122609     extend: 'Ext.layout.component.field.Field',
122610
122611     /* End Definitions */
122612
122613     type: 'sliderfield',
122614
122615     sizeBodyContents: function(width, height) {
122616         var owner = this.owner,
122617             thumbs = owner.thumbs,
122618             length = thumbs.length,
122619             inputEl = owner.inputEl,
122620             innerEl = owner.innerEl,
122621             endEl = owner.endEl,
122622             i = 0;
122623
122624         /*
122625          * If we happen to be animating during a resize, the position of the thumb will likely be off
122626          * when the animation stops. As such, just stop any animations before syncing the thumbs.
122627          */
122628         for(; i < length; ++i) {
122629             thumbs[i].el.stopAnimation();
122630         }
122631         
122632         if (owner.vertical) {
122633             inputEl.setHeight(height);
122634             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
122635         }
122636         else {
122637             inputEl.setWidth(width);
122638             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
122639         }
122640         owner.syncThumbs();
122641     }
122642 });
122643
122644 /**
122645  * @class Ext.layout.container.Absolute
122646  * @extends Ext.layout.container.Anchor
122647  *
122648  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
122649  * ability for x/y positioning using the standard x and y component config options.
122650  *
122651  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
122652  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
122653  *
122654  *     @example
122655  *     Ext.create('Ext.form.Panel', {
122656  *         title: 'Absolute Layout',
122657  *         width: 300,
122658  *         height: 275,
122659  *         layout:'absolute',
122660  *         layoutConfig: {
122661  *             // layout-specific configs go here
122662  *             //itemCls: 'x-abs-layout-item',
122663  *         },
122664  *         url:'save-form.php',
122665  *         defaultType: 'textfield',
122666  *         items: [{
122667  *             x: 10,
122668  *             y: 10,
122669  *             xtype:'label',
122670  *             text: 'Send To:'
122671  *         },{
122672  *             x: 80,
122673  *             y: 10,
122674  *             name: 'to',
122675  *             anchor:'90%'  // anchor width by percentage
122676  *         },{
122677  *             x: 10,
122678  *             y: 40,
122679  *             xtype:'label',
122680  *             text: 'Subject:'
122681  *         },{
122682  *             x: 80,
122683  *             y: 40,
122684  *             name: 'subject',
122685  *             anchor: '90%'  // anchor width by percentage
122686  *         },{
122687  *             x:0,
122688  *             y: 80,
122689  *             xtype: 'textareafield',
122690  *             name: 'msg',
122691  *             anchor: '100% 100%'  // anchor width and height
122692  *         }],
122693  *         renderTo: Ext.getBody()
122694  *     });
122695  */
122696 Ext.define('Ext.layout.container.Absolute', {
122697
122698     /* Begin Definitions */
122699
122700     alias: 'layout.absolute',
122701     extend: 'Ext.layout.container.Anchor',
122702     alternateClassName: 'Ext.layout.AbsoluteLayout',
122703
122704     /* End Definitions */
122705
122706     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
122707
122708     type: 'absolute',
122709
122710     onLayout: function() {
122711         var me = this,
122712             target = me.getTarget(),
122713             targetIsBody = target.dom === document.body;
122714
122715         // Do not set position: relative; when the absolute layout target is the body
122716         if (!targetIsBody) {
122717             target.position();
122718         }
122719         me.paddingLeft = target.getPadding('l');
122720         me.paddingTop = target.getPadding('t');
122721         me.callParent(arguments);
122722     },
122723
122724     // private
122725     adjustWidthAnchor: function(value, comp) {
122726         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
122727         return value ? value - comp.getPosition(true)[0] : value;
122728     },
122729
122730     // private
122731     adjustHeightAnchor: function(value, comp) {
122732         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
122733         return value ? value - comp.getPosition(true)[1] : value;
122734     }
122735 });
122736 /**
122737  * @class Ext.layout.container.Accordion
122738  * @extends Ext.layout.container.VBox
122739  *
122740  * This is a layout that manages multiple Panels in an expandable accordion style such that only
122741  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
122742  *
122743  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
122744  *
122745  *     @example
122746  *     Ext.create('Ext.panel.Panel', {
122747  *         title: 'Accordion Layout',
122748  *         width: 300,
122749  *         height: 300,
122750  *         layout:'accordion',
122751  *         defaults: {
122752  *             // applied to each contained panel
122753  *             bodyStyle: 'padding:15px'
122754  *         },
122755  *         layoutConfig: {
122756  *             // layout-specific configs go here
122757  *             titleCollapse: false,
122758  *             animate: true,
122759  *             activeOnTop: true
122760  *         },
122761  *         items: [{
122762  *             title: 'Panel 1',
122763  *             html: 'Panel content!'
122764  *         },{
122765  *             title: 'Panel 2',
122766  *             html: 'Panel content!'
122767  *         },{
122768  *             title: 'Panel 3',
122769  *             html: 'Panel content!'
122770  *         }],
122771  *         renderTo: Ext.getBody()
122772  *     });
122773  */
122774 Ext.define('Ext.layout.container.Accordion', {
122775     extend: 'Ext.layout.container.VBox',
122776     alias: ['layout.accordion'],
122777     alternateClassName: 'Ext.layout.AccordionLayout',
122778
122779     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
122780
122781     align: 'stretch',
122782
122783     /**
122784      * @cfg {Boolean} fill
122785      * True to adjust the active item's height to fill the available space in the container, false to use the
122786      * item's current height, or auto height if not explicitly set.
122787      */
122788     fill : true,
122789
122790     /**
122791      * @cfg {Boolean} autoWidth
122792      * Child Panels have their width actively managed to fit within the accordion's width.
122793      * @deprecated This config is ignored in ExtJS 4
122794      */
122795     autoWidth : true,
122796
122797     /**
122798      * @cfg {Boolean} titleCollapse
122799      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
122800      * expand/collapse only when the toggle tool button is clicked.  When set to false,
122801      * {@link #hideCollapseTool} should be false also.
122802      */
122803     titleCollapse : true,
122804
122805     /**
122806      * @cfg {Boolean} hideCollapseTool
122807      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
122808      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
122809      */
122810     hideCollapseTool : false,
122811
122812     /**
122813      * @cfg {Boolean} collapseFirst
122814      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
122815      * in the contained Panels' title bars, false to render it last.
122816      */
122817     collapseFirst : false,
122818
122819     /**
122820      * @cfg {Boolean} animate
122821      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
122822      * close directly with no animation. Note: The layout performs animated collapsing
122823      * and expanding, <i>not</i> the child Panels.
122824      */
122825     animate : true,
122826     /**
122827      * @cfg {Boolean} activeOnTop
122828      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
122829      *
122830      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
122831      * false to keep the panels in the rendered order.
122832      */
122833     activeOnTop : false,
122834     /**
122835      * @cfg {Boolean} multi
122836      * Set to <code>true</code> to enable multiple accordion items to be open at once.
122837      */
122838     multi: false,
122839
122840     constructor: function() {
122841         var me = this;
122842
122843         me.callParent(arguments);
122844
122845         // animate flag must be false during initial render phase so we don't get animations.
122846         me.initialAnimate = me.animate;
122847         me.animate = false;
122848
122849         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
122850         if (me.fill === false) {
122851             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
122852         }
122853     },
122854
122855     // Cannot lay out a fitting accordion before we have been allocated a height.
122856     // So during render phase, layout will not be performed.
122857     beforeLayout: function() {
122858         var me = this;
122859
122860         me.callParent(arguments);
122861         if (me.fill) {
122862             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
122863                 return false;
122864             }
122865         } else {
122866             me.owner.componentLayout.monitorChildren = false;
122867             me.autoSize = true;
122868             me.owner.setAutoScroll(true);
122869         }
122870     },
122871
122872     renderItems : function(items, target) {
122873         var me = this,
122874             ln = items.length,
122875             i = 0,
122876             comp,
122877             targetSize = me.getLayoutTargetSize(),
122878             renderedPanels = [];
122879
122880         for (; i < ln; i++) {
122881             comp = items[i];
122882             if (!comp.rendered) {
122883                 renderedPanels.push(comp);
122884
122885                 // Set up initial properties for Panels in an accordion.
122886                 if (me.collapseFirst) {
122887                     comp.collapseFirst = me.collapseFirst;
122888                 }
122889                 if (me.hideCollapseTool) {
122890                     comp.hideCollapseTool = me.hideCollapseTool;
122891                     comp.titleCollapse = true;
122892                 }
122893                 else if (me.titleCollapse) {
122894                     comp.titleCollapse = me.titleCollapse;
122895                 }
122896
122897                 delete comp.hideHeader;
122898                 comp.collapsible = true;
122899                 comp.title = comp.title || '&#160;';
122900
122901                 // Set initial sizes
122902                 comp.width = targetSize.width;
122903                 if (me.fill) {
122904                     delete comp.height;
122905                     delete comp.flex;
122906
122907                     // If there is an expanded item, all others must be rendered collapsed.
122908                     if (me.expandedItem !== undefined) {
122909                         comp.collapsed = true;
122910                     }
122911                     // Otherwise expand the first item with collapsed explicitly configured as false
122912                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
122913                         comp.flex = 1;
122914                         me.expandedItem = i;
122915                     } else {
122916                         comp.collapsed = true;
122917                     }
122918                     // If we are fitting, then intercept expand/collapse requests.
122919                     me.owner.mon(comp, {
122920                         show: me.onComponentShow,
122921                         beforeexpand: me.onComponentExpand,
122922                         beforecollapse: me.onComponentCollapse,
122923                         scope: me
122924                     });
122925                 } else {
122926                     delete comp.flex;
122927                     comp.animCollapse = me.initialAnimate;
122928                     comp.autoHeight = true;
122929                     comp.autoScroll = false;
122930                 }
122931                 comp.border = comp.collapsed;
122932             }
122933         }
122934
122935         // If no collapsed:false Panels found, make the first one expanded.
122936         if (ln && me.expandedItem === undefined) {
122937             me.expandedItem = 0;
122938             comp = items[0];
122939             comp.collapsed = comp.border = false;
122940             if (me.fill) {
122941                 comp.flex = 1;
122942             }
122943         }
122944
122945         // Render all Panels.
122946         me.callParent(arguments);
122947
122948         // Postprocess rendered Panels.
122949         ln = renderedPanels.length;
122950         for (i = 0; i < ln; i++) {
122951             comp = renderedPanels[i];
122952
122953             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
122954             delete comp.width;
122955
122956             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
122957             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
122958         }
122959     },
122960
122961     onLayout: function() {
122962         var me = this;
122963
122964
122965         if (me.fill) {
122966             me.callParent(arguments);
122967         } else {
122968             var targetSize = me.getLayoutTargetSize(),
122969                 items = me.getVisibleItems(),
122970                 len = items.length,
122971                 i = 0, comp;
122972
122973             for (; i < len; i++) {
122974                 comp = items[i];
122975                 if (comp.collapsed) {
122976                     items[i].setWidth(targetSize.width);
122977                 } else {
122978                     items[i].setSize(null, null);
122979                 }
122980             }
122981         }
122982         me.updatePanelClasses();
122983
122984         return me;
122985     },
122986
122987     updatePanelClasses: function() {
122988         var children = this.getLayoutItems(),
122989             ln = children.length,
122990             siblingCollapsed = true,
122991             i, child;
122992
122993         for (i = 0; i < ln; i++) {
122994             child = children[i];
122995
122996             // Fix for EXTJSIV-3724. Windows only.
122997             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
122998             if (Ext.isWindows) {
122999                 child.el.dom.scrollTop = 0;
123000             }
123001
123002             if (siblingCollapsed) {
123003                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123004             }
123005             else {
123006                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123007             }
123008
123009             if (i + 1 == ln && child.collapsed) {
123010                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123011             }
123012             else {
123013                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123014             }
123015             siblingCollapsed = child.collapsed;
123016         }
123017     },
123018     
123019     animCallback: function(){
123020         Ext.Array.forEach(this.toCollapse, function(comp){
123021             comp.fireEvent('collapse', comp);
123022         });
123023         
123024         Ext.Array.forEach(this.toExpand, function(comp){
123025             comp.fireEvent('expand', comp);
123026         });    
123027     },
123028     
123029     setupEvents: function(){
123030         this.toCollapse = [];
123031         this.toExpand = [];    
123032     },
123033
123034     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
123035     // their headers.
123036     // The expanded Component receives the only flex value, and so gets all remaining space.
123037     onComponentExpand: function(toExpand) {
123038         var me = this,
123039             it = me.owner.items.items,
123040             len = it.length,
123041             i = 0,
123042             comp;
123043
123044         me.setupEvents();
123045         for (; i < len; i++) {
123046             comp = it[i];
123047             if (comp === toExpand && comp.collapsed) {
123048                 me.setExpanded(comp);
123049             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
123050                 me.setCollapsed(comp);
123051             }
123052         }
123053
123054         me.animate = me.initialAnimate;
123055         if (me.activeOnTop) {
123056             // insert will trigger a layout
123057             me.owner.insert(0, toExpand); 
123058         } else {
123059             me.layout();
123060         }
123061         me.animate = false;
123062         return false;
123063     },
123064
123065     onComponentCollapse: function(comp) {
123066         var me = this,
123067             toExpand = comp.next() || comp.prev(),
123068             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
123069
123070         me.setupEvents();
123071         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
123072         // then ask the box layout to collapse it to its header.
123073         if (me.multi) {
123074             me.setCollapsed(comp);
123075
123076             // If the collapsing Panel is the only expanded one, expand the following Component.
123077             // All this is handling fill: true, so there must be at least one expanded,
123078             if (expanded.length === 1 && expanded[0] === comp) {
123079                 me.setExpanded(toExpand);
123080             }
123081
123082             me.animate = me.initialAnimate;
123083             me.layout();
123084             me.animate = false;
123085         }
123086         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
123087         else if (toExpand) {
123088             me.onComponentExpand(toExpand);
123089         }
123090         return false;
123091     },
123092
123093     onComponentShow: function(comp) {
123094         // Showing a Component means that you want to see it, so expand it.
123095         this.onComponentExpand(comp);
123096     },
123097
123098     setCollapsed: function(comp) {
123099         var otherDocks = comp.getDockedItems(),
123100             dockItem,
123101             len = otherDocks.length,
123102             i = 0;
123103
123104         // Hide all docked items except the header
123105         comp.hiddenDocked = [];
123106         for (; i < len; i++) {
123107             dockItem = otherDocks[i];
123108             if ((dockItem !== comp.header) && !dockItem.hidden) {
123109                 dockItem.hidden = true;
123110                 comp.hiddenDocked.push(dockItem);
123111             }
123112         }
123113         comp.addCls(comp.collapsedCls);
123114         comp.header.addCls(comp.collapsedHeaderCls);
123115         comp.height = comp.header.getHeight();
123116         comp.el.setHeight(comp.height);
123117         comp.collapsed = true;
123118         delete comp.flex;
123119         if (this.initialAnimate) {
123120             this.toCollapse.push(comp);
123121         } else {
123122             comp.fireEvent('collapse', comp);
123123         }
123124         if (comp.collapseTool) {
123125             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
123126         }
123127     },
123128
123129     setExpanded: function(comp) {
123130         var otherDocks = comp.hiddenDocked,
123131             len = otherDocks ? otherDocks.length : 0,
123132             i = 0;
123133
123134         // Show temporarily hidden docked items
123135         for (; i < len; i++) {
123136             otherDocks[i].show();
123137         }
123138
123139         // If it was an initial native collapse which hides the body
123140         if (!comp.body.isVisible()) {
123141             comp.body.show();
123142         }
123143         delete comp.collapsed;
123144         delete comp.height;
123145         delete comp.componentLayout.lastComponentSize;
123146         comp.suspendLayout = false;
123147         comp.flex = 1;
123148         comp.removeCls(comp.collapsedCls);
123149         comp.header.removeCls(comp.collapsedHeaderCls);
123150          if (this.initialAnimate) {
123151             this.toExpand.push(comp);
123152         } else {
123153             comp.fireEvent('expand', comp);
123154         }
123155         if (comp.collapseTool) {
123156             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
123157         }
123158         comp.setAutoScroll(comp.initialConfig.autoScroll);
123159     }
123160 });
123161 /**
123162  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
123163  * layout to resize both immediate siblings.
123164  *
123165  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
123166  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
123167  * by the layout.
123168  *
123169  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
123170  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
123171  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
123172  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
123173  */
123174 Ext.define('Ext.resizer.Splitter', {
123175     extend: 'Ext.Component',
123176     requires: ['Ext.XTemplate'],
123177     uses: ['Ext.resizer.SplitterTracker'],
123178     alias: 'widget.splitter',
123179
123180     renderTpl: [
123181         '<tpl if="collapsible===true">',
123182             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
123183                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
123184         '</tpl>'
123185     ],
123186
123187     baseCls: Ext.baseCSSPrefix + 'splitter',
123188     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
123189
123190     /**
123191      * @cfg {Boolean} collapsible
123192      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
123193      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
123194      */
123195     collapsible: false,
123196
123197     /**
123198      * @cfg {Boolean} performCollapse
123199      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
123200      * state of the {@link #collapseTarget}.</p>
123201      */
123202
123203     /**
123204      * @cfg {Boolean} collapseOnDblClick
123205      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
123206      */
123207     collapseOnDblClick: true,
123208
123209     /**
123210      * @cfg {Number} defaultSplitMin
123211      * Provides a default minimum width or height for the two components
123212      * that the splitter is between.
123213      */
123214     defaultSplitMin: 40,
123215
123216     /**
123217      * @cfg {Number} defaultSplitMax
123218      * Provides a default maximum width or height for the two components
123219      * that the splitter is between.
123220      */
123221     defaultSplitMax: 1000,
123222
123223     /**
123224      * @cfg {String} collapsedCls
123225      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
123226      */
123227
123228     width: 5,
123229     height: 5,
123230
123231     /**
123232      * @cfg {String/Ext.panel.Panel} collapseTarget
123233      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
123234      * <p>Or the immediate sibling Panel to collapse.</p>
123235      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
123236      * <p><b>Note that only Panels may be collapsed.</b></p>
123237      */
123238     collapseTarget: 'next',
123239
123240     /**
123241      * @property orientation
123242      * @type String
123243      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
123244      * when used in a vbox layout.
123245      */
123246
123247     onRender: function() {
123248         var me = this,
123249             target = me.getCollapseTarget(),
123250             collapseDir = me.getCollapseDirection();
123251
123252         Ext.applyIf(me.renderData, {
123253             collapseDir: collapseDir,
123254             collapsible: me.collapsible || target.collapsible
123255         });
123256
123257         me.addChildEls('collapseEl');
123258
123259         this.callParent(arguments);
123260
123261         // Add listeners on the mini-collapse tool unless performCollapse is set to false
123262         if (me.performCollapse !== false) {
123263             if (me.renderData.collapsible) {
123264                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
123265             }
123266             if (me.collapseOnDblClick) {
123267                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
123268             }
123269         }
123270
123271         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
123272         me.mon(target, 'collapse', me.onTargetCollapse, me);
123273         me.mon(target, 'expand', me.onTargetExpand, me);
123274
123275         me.el.addCls(me.baseCls + '-' + me.orientation);
123276         me.el.unselectable();
123277
123278         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
123279             el: me.el
123280         });
123281
123282         // Relay the most important events to our owner (could open wider later):
123283         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
123284     },
123285
123286     getCollapseDirection: function() {
123287         var me = this,
123288             idx,
123289             type = me.ownerCt.layout.type;
123290
123291         // Avoid duplication of string tests.
123292         // Create a two bit truth table of the configuration of the Splitter:
123293         // Collapse Target | orientation
123294         //        0              0             = next, horizontal
123295         //        0              1             = next, vertical
123296         //        1              0             = prev, horizontal
123297         //        1              1             = prev, vertical
123298         if (me.collapseTarget.isComponent) {
123299             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
123300         } else {
123301             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
123302         }
123303
123304         // Read the data out the truth table
123305         me.orientation = ['horizontal', 'vertical'][idx & 1];
123306         return ['bottom', 'right', 'top', 'left'][idx];
123307     },
123308
123309     getCollapseTarget: function() {
123310         var me = this;
123311
123312         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
123313     },
123314
123315     onTargetCollapse: function(target) {
123316         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
123317     },
123318
123319     onTargetExpand: function(target) {
123320         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
123321     },
123322
123323     toggleTargetCmp: function(e, t) {
123324         var cmp = this.getCollapseTarget();
123325
123326         if (cmp.isVisible()) {
123327             // restore
123328             if (cmp.collapsed) {
123329                 cmp.expand(cmp.animCollapse);
123330             // collapse
123331             } else {
123332                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
123333             }
123334         }
123335     },
123336
123337     /*
123338      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
123339      */
123340     setSize: function() {
123341         var me = this;
123342         me.callParent(arguments);
123343         if (Ext.isIE) {
123344             me.el.repaint();
123345         }
123346     }
123347 });
123348
123349 /**
123350  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
123351  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
123352  *
123353  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
123354  * config, and should generally not need to be created directly via the new keyword.
123355  *
123356  *     @example
123357  *     Ext.create('Ext.panel.Panel', {
123358  *         width: 500,
123359  *         height: 400,
123360  *         title: 'Border Layout',
123361  *         layout: 'border',
123362  *         items: [{
123363  *             title: 'South Region is resizable',
123364  *             region: 'south',     // position for region
123365  *             xtype: 'panel',
123366  *             height: 100,
123367  *             split: true,         // enable resizing
123368  *             margins: '0 5 5 5'
123369  *         },{
123370  *             // xtype: 'panel' implied by default
123371  *             title: 'West Region is collapsible',
123372  *             region:'west',
123373  *             xtype: 'panel',
123374  *             margins: '5 0 0 5',
123375  *             width: 200,
123376  *             collapsible: true,   // make collapsible
123377  *             id: 'west-region-container',
123378  *             layout: 'fit'
123379  *         },{
123380  *             title: 'Center Region',
123381  *             region: 'center',     // center region is required, no width/height specified
123382  *             xtype: 'panel',
123383  *             layout: 'fit',
123384  *             margins: '5 5 0 0'
123385  *         }],
123386  *         renderTo: Ext.getBody()
123387  *     });
123388  *
123389  * # Notes
123390  *
123391  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
123392  *   The child item in the center region will always be resized to fill the remaining space
123393  *   not used by the other regions in the layout.
123394  *
123395  * - Any child items with a region of `west` or `east` may be configured with either an initial
123396  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
123397  *   **string** (Which is simply divided by 100 and used as a flex value).
123398  *   The 'center' region has a flex value of `1`.
123399  *
123400  * - Any child items with a region of `north` or `south` may be configured with either an initial
123401  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
123402  *   **string** (Which is simply divided by 100 and used as a flex value).
123403  *   The 'center' region has a flex value of `1`.
123404  *
123405  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
123406  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
123407  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
123408  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
123409  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
123410  *   the west region:
123411  *
123412  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
123413  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
123414  *       wrc.{@link Ext.container.Container#add add}({
123415  *           title: 'Added Panel',
123416  *           html: 'Some content'
123417  *       });
123418  *
123419  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
123420  */
123421 Ext.define('Ext.layout.container.Border', {
123422
123423     alias: ['layout.border'],
123424     extend: 'Ext.layout.container.Container',
123425     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
123426     alternateClassName: 'Ext.layout.BorderLayout',
123427
123428     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
123429
123430     itemCls: Ext.baseCSSPrefix + 'border-item',
123431
123432     bindToOwnerCtContainer: true,
123433
123434     percentageRe: /(\d+)%/,
123435
123436     slideDirection: {
123437         north: 't',
123438         south: 'b',
123439         west: 'l',
123440         east: 'r'
123441     },
123442
123443     constructor: function(config) {
123444         this.initialConfig = config;
123445         this.callParent(arguments);
123446     },
123447
123448     onLayout: function() {
123449         var me = this;
123450         if (!me.borderLayoutInitialized) {
123451             me.initializeBorderLayout();
123452         }
123453
123454         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
123455         me.fixHeightConstraints();
123456         me.shadowLayout.onLayout();
123457         if (me.embeddedContainer) {
123458             me.embeddedContainer.layout.onLayout();
123459         }
123460
123461         // If the panel was originally configured with collapsed: true, it will have
123462         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
123463         if (!me.initialCollapsedComplete) {
123464             Ext.iterate(me.regions, function(name, region){
123465                 if (region.borderCollapse) {
123466                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
123467                 }
123468             });
123469             me.initialCollapsedComplete = true;
123470         }
123471     },
123472
123473     isValidParent : function(item, target, position) {
123474         if (!this.borderLayoutInitialized) {
123475             this.initializeBorderLayout();
123476         }
123477
123478         // Delegate this operation to the shadow "V" or "H" box layout.
123479         return this.shadowLayout.isValidParent(item, target, position);
123480     },
123481
123482     beforeLayout: function() {
123483         if (!this.borderLayoutInitialized) {
123484             this.initializeBorderLayout();
123485         }
123486
123487         // Delegate this operation to the shadow "V" or "H" box layout.
123488         this.shadowLayout.beforeLayout();
123489
123490         // note: don't call base because that does a renderItems again
123491     },
123492
123493     renderItems: function(items, target) {
123494     },
123495
123496     renderItem: function(item) {
123497     },
123498
123499     renderChildren: function() {
123500         if (!this.borderLayoutInitialized) {
123501             this.initializeBorderLayout();
123502         }
123503
123504         this.shadowLayout.renderChildren();
123505     },
123506
123507     /*
123508      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
123509      * We must not include child items which are floated over the layout (are primed with a slide out animation)
123510      */
123511     getVisibleItems: function() {
123512         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
123513     },
123514
123515     initializeBorderLayout: function() {
123516         var me = this,
123517             i = 0,
123518             items = me.getLayoutItems(),
123519             ln = items.length,
123520             regions = (me.regions = {}),
123521             vBoxItems = [],
123522             hBoxItems = [],
123523             horizontalFlex = 0,
123524             verticalFlex = 0,
123525             comp, percentage;
123526
123527         // Map of Splitters for each region
123528         me.splitters = {};
123529
123530         // Map of regions
123531         for (; i < ln; i++) {
123532             comp = items[i];
123533             regions[comp.region] = comp;
123534
123535             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
123536             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
123537
123538                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
123539                 comp.borderCollapse = comp.collapsed;
123540                 comp.collapsed = false;
123541
123542                 comp.on({
123543                     beforecollapse: me.onBeforeRegionCollapse,
123544                     beforeexpand: me.onBeforeRegionExpand,
123545                     destroy: me.onRegionDestroy,
123546                     scope: me
123547                 });
123548                 me.setupState(comp);
123549             }
123550         }
123551         comp = regions.center;
123552         if (!comp.flex) {
123553             comp.flex = 1;
123554         }
123555         delete comp.width;
123556         comp.maintainFlex = true;
123557
123558         // Begin the VBox and HBox item list.
123559         comp = regions.west;
123560         if (comp) {
123561             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
123562             hBoxItems.push(comp);
123563             if (comp.split) {
123564                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
123565             }
123566             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123567             if (percentage) {
123568                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123569                 delete comp.width;
123570             }
123571         }
123572         comp = regions.north;
123573         if (comp) {
123574             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
123575             vBoxItems.push(comp);
123576             if (comp.split) {
123577                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
123578             }
123579             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123580             if (percentage) {
123581                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123582                 delete comp.height;
123583             }
123584         }
123585
123586         // Decide into which Collection the center region goes.
123587         if (regions.north || regions.south) {
123588             if (regions.east || regions.west) {
123589
123590                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
123591                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
123592                     xtype: 'container',
123593                     region: 'center',
123594                     id: me.owner.id + '-embedded-center',
123595                     cls: Ext.baseCSSPrefix + 'border-item',
123596                     flex: regions.center.flex,
123597                     maintainFlex: true,
123598                     layout: {
123599                         type: 'hbox',
123600                         align: 'stretch',
123601                         getVisibleItems: me.getVisibleItems
123602                     }
123603                 }));
123604                 hBoxItems.push(regions.center);
123605             }
123606             // No east or west: the original center goes straight into the vbox
123607             else {
123608                 vBoxItems.push(regions.center);
123609             }
123610         }
123611         // If we have no north or south, then the center is part of the HBox items
123612         else {
123613             hBoxItems.push(regions.center);
123614         }
123615
123616         // Finish off the VBox and HBox item list.
123617         comp = regions.south;
123618         if (comp) {
123619             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
123620             if (comp.split) {
123621                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
123622             }
123623             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123624             if (percentage) {
123625                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123626                 delete comp.height;
123627             }
123628             vBoxItems.push(comp);
123629         }
123630         comp = regions.east;
123631         if (comp) {
123632             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
123633             if (comp.split) {
123634                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
123635             }
123636             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123637             if (percentage) {
123638                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123639                 delete comp.width;
123640             }
123641             hBoxItems.push(comp);
123642         }
123643
123644         // Create the injected "items" collections for the Containers.
123645         // If we have north or south, then the shadow Container will be a VBox.
123646         // If there are also east or west regions, its center will be a shadow HBox.
123647         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
123648         if (regions.north || regions.south) {
123649
123650             me.shadowContainer = Ext.create('Ext.container.Container', {
123651                 ownerCt: me.owner,
123652                 el: me.getTarget(),
123653                 layout: Ext.applyIf({
123654                     type: 'vbox',
123655                     align: 'stretch',
123656                     getVisibleItems: me.getVisibleItems
123657                 }, me.initialConfig)
123658             });
123659             me.createItems(me.shadowContainer, vBoxItems);
123660
123661             // Allow the Splitters to orientate themselves
123662             if (me.splitters.north) {
123663                 me.splitters.north.ownerCt = me.shadowContainer;
123664             }
123665             if (me.splitters.south) {
123666                 me.splitters.south.ownerCt = me.shadowContainer;
123667             }
123668
123669             // Inject items into the HBox Container if there is one - if there was an east or west.
123670             if (me.embeddedContainer) {
123671                 me.embeddedContainer.ownerCt = me.shadowContainer;
123672                 me.createItems(me.embeddedContainer, hBoxItems);
123673
123674                 // Allow the Splitters to orientate themselves
123675                 if (me.splitters.east) {
123676                     me.splitters.east.ownerCt = me.embeddedContainer;
123677                 }
123678                 if (me.splitters.west) {
123679                     me.splitters.west.ownerCt = me.embeddedContainer;
123680                 }
123681
123682                 // These spliiters need to be constrained by components one-level below
123683                 // the component in their vobx. We update the min/maxHeight on the helper
123684                 // (embeddedContainer) prior to starting the split/drag. This has to be
123685                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
123686                 // dynamically.
123687                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
123688                     if (splitter) {
123689                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
123690                     }
123691                 });
123692
123693                 // The east or west region wanted a percentage
123694                 if (horizontalFlex) {
123695                     regions.center.flex -= horizontalFlex;
123696                 }
123697                 // The north or south region wanted a percentage
123698                 if (verticalFlex) {
123699                     me.embeddedContainer.flex -= verticalFlex;
123700                 }
123701             } else {
123702                 // The north or south region wanted a percentage
123703                 if (verticalFlex) {
123704                     regions.center.flex -= verticalFlex;
123705                 }
123706             }
123707         }
123708         // If we have no north or south, then there's only one Container, and it's
123709         // an HBox, or, if only a center region was specified, a Fit.
123710         else {
123711             me.shadowContainer = Ext.create('Ext.container.Container', {
123712                 ownerCt: me.owner,
123713                 el: me.getTarget(),
123714                 layout: Ext.applyIf({
123715                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
123716                     align: 'stretch'
123717                 }, me.initialConfig)
123718             });
123719             me.createItems(me.shadowContainer, hBoxItems);
123720
123721             // Allow the Splitters to orientate themselves
123722             if (me.splitters.east) {
123723                 me.splitters.east.ownerCt = me.shadowContainer;
123724             }
123725             if (me.splitters.west) {
123726                 me.splitters.west.ownerCt = me.shadowContainer;
123727             }
123728
123729             // The east or west region wanted a percentage
123730             if (horizontalFlex) {
123731                 regions.center.flex -= verticalFlex;
123732             }
123733         }
123734
123735         // Create upward links from the region Components to their shadow ownerCts
123736         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
123737             items[i].shadowOwnerCt = me.shadowContainer;
123738         }
123739         if (me.embeddedContainer) {
123740             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
123741                 items[i].shadowOwnerCt = me.embeddedContainer;
123742             }
123743         }
123744
123745         // This is the layout that we delegate all operations to
123746         me.shadowLayout = me.shadowContainer.getLayout();
123747
123748         me.borderLayoutInitialized = true;
123749     },
123750
123751     setupState: function(comp){
123752         var getState = comp.getState;
123753         comp.getState = function(){
123754             // call the original getState
123755             var state = getState.call(comp) || {},
123756                 region = comp.region;
123757
123758             state.collapsed = !!comp.collapsed;
123759             if (region == 'west' || region == 'east') {
123760                 state.width = comp.getWidth();
123761             } else {
123762                 state.height = comp.getHeight();
123763             }
123764             return state;
123765         };
123766         comp.addStateEvents(['collapse', 'expand', 'resize']);
123767     },
123768
123769     /**
123770      * Create the items collection for our shadow/embedded containers
123771      * @private
123772      */
123773     createItems: function(container, items){
123774         // Have to inject an items Collection *after* construction.
123775         // The child items of the shadow layout must retain their original, user-defined ownerCt
123776         delete container.items;
123777         container.initItems();
123778         container.items.addAll(items);
123779     },
123780
123781     // Private
123782     // Create a splitter for a child of the layout.
123783     createSplitter: function(comp) {
123784         var me = this,
123785             interceptCollapse = (comp.collapseMode != 'header'),
123786             resizer;
123787
123788         resizer = Ext.create('Ext.resizer.Splitter', {
123789             hidden: !!comp.hidden,
123790             collapseTarget: comp,
123791             performCollapse: !interceptCollapse,
123792             listeners: interceptCollapse ? {
123793                 click: {
123794                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123795                     element: 'collapseEl'
123796                 }
123797             } : null
123798         });
123799
123800         // Mini collapse means that the splitter is the placeholder Component
123801         if (comp.collapseMode == 'mini') {
123802             comp.placeholder = resizer;
123803             resizer.collapsedCls = comp.collapsedCls;
123804         }
123805
123806         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
123807         comp.on({
123808             hide: me.onRegionVisibilityChange,
123809             show: me.onRegionVisibilityChange,
123810             scope: me
123811         });
123812         return resizer;
123813     },
123814
123815     // Private
123816     // Propagates the min/maxHeight values from the inner hbox items to its container.
123817     fixHeightConstraints: function () {
123818         var me = this,
123819             ct = me.embeddedContainer,
123820             maxHeight = 1e99, minHeight = -1;
123821
123822         if (!ct) {
123823             return;
123824         }
123825
123826         ct.items.each(function (item) {
123827             if (Ext.isNumber(item.maxHeight)) {
123828                 maxHeight = Math.max(maxHeight, item.maxHeight);
123829             }
123830             if (Ext.isNumber(item.minHeight)) {
123831                 minHeight = Math.max(minHeight, item.minHeight);
123832             }
123833         });
123834
123835         ct.maxHeight = maxHeight;
123836         ct.minHeight = minHeight;
123837     },
123838
123839     // Hide/show a region's associated splitter when the region is hidden/shown
123840     onRegionVisibilityChange: function(comp){
123841         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
123842         this.layout();
123843     },
123844
123845     // Called when a splitter mini-collapse tool is clicked on.
123846     // The listener is only added if this layout is controlling collapsing,
123847     // not if the component's collapseMode is 'mini' or 'header'.
123848     onSplitterCollapseClick: function(comp) {
123849         if (comp.collapsed) {
123850             this.onPlaceHolderToolClick(null, null, null, {client: comp});
123851         } else {
123852             comp.collapse();
123853         }
123854     },
123855
123856     /**
123857      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
123858      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
123859      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
123860      *
123861      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
123862      * collapsed.**
123863      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
123864      * Ext.panel.Panel#placeholder placeholder}.
123865      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
123866      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
123867      */
123868     getPlaceholder: function(comp) {
123869         var me = this,
123870             placeholder = comp.placeholder,
123871             shadowContainer = comp.shadowOwnerCt,
123872             shadowLayout = shadowContainer.layout,
123873             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
123874             horiz = (comp.region == 'north' || comp.region == 'south');
123875
123876         // No placeholder if the collapse mode is not the Border layout default
123877         if (comp.collapseMode == 'header') {
123878             return;
123879         }
123880
123881         // Provide a replacement Container with an expand tool
123882         if (!placeholder) {
123883             if (comp.collapseMode == 'mini') {
123884                 placeholder = Ext.create('Ext.resizer.Splitter', {
123885                     id: 'collapse-placeholder-' + comp.id,
123886                     collapseTarget: comp,
123887                     performCollapse: false,
123888                     listeners: {
123889                         click: {
123890                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123891                             element: 'collapseEl'
123892                         }
123893                     }
123894                 });
123895                 placeholder.addCls(placeholder.collapsedCls);
123896             } else {
123897                 placeholder = {
123898                     id: 'collapse-placeholder-' + comp.id,
123899                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
123900                     xtype: 'header',
123901                     orientation: horiz ? 'horizontal' : 'vertical',
123902                     title: comp.title,
123903                     textCls: comp.headerTextCls,
123904                     iconCls: comp.iconCls,
123905                     baseCls: comp.baseCls + '-header',
123906                     ui: comp.ui,
123907                     indicateDrag: comp.draggable,
123908                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
123909                     listeners: comp.floatable ? {
123910                         click: {
123911                             fn: function(e) {
123912                                 me.floatCollapsedPanel(e, comp);
123913                             },
123914                             element: 'el'
123915                         }
123916                     } : null
123917                 };
123918                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
123919                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
123920                     placeholder.width = 25;
123921                 }
123922                 if (!comp.hideCollapseTool) {
123923                     placeholder[horiz ? 'tools' : 'items'] = [{
123924                         xtype: 'tool',
123925                         client: comp,
123926                         type: 'expand-' + oppositeDirection,
123927                         handler: me.onPlaceHolderToolClick,
123928                         scope: me
123929                     }];
123930                 }
123931             }
123932             placeholder = me.owner.createComponent(placeholder);
123933             if (comp.isXType('panel')) {
123934                 comp.on({
123935                     titlechange: me.onRegionTitleChange,
123936                     iconchange: me.onRegionIconChange,
123937                     scope: me
123938                 });
123939             }
123940         }
123941
123942         // The collapsed Component holds a reference to its placeholder and vice versa
123943         comp.placeholder = placeholder;
123944         placeholder.comp = comp;
123945
123946         return placeholder;
123947     },
123948
123949     /**
123950      * @private
123951      * Update the placeholder title when panel title has been set or changed.
123952      */
123953     onRegionTitleChange: function(comp, newTitle) {
123954         comp.placeholder.setTitle(newTitle);
123955     },
123956
123957     /**
123958      * @private
123959      * Update the placeholder iconCls when panel iconCls has been set or changed.
123960      */
123961     onRegionIconChange: function(comp, newIconCls) {
123962         comp.placeholder.setIconCls(newIconCls);
123963     },
123964
123965     /**
123966      * @private
123967      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
123968      * when configured with a flex, calls this method on its ownerCt's layout.
123969      * @param {Ext.Component} child The child Component to calculate the box for
123970      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
123971      */
123972     calculateChildBox: function(comp) {
123973         var me = this;
123974         if (me.shadowContainer.items.contains(comp)) {
123975             return me.shadowContainer.layout.calculateChildBox(comp);
123976         }
123977         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
123978             return me.embeddedContainer.layout.calculateChildBox(comp);
123979         }
123980     },
123981
123982     /**
123983      * @private
123984      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
123985      * with a placeholder Header orientated in the appropriate dimension.
123986      * @param comp The Panel being collapsed.
123987      * @param direction
123988      * @param animate
123989      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
123990      */
123991     onBeforeRegionCollapse: function(comp, direction, animate) {
123992         if (comp.collapsedChangingLayout) {
123993             return false;
123994         }
123995         comp.collapsedChangingLayout = true;
123996         var me = this,
123997             compEl = comp.el,
123998             width,
123999             miniCollapse = comp.collapseMode == 'mini',
124000             shadowContainer = comp.shadowOwnerCt,
124001             shadowLayout = shadowContainer.layout,
124002             placeholder = comp.placeholder,
124003             sl = me.owner.suspendLayout,
124004             scsl = shadowContainer.suspendLayout,
124005             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
124006
124007         // Do not trigger a layout during transition to collapsed Component
124008         me.owner.suspendLayout = true;
124009         shadowContainer.suspendLayout = true;
124010
124011         // Prevent upward notifications from downstream layouts
124012         shadowLayout.layoutBusy = true;
124013         if (shadowContainer.componentLayout) {
124014             shadowContainer.componentLayout.layoutBusy = true;
124015         }
124016         me.shadowContainer.layout.layoutBusy = true;
124017         me.layoutBusy = true;
124018         me.owner.componentLayout.layoutBusy = true;
124019
124020         // Provide a replacement Container with an expand tool
124021         if (!placeholder) {
124022             placeholder = me.getPlaceholder(comp);
124023         }
124024
124025         // placeholder already in place; show it.
124026         if (placeholder.shadowOwnerCt === shadowContainer) {
124027             placeholder.show();
124028         }
124029         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
124030         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
124031         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
124032         else {
124033             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
124034             placeholder.shadowOwnerCt = shadowContainer;
124035             placeholder.ownerCt = me.owner;
124036         }
124037
124038         // Flag the collapsing Component as hidden and show the placeholder.
124039         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
124040         // We hide or slideOut the Component's element
124041         comp.hidden = true;
124042
124043         if (!placeholder.rendered) {
124044             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
124045
124046             // The inserted placeholder does not have the proper size, so copy the width
124047             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
124048             // without recursive layouts. This is only an issue initially. After this time,
124049             // placeholder will have the correct width/height set by the layout (which has
124050             // already happened when we get here initially).
124051             if (comp.region == 'north' || comp.region == 'south') {
124052                 placeholder.setCalculatedSize(comp.getWidth());
124053             } else {
124054                 placeholder.setCalculatedSize(undefined, comp.getHeight());
124055             }
124056         }
124057
124058         // Jobs to be done after the collapse has been done
124059         function afterCollapse() {
124060             // Reinstate automatic laying out.
124061             me.owner.suspendLayout = sl;
124062             shadowContainer.suspendLayout = scsl;
124063             delete shadowLayout.layoutBusy;
124064             if (shadowContainer.componentLayout) {
124065                 delete shadowContainer.componentLayout.layoutBusy;
124066             }
124067             delete me.shadowContainer.layout.layoutBusy;
124068             delete me.layoutBusy;
124069             delete me.owner.componentLayout.layoutBusy;
124070             delete comp.collapsedChangingLayout;
124071
124072             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
124073             comp.collapsed = true;
124074             comp.fireEvent('collapse', comp);
124075         }
124076
124077         /*
124078          * Set everything to the new positions. Note that we
124079          * only want to animate the collapse if it wasn't configured
124080          * initially with collapsed: true
124081          */
124082         if (comp.animCollapse && me.initialCollapsedComplete) {
124083             shadowLayout.layout();
124084             compEl.dom.style.zIndex = 100;
124085
124086             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124087             if (!miniCollapse) {
124088                 placeholder.el.hide();
124089             }
124090             compEl.slideOut(me.slideDirection[comp.region], {
124091                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124092                 listeners: {
124093                     afteranimate: function() {
124094                         compEl.show().setLeftTop(-10000, -10000);
124095                         compEl.dom.style.zIndex = '';
124096
124097                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124098                        if (!miniCollapse) {
124099                             placeholder.el.slideIn(me.slideDirection[comp.region], {
124100                                 easing: 'linear',
124101                                 duration: 100
124102                             });
124103                         }
124104                         afterCollapse();
124105                     }
124106                 }
124107             });
124108         } else {
124109             compEl.setLeftTop(-10000, -10000);
124110             shadowLayout.layout();
124111             afterCollapse();
124112         }
124113
124114         return false;
124115     },
124116
124117     // Hijack the expand operation to remove the placeholder and slide the region back in.
124118     onBeforeRegionExpand: function(comp, animate) {
124119         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
124120         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
124121         return false;
124122     },
124123
124124     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
124125     onPlaceHolderToolClick: function(e, target, owner, tool) {
124126         var me = this,
124127             comp = tool.client,
124128
124129             // Hide the placeholder unless it was the Component's preexisting splitter
124130             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
124131             compEl = comp.el,
124132             toCompBox,
124133             placeholder = comp.placeholder,
124134             placeholderEl = placeholder.el,
124135             shadowContainer = comp.shadowOwnerCt,
124136             shadowLayout = shadowContainer.layout,
124137             curSize,
124138             sl = me.owner.suspendLayout,
124139             scsl = shadowContainer.suspendLayout,
124140             isFloating;
124141
124142         if (comp.collapsedChangingLayout) {
124143             return false;
124144         }
124145         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
124146             return false;
124147         }
124148         comp.collapsedChangingLayout = true;
124149         // If the slide in is still going, stop it.
124150         // This will either leave the Component in its fully floated state (which is processed below)
124151         // or in its collapsed state. Either way, we expand it..
124152         if (comp.getActiveAnimation()) {
124153             comp.stopAnimation();
124154         }
124155
124156         // If the Component is fully floated when they click the placeholder Tool,
124157         // it will be primed with a slide out animation object... so delete that
124158         // and remove the mouseout listeners
124159         if (comp.slideOutAnim) {
124160             // Remove mouse leave monitors
124161             compEl.un(comp.panelMouseMon);
124162             placeholderEl.un(comp.placeholderMouseMon);
124163
124164             delete comp.slideOutAnim;
124165             delete comp.panelMouseMon;
124166             delete comp.placeholderMouseMon;
124167
124168             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
124169             isFloating = true;
124170         }
124171
124172         // Do not trigger a layout during transition to expanded Component
124173         me.owner.suspendLayout = true;
124174         shadowContainer.suspendLayout = true;
124175
124176         // Prevent upward notifications from downstream layouts
124177         shadowLayout.layoutBusy = true;
124178         if (shadowContainer.componentLayout) {
124179             shadowContainer.componentLayout.layoutBusy = true;
124180         }
124181         me.shadowContainer.layout.layoutBusy = true;
124182         me.layoutBusy = true;
124183         me.owner.componentLayout.layoutBusy = true;
124184
124185         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
124186         // Find where the shadow Box layout plans to put the expanding Component.
124187         comp.hidden = false;
124188         comp.collapsed = false;
124189         if (hidePlaceholder) {
124190             placeholder.hidden = true;
124191         }
124192         toCompBox = shadowLayout.calculateChildBox(comp);
124193
124194         // Show the collapse tool in case it was hidden by the slide-in
124195         if (comp.collapseTool) {
124196             comp.collapseTool.show();
124197         }
124198
124199         // If we're going to animate, we need to hide the component before moving it back into position
124200         if (comp.animCollapse && !isFloating) {
124201             compEl.setStyle('visibility', 'hidden');
124202         }
124203         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124204
124205         // Equalize the size of the expanding Component prior to animation
124206         // in case the layout area has changed size during the time it was collapsed.
124207         curSize = comp.getSize();
124208         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124209             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124210         }
124211
124212         // Jobs to be done after the expand has been done
124213         function afterExpand() {
124214             // Reinstate automatic laying out.
124215             me.owner.suspendLayout = sl;
124216             shadowContainer.suspendLayout = scsl;
124217             delete shadowLayout.layoutBusy;
124218             if (shadowContainer.componentLayout) {
124219                 delete shadowContainer.componentLayout.layoutBusy;
124220             }
124221             delete me.shadowContainer.layout.layoutBusy;
124222             delete me.layoutBusy;
124223             delete me.owner.componentLayout.layoutBusy;
124224             delete comp.collapsedChangingLayout;
124225
124226             // In case it was floated out and they clicked the re-expand tool
124227             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124228
124229             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
124230             comp.fireEvent('expand', comp);
124231         }
124232
124233         // Hide the placeholder
124234         if (hidePlaceholder) {
124235             placeholder.el.hide();
124236         }
124237
124238         // Slide the expanding Component to its new position.
124239         // When that is done, layout the layout.
124240         if (comp.animCollapse && !isFloating) {
124241             compEl.dom.style.zIndex = 100;
124242             compEl.slideIn(me.slideDirection[comp.region], {
124243                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124244                 listeners: {
124245                     afteranimate: function() {
124246                         compEl.dom.style.zIndex = '';
124247                         comp.hidden = false;
124248                         shadowLayout.onLayout();
124249                         afterExpand();
124250                     }
124251                 }
124252             });
124253         } else {
124254             shadowLayout.onLayout();
124255             afterExpand();
124256         }
124257     },
124258
124259     floatCollapsedPanel: function(e, comp) {
124260
124261         if (comp.floatable === false) {
124262             return;
124263         }
124264
124265         var me = this,
124266             compEl = comp.el,
124267             placeholder = comp.placeholder,
124268             placeholderEl = placeholder.el,
124269             shadowContainer = comp.shadowOwnerCt,
124270             shadowLayout = shadowContainer.layout,
124271             placeholderBox = shadowLayout.getChildBox(placeholder),
124272             scsl = shadowContainer.suspendLayout,
124273             curSize, toCompBox, compAnim;
124274
124275         // Ignore clicks on tools.
124276         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
124277             return;
124278         }
124279
124280         // It's *being* animated, ignore the click.
124281         // Possible future enhancement: Stop and *reverse* the current active Fx.
124282         if (compEl.getActiveAnimation()) {
124283             return;
124284         }
124285
124286         // If the Component is already fully floated when they click the placeholder,
124287         // it will be primed with a slide out animation object... so slide it out.
124288         if (comp.slideOutAnim) {
124289             me.slideOutFloatedComponent(comp);
124290             return;
124291         }
124292
124293         // Function to be called when the mouse leaves the floated Panel
124294         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
124295         function onMouseLeaveFloated(e) {
124296             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
124297
124298             // If mouse is not within slide Region, slide it out
124299             if (!slideRegion.contains(e.getPoint())) {
124300                 me.slideOutFloatedComponent(comp);
124301             }
124302         }
124303
124304         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
124305         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
124306
124307         // Do not trigger a layout during slide out of the Component
124308         shadowContainer.suspendLayout = true;
124309
124310         // Prevent upward notifications from downstream layouts
124311         me.layoutBusy = true;
124312         me.owner.componentLayout.layoutBusy = true;
124313
124314         // The collapse tool is hidden while slid.
124315         // It is re-shown on expand.
124316         if (comp.collapseTool) {
124317             comp.collapseTool.hide();
124318         }
124319
124320         // Set flags so that the layout will calculate the boxes for what we want
124321         comp.hidden = false;
124322         comp.collapsed = false;
124323         placeholder.hidden = true;
124324
124325         // Recalculate new arrangement of the Component being floated.
124326         toCompBox = shadowLayout.calculateChildBox(comp);
124327         placeholder.hidden = false;
124328
124329         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
124330         if (comp.region == 'north' || comp.region == 'west') {
124331             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
124332         } else {
124333             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
124334         }
124335         compEl.setStyle('visibility', 'hidden');
124336         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124337
124338         // Equalize the size of the expanding Component prior to animation
124339         // in case the layout area has changed size during the time it was collapsed.
124340         curSize = comp.getSize();
124341         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124342             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124343         }
124344
124345         // This animation slides the collapsed Component's el out to just beyond its placeholder
124346         compAnim = {
124347             listeners: {
124348                 afteranimate: function() {
124349                     shadowContainer.suspendLayout = scsl;
124350                     delete me.layoutBusy;
124351                     delete me.owner.componentLayout.layoutBusy;
124352
124353                     // Prime the Component with an Anim config object to slide it back out
124354                     compAnim.listeners = {
124355                         afterAnimate: function() {
124356                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
124357
124358                             // Reinstate the correct, current state after slide out animation finishes
124359                             comp.hidden = true;
124360                             comp.collapsed = true;
124361                             delete comp.slideOutAnim;
124362                             delete comp.panelMouseMon;
124363                             delete comp.placeholderMouseMon;
124364                         }
124365                     };
124366                     comp.slideOutAnim = compAnim;
124367                 }
124368             },
124369             duration: 500
124370         };
124371
124372         // Give the element the correct class which places it at a high z-index
124373         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124374
124375         // Begin the slide in
124376         compEl.slideIn(me.slideDirection[comp.region], compAnim);
124377
124378         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
124379         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
124380
124381     },
124382
124383     slideOutFloatedComponent: function(comp) {
124384         var compEl = comp.el,
124385             slideOutAnim;
124386
124387         // Remove mouse leave monitors
124388         compEl.un(comp.panelMouseMon);
124389         comp.placeholder.el.un(comp.placeholderMouseMon);
124390
124391         // Slide the Component out
124392         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
124393
124394         delete comp.slideOutAnim;
124395         delete comp.panelMouseMon;
124396         delete comp.placeholderMouseMon;
124397     },
124398
124399     /*
124400      * @private
124401      * Ensure any collapsed placeholder Component is destroyed along with its region.
124402      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
124403      */
124404     onRegionDestroy: function(comp) {
124405         var placeholder = comp.placeholder;
124406         if (placeholder) {
124407             delete placeholder.ownerCt;
124408             placeholder.destroy();
124409         }
124410     },
124411
124412     /*
124413      * @private
124414      * Ensure any shadow Containers are destroyed.
124415      * Ensure we don't keep references to Components.
124416      */
124417     onDestroy: function() {
124418         var me = this,
124419             shadowContainer = me.shadowContainer,
124420             embeddedContainer = me.embeddedContainer;
124421
124422         if (shadowContainer) {
124423             delete shadowContainer.ownerCt;
124424             Ext.destroy(shadowContainer);
124425         }
124426
124427         if (embeddedContainer) {
124428             delete embeddedContainer.ownerCt;
124429             Ext.destroy(embeddedContainer);
124430         }
124431         delete me.regions;
124432         delete me.splitters;
124433         delete me.shadowContainer;
124434         delete me.embeddedContainer;
124435         me.callParent(arguments);
124436     }
124437 });
124438
124439 /**
124440  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
124441  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
124442  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
124443  * and should generally not need to be created directly via the new keyword.
124444  *
124445  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
124446  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
124447  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
124448  * so that functionality must be provided by the developer.
124449  *
124450  * To change the active card of a container, call the setActiveItem method of its layout:
124451  *
124452  *     Ext.create('Ext.panel.Panel', {
124453  *         layout: 'card',
124454  *         items: [
124455  *             { html: 'Card 1' },
124456  *             { html: 'Card 2' }
124457  *         ],
124458  *         renderTo: Ext.getBody()
124459  *     });
124460  *
124461  *     p.getLayout().setActiveItem(1);
124462  *
124463  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
124464  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
124465  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
124466  * completely different implementation.  For serious implementations, a better approach would be to extend
124467  * CardLayout to provide the custom functionality needed.
124468  *
124469  *     @example
124470  *     var navigate = function(panel, direction){
124471  *         // This routine could contain business logic required to manage the navigation steps.
124472  *         // It would call setActiveItem as needed, manage navigation button state, handle any
124473  *         // branching logic that might be required, handle alternate actions like cancellation
124474  *         // or finalization, etc.  A complete wizard implementation could get pretty
124475  *         // sophisticated depending on the complexity required, and should probably be
124476  *         // done as a subclass of CardLayout in a real-world implementation.
124477  *         var layout = panel.getLayout();
124478  *         layout[direction]();
124479  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
124480  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
124481  *     };
124482  *
124483  *     Ext.create('Ext.panel.Panel', {
124484  *         title: 'Example Wizard',
124485  *         width: 300,
124486  *         height: 200,
124487  *         layout: 'card',
124488  *         bodyStyle: 'padding:15px',
124489  *         defaults: {
124490  *             // applied to each contained panel
124491  *             border: false
124492  *         },
124493  *         // just an example of one possible navigation scheme, using buttons
124494  *         bbar: [
124495  *             {
124496  *                 id: 'move-prev',
124497  *                 text: 'Back',
124498  *                 handler: function(btn) {
124499  *                     navigate(btn.up("panel"), "prev");
124500  *                 },
124501  *                 disabled: true
124502  *             },
124503  *             '->', // greedy spacer so that the buttons are aligned to each side
124504  *             {
124505  *                 id: 'move-next',
124506  *                 text: 'Next',
124507  *                 handler: function(btn) {
124508  *                     navigate(btn.up("panel"), "next");
124509  *                 }
124510  *             }
124511  *         ],
124512  *         // the panels (or "cards") within the layout
124513  *         items: [{
124514  *             id: 'card-0',
124515  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
124516  *         },{
124517  *             id: 'card-1',
124518  *             html: '<p>Step 2 of 3</p>'
124519  *         },{
124520  *             id: 'card-2',
124521  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
124522  *         }],
124523  *         renderTo: Ext.getBody()
124524  *     });
124525  */
124526 Ext.define('Ext.layout.container.Card', {
124527
124528     /* Begin Definitions */
124529
124530     alias: ['layout.card'],
124531     alternateClassName: 'Ext.layout.CardLayout',
124532
124533     extend: 'Ext.layout.container.AbstractCard',
124534
124535     /* End Definitions */
124536
124537     /**
124538      * Makes the given card active.
124539      *
124540      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
124541      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
124542      *     var panel = Ext.create('Ext.panel.Panel', {
124543      *         layout: 'card',
124544      *         activeItem: 0,
124545      *         items: [card1, card2]
124546      *     });
124547      *     // These are all equivalent
124548      *     panel.getLayout().setActiveItem(card2);
124549      *     panel.getLayout().setActiveItem('card-2');
124550      *     panel.getLayout().setActiveItem(1);
124551      *
124552      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
124553      * {@link Ext.Component#itemId itemId}, or index of component.
124554      * @return {Ext.Component} the activated component or false when nothing activated.
124555      * False is returned also when trying to activate an already active card.
124556      */
124557     setActiveItem: function(newCard) {
124558         var me = this,
124559             owner = me.owner,
124560             oldCard = me.activeItem,
124561             newIndex;
124562
124563         newCard = me.parseActiveItem(newCard);
124564         newIndex = owner.items.indexOf(newCard);
124565
124566         // If the card is not a child of the owner, then add it
124567         if (newIndex == -1) {
124568             newIndex = owner.items.items.length;
124569             owner.add(newCard);
124570         }
124571
124572         // Is this a valid, different card?
124573         if (newCard && oldCard != newCard) {
124574             // If the card has not been rendered yet, now is the time to do so.
124575             if (!newCard.rendered) {
124576                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
124577                 me.configureItem(newCard, 0);
124578             }
124579
124580             me.activeItem = newCard;
124581
124582             // Fire the beforeactivate and beforedeactivate events on the cards
124583             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
124584                 return false;
124585             }
124586             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
124587                 return false;
124588             }
124589
124590             // If the card hasnt been sized yet, do it now
124591             if (me.sizeAllCards) {
124592                 // onLayout calls setItemBox
124593                 me.onLayout();
124594             }
124595             else {
124596                 me.setItemBox(newCard, me.getTargetBox());
124597             }
124598
124599             me.owner.suspendLayout = true;
124600
124601             if (oldCard) {
124602                 if (me.hideInactive) {
124603                     oldCard.hide();
124604                 }
124605                 oldCard.fireEvent('deactivate', oldCard, newCard);
124606             }
124607
124608             // Make sure the new card is shown
124609             me.owner.suspendLayout = false;
124610             if (newCard.hidden) {
124611                 newCard.show();
124612             } else {
124613                 me.onLayout();
124614             }
124615
124616             newCard.fireEvent('activate', newCard, oldCard);
124617
124618             return newCard;
124619         }
124620         return false;
124621     },
124622
124623     configureItem: function(item) {
124624         // Card layout only controls dimensions which IT has controlled.
124625         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
124626         item.layoutManagedHeight = 0;
124627         item.layoutManagedWidth = 0;
124628
124629         this.callParent(arguments);
124630     }});
124631 /**
124632  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
124633  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
124634  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
124635  * and should generally not need to be created directly via the new keyword.
124636  *
124637  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
124638  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
124639  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
124640  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
124641  *
124642  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
124643  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
124644  *
124645  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
124646  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
124647  * are skipped, but their widths are subtracted from the overall container width.
124648  *
124649  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
124650  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
124651  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
124652  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
124653  * together, otherwise your layout may not render as expected.
124654  *
124655  *     @example
124656  *     // All columns are percentages -- they must add up to 1
124657  *     Ext.create('Ext.panel.Panel', {
124658  *         title: 'Column Layout - Percentage Only',
124659  *         width: 350,
124660  *         height: 250,
124661  *         layout:'column',
124662  *         items: [{
124663  *             title: 'Column 1',
124664  *             columnWidth: .25
124665  *         },{
124666  *             title: 'Column 2',
124667  *             columnWidth: .55
124668  *         },{
124669  *             title: 'Column 3',
124670  *             columnWidth: .20
124671  *         }],
124672  *         renderTo: Ext.getBody()
124673  *     });
124674  *
124675  *     // Mix of width and columnWidth -- all columnWidth values must add up
124676  *     // to 1. The first column will take up exactly 120px, and the last two
124677  *     // columns will fill the remaining container width.
124678  *
124679  *     Ext.create('Ext.Panel', {
124680  *         title: 'Column Layout - Mixed',
124681  *         width: 350,
124682  *         height: 250,
124683  *         layout:'column',
124684  *         items: [{
124685  *             title: 'Column 1',
124686  *             width: 120
124687  *         },{
124688  *             title: 'Column 2',
124689  *             columnWidth: .7
124690  *         },{
124691  *             title: 'Column 3',
124692  *             columnWidth: .3
124693  *         }],
124694  *         renderTo: Ext.getBody()
124695  *     });
124696  */
124697 Ext.define('Ext.layout.container.Column', {
124698
124699     extend: 'Ext.layout.container.Auto',
124700     alias: ['layout.column'],
124701     alternateClassName: 'Ext.layout.ColumnLayout',
124702
124703     type: 'column',
124704
124705     itemCls: Ext.baseCSSPrefix + 'column',
124706
124707     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
124708
124709     scrollOffset: 0,
124710
124711     bindToOwnerCtComponent: false,
124712
124713     getRenderTarget : function() {
124714         if (!this.innerCt) {
124715
124716             // the innerCt prevents wrapping and shuffling while
124717             // the container is resizing
124718             this.innerCt = this.getTarget().createChild({
124719                 cls: Ext.baseCSSPrefix + 'column-inner'
124720             });
124721
124722             // Column layout uses natural HTML flow to arrange the child items.
124723             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
124724             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
124725             this.clearEl = this.innerCt.createChild({
124726                 cls: Ext.baseCSSPrefix + 'clear',
124727                 role: 'presentation'
124728             });
124729         }
124730         return this.innerCt;
124731     },
124732
124733     // private
124734     onLayout : function() {
124735         var me = this,
124736             target = me.getTarget(),
124737             items = me.getLayoutItems(),
124738             len = items.length,
124739             item,
124740             i,
124741             parallelMargins = [],
124742             itemParallelMargins,
124743             size,
124744             availableWidth,
124745             columnWidth;
124746
124747         size = me.getLayoutTargetSize();
124748         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
124749             return;
124750         }
124751
124752         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
124753         // If, after the layout, it is detected that there is vertical overflow,
124754         // we will recurse back through here. Do not adjust overflow style at that time.
124755         if (me.adjustmentPass) {
124756             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124757                 size.width = me.adjustedWidth;
124758             }
124759         } else {
124760             i = target.getStyle('overflow');
124761             if (i && i != 'hidden') {
124762                 me.autoScroll = true;
124763                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
124764                     target.setStyle('overflow', 'hidden');
124765                     size = me.getLayoutTargetSize();
124766                 }
124767             }
124768         }
124769
124770         availableWidth = size.width - me.scrollOffset;
124771         me.innerCt.setWidth(availableWidth);
124772
124773         // some columns can be percentages while others are fixed
124774         // so we need to make 2 passes
124775         for (i = 0; i < len; i++) {
124776             item = items[i];
124777             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
124778             if (!item.columnWidth) {
124779                 availableWidth -= (item.getWidth() + itemParallelMargins);
124780             }
124781         }
124782
124783         availableWidth = availableWidth < 0 ? 0 : availableWidth;
124784         for (i = 0; i < len; i++) {
124785             item = items[i];
124786             if (item.columnWidth) {
124787                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
124788                 me.setItemSize(item, columnWidth, item.height);
124789             } else {
124790                 me.layoutItem(item);
124791             }
124792         }
124793
124794         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
124795         if (!me.adjustmentPass && me.autoScroll) {
124796
124797             // If there's a vertical overflow, relay with scrollbars
124798             target.setStyle('overflow', 'auto');
124799             me.adjustmentPass = (target.dom.scrollHeight > size.height);
124800             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124801                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
124802             } else {
124803                 target.setStyle('overflow', 'auto');
124804             }
124805
124806             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
124807             if (me.adjustmentPass) {
124808                 me.onLayout();
124809             }
124810         }
124811         delete me.adjustmentPass;
124812     },
124813
124814     configureItem: function(item) {
124815         this.callParent(arguments);
124816
124817         if (item.columnWidth) {
124818             item.layoutManagedWidth = 1;
124819         }
124820     }
124821 });
124822 /**
124823  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
124824  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
124825  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
124826  * need to be created directly via the new keyword.
124827  *
124828  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
124829  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
124830  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
124831  * added to a TableLayout can supply the following table-specific config properties:
124832  *
124833  *   - **rowspan** Applied to the table cell containing the item.
124834  *   - **colspan** Applied to the table cell containing the item.
124835  *   - **cellId** An id applied to the table cell containing the item.
124836  *   - **cellCls** A CSS class name added to the table cell containing the item.
124837  *
124838  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
124839  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
124840  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
124841  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
124842  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
124843  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
124844  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
124845  * end up with missing and/or extra cells! Example usage:
124846  *
124847  *     @example
124848  *     Ext.create('Ext.panel.Panel', {
124849  *         title: 'Table Layout',
124850  *         width: 300,
124851  *         height: 150,
124852  *         layout: {
124853  *             type: 'table',
124854  *             // The total column count must be specified here
124855  *             columns: 3
124856  *         },
124857  *         defaults: {
124858  *             // applied to each contained panel
124859  *             bodyStyle: 'padding:20px'
124860  *         },
124861  *         items: [{
124862  *             html: 'Cell A content',
124863  *             rowspan: 2
124864  *         },{
124865  *             html: 'Cell B content',
124866  *             colspan: 2
124867  *         },{
124868  *             html: 'Cell C content',
124869  *             cellCls: 'highlight'
124870  *         },{
124871  *             html: 'Cell D content'
124872  *         }],
124873  *         renderTo: Ext.getBody()
124874  *     });
124875  */
124876 Ext.define('Ext.layout.container.Table', {
124877
124878     /* Begin Definitions */
124879
124880     alias: ['layout.table'],
124881     extend: 'Ext.layout.container.Auto',
124882     alternateClassName: 'Ext.layout.TableLayout',
124883
124884     /* End Definitions */
124885
124886     /**
124887      * @cfg {Number} columns
124888      * The total number of columns to create in the table for this layout. If not specified, all Components added to
124889      * this layout will be rendered into a single row using one column per Component.
124890      */
124891
124892     // private
124893     monitorResize:false,
124894
124895     type: 'table',
124896
124897     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
124898     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
124899     autoSize: true,
124900
124901     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
124902
124903     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
124904     tableCls: Ext.baseCSSPrefix + 'table-layout',
124905     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
124906
124907     /**
124908      * @cfg {Object} tableAttrs
124909      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124910      * create the layout's `<table>` element. Example:
124911      *
124912      *     {
124913      *         xtype: 'panel',
124914      *         layout: {
124915      *             type: 'table',
124916      *             columns: 3,
124917      *             tableAttrs: {
124918      *                 style: {
124919      *                     width: '100%'
124920      *                 }
124921      *             }
124922      *         }
124923      *     }
124924      */
124925     tableAttrs:null,
124926
124927     /**
124928      * @cfg {Object} trAttrs
124929      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124930      * create the layout's <tr> elements.
124931      */
124932
124933     /**
124934      * @cfg {Object} tdAttrs
124935      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124936      * create the layout's <td> elements.
124937      */
124938
124939     /**
124940      * @private
124941      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
124942      * location in the table structure.
124943      */
124944     renderItems: function(items) {
124945         var tbody = this.getTable().tBodies[0],
124946             rows = tbody.rows,
124947             i = 0,
124948             len = items.length,
124949             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
124950
124951         // Calculate the correct cell structure for the current items
124952         cells = this.calculateCells(items);
124953
124954         // Loop over each cell and compare to the current cells in the table, inserting/
124955         // removing/moving cells as needed, and making sure each item is rendered into
124956         // the correct cell.
124957         for (; i < len; i++) {
124958             curCell = cells[i];
124959             rowIdx = curCell.rowIdx;
124960             cellIdx = curCell.cellIdx;
124961             item = items[i];
124962
124963             // If no row present, create and insert one
124964             trEl = rows[rowIdx];
124965             if (!trEl) {
124966                 trEl = tbody.insertRow(rowIdx);
124967                 if (this.trAttrs) {
124968                     trEl.set(this.trAttrs);
124969                 }
124970             }
124971
124972             // If no cell present, create and insert one
124973             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
124974             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
124975                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
124976                 itemCt.setWidth(null);
124977             }
124978
124979             // Render or move the component into the cell
124980             if (!item.rendered) {
124981                 this.renderItem(item, itemCt, 0);
124982             }
124983             else if (!this.isValidParent(item, itemCt, 0)) {
124984                 this.moveItem(item, itemCt, 0);
124985             }
124986
124987             // Set the cell properties
124988             if (this.tdAttrs) {
124989                 tdEl.set(this.tdAttrs);
124990             }
124991             tdEl.set({
124992                 colSpan: item.colspan || 1,
124993                 rowSpan: item.rowspan || 1,
124994                 id: item.cellId || '',
124995                 cls: this.cellCls + ' ' + (item.cellCls || '')
124996             });
124997
124998             // If at the end of a row, remove any extra cells
124999             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
125000                 cellIdx++;
125001                 while (trEl.cells[cellIdx]) {
125002                     trEl.deleteCell(cellIdx);
125003                 }
125004             }
125005         }
125006
125007         // Delete any extra rows
125008         rowIdx++;
125009         while (tbody.rows[rowIdx]) {
125010             tbody.deleteRow(rowIdx);
125011         }
125012     },
125013
125014     afterLayout: function() {
125015         this.callParent();
125016
125017         if (this.needsDivWrap()) {
125018             // set wrapper div width to match layed out item - see docs below
125019             Ext.Array.forEach(this.getLayoutItems(), function(item) {
125020                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
125021             });
125022         }
125023     },
125024
125025     /**
125026      * @private
125027      * Determine the row and cell indexes for each component, taking into consideration
125028      * the number of columns and each item's configured colspan/rowspan values.
125029      * @param {Array} items The layout components
125030      * @return {Object[]} List of row and cell indexes for each of the components
125031      */
125032     calculateCells: function(items) {
125033         var cells = [],
125034             rowIdx = 0,
125035             colIdx = 0,
125036             cellIdx = 0,
125037             totalCols = this.columns || Infinity,
125038             rowspans = [], //rolling list of active rowspans for each column
125039             i = 0, j,
125040             len = items.length,
125041             item;
125042
125043         for (; i < len; i++) {
125044             item = items[i];
125045
125046             // Find the first available row/col slot not taken up by a spanning cell
125047             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
125048                 if (colIdx >= totalCols) {
125049                     // move down to next row
125050                     colIdx = 0;
125051                     cellIdx = 0;
125052                     rowIdx++;
125053
125054                     // decrement all rowspans
125055                     for (j = 0; j < totalCols; j++) {
125056                         if (rowspans[j] > 0) {
125057                             rowspans[j]--;
125058                         }
125059                     }
125060                 } else {
125061                     colIdx++;
125062                 }
125063             }
125064
125065             // Add the cell info to the list
125066             cells.push({
125067                 rowIdx: rowIdx,
125068                 cellIdx: cellIdx
125069             });
125070
125071             // Increment
125072             for (j = item.colspan || 1; j; --j) {
125073                 rowspans[colIdx] = item.rowspan || 1;
125074                 ++colIdx;
125075             }
125076             ++cellIdx;
125077         }
125078
125079         return cells;
125080     },
125081
125082     /**
125083      * @private
125084      * Return the layout's table element, creating it if necessary.
125085      */
125086     getTable: function() {
125087         var table = this.table;
125088         if (!table) {
125089             table = this.table = this.getTarget().createChild(
125090                 Ext.apply({
125091                     tag: 'table',
125092                     role: 'presentation',
125093                     cls: this.tableCls,
125094                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
125095                     cn: {tag: 'tbody'}
125096                 }, this.tableAttrs),
125097                 null, true
125098             );
125099         }
125100         return table;
125101     },
125102
125103     /**
125104      * @private
125105      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
125106      * will include that padding in the size of the cell, making it always larger than the
125107      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
125108      * and then set that div's width to match the item rendered within it afterLayout. This method
125109      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
125110      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
125111      */
125112     needsDivWrap: function() {
125113         return Ext.isOpera10_5;
125114     }
125115 });
125116 /**
125117  * A base class for all menu items that require menu-related functionality such as click handling,
125118  * sub-menus, icons, etc.
125119  *
125120  *     @example
125121  *     Ext.create('Ext.menu.Menu', {
125122  *         width: 100,
125123  *         height: 100,
125124  *         floating: false,  // usually you want this set to True (default)
125125  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125126  *         items: [{
125127  *             text: 'icon item',
125128  *             iconCls: 'add16'
125129  *         },{
125130  *             text: 'text item'
125131  *         },{
125132  *             text: 'plain item',
125133  *             plain: true
125134  *         }]
125135  *     });
125136  */
125137 Ext.define('Ext.menu.Item', {
125138     extend: 'Ext.Component',
125139     alias: 'widget.menuitem',
125140     alternateClassName: 'Ext.menu.TextItem',
125141
125142     /**
125143      * @property {Boolean} activated
125144      * Whether or not this item is currently activated
125145      */
125146
125147     /**
125148      * @property {Ext.menu.Menu} parentMenu
125149      * The parent Menu of this item.
125150      */
125151
125152     /**
125153      * @cfg {String} activeCls
125154      * The CSS class added to the menu item when the item is activated (focused/mouseover).
125155      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
125156      */
125157     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
125158
125159     /**
125160      * @cfg {String} ariaRole @hide
125161      */
125162     ariaRole: 'menuitem',
125163
125164     /**
125165      * @cfg {Boolean} canActivate
125166      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
125167      */
125168     canActivate: true,
125169
125170     /**
125171      * @cfg {Number} clickHideDelay
125172      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
125173      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
125174      */
125175     clickHideDelay: 1,
125176
125177     /**
125178      * @cfg {Boolean} destroyMenu
125179      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
125180      */
125181     destroyMenu: true,
125182
125183     /**
125184      * @cfg {String} disabledCls
125185      * The CSS class added to the menu item when the item is disabled.
125186      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
125187      */
125188     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
125189
125190     /**
125191      * @cfg {String} href
125192      * The href attribute to use for the underlying anchor link. Defaults to `#`.
125193      * @markdown
125194      */
125195
125196      /**
125197       * @cfg {String} hrefTarget
125198       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
125199       * @markdown
125200       */
125201
125202     /**
125203      * @cfg {Boolean} hideOnClick
125204      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
125205      * @markdown
125206      */
125207     hideOnClick: true,
125208
125209     /**
125210      * @cfg {String} icon
125211      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
125212      * @markdown
125213      */
125214
125215     /**
125216      * @cfg {String} iconCls
125217      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
125218      * @markdown
125219      */
125220
125221     isMenuItem: true,
125222
125223     /**
125224      * @cfg {Mixed} menu
125225      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
125226      * which will act as a sub-menu to this item.
125227      * @markdown
125228      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
125229      */
125230
125231     /**
125232      * @cfg {String} menuAlign
125233      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
125234      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
125235      * @markdown
125236      */
125237     menuAlign: 'tl-tr?',
125238
125239     /**
125240      * @cfg {Number} menuExpandDelay
125241      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
125242      * @markdown
125243      */
125244     menuExpandDelay: 200,
125245
125246     /**
125247      * @cfg {Number} menuHideDelay
125248      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
125249      * @markdown
125250      */
125251     menuHideDelay: 200,
125252
125253     /**
125254      * @cfg {Boolean} plain
125255      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
125256      * @markdown
125257      */
125258
125259     renderTpl: [
125260         '<tpl if="plain">',
125261             '{text}',
125262         '</tpl>',
125263         '<tpl if="!plain">',
125264             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
125265                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
125266                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
125267                 '<tpl if="menu">',
125268                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
125269                 '</tpl>',
125270             '</a>',
125271         '</tpl>'
125272     ],
125273
125274     maskOnDisable: false,
125275
125276     /**
125277      * @cfg {String} text
125278      * The text/html to display in this item. Defaults to `undefined`.
125279      * @markdown
125280      */
125281
125282     activate: function() {
125283         var me = this;
125284
125285         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
125286             me.el.addCls(me.activeCls);
125287             me.focus();
125288             me.activated = true;
125289             me.fireEvent('activate', me);
125290         }
125291     },
125292
125293     blur: function() {
125294         this.$focused = false;
125295         this.callParent(arguments);
125296     },
125297
125298     deactivate: function() {
125299         var me = this;
125300
125301         if (me.activated) {
125302             me.el.removeCls(me.activeCls);
125303             me.blur();
125304             me.hideMenu();
125305             me.activated = false;
125306             me.fireEvent('deactivate', me);
125307         }
125308     },
125309
125310     deferExpandMenu: function() {
125311         var me = this;
125312
125313         if (!me.menu.rendered || !me.menu.isVisible()) {
125314             me.parentMenu.activeChild = me.menu;
125315             me.menu.parentItem = me;
125316             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
125317             me.menu.showBy(me, me.menuAlign);
125318         }
125319     },
125320
125321     deferHideMenu: function() {
125322         if (this.menu.isVisible()) {
125323             this.menu.hide();
125324         }
125325     },
125326
125327     deferHideParentMenus: function() {
125328         Ext.menu.Manager.hideAll();
125329     },
125330
125331     expandMenu: function(delay) {
125332         var me = this;
125333
125334         if (me.menu) {
125335             clearTimeout(me.hideMenuTimer);
125336             if (delay === 0) {
125337                 me.deferExpandMenu();
125338             } else {
125339                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
125340             }
125341         }
125342     },
125343
125344     focus: function() {
125345         this.$focused = true;
125346         this.callParent(arguments);
125347     },
125348
125349     getRefItems: function(deep){
125350         var menu = this.menu,
125351             items;
125352
125353         if (menu) {
125354             items = menu.getRefItems(deep);
125355             items.unshift(menu);
125356         }
125357         return items || [];
125358     },
125359
125360     hideMenu: function(delay) {
125361         var me = this;
125362
125363         if (me.menu) {
125364             clearTimeout(me.expandMenuTimer);
125365             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
125366         }
125367     },
125368
125369     initComponent: function() {
125370         var me = this,
125371             prefix = Ext.baseCSSPrefix,
125372             cls = [prefix + 'menu-item'];
125373
125374         me.addEvents(
125375             /**
125376              * @event activate
125377              * Fires when this item is activated
125378              * @param {Ext.menu.Item} item The activated item
125379              */
125380             'activate',
125381
125382             /**
125383              * @event click
125384              * Fires when this item is clicked
125385              * @param {Ext.menu.Item} item The item that was clicked
125386              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
125387              */
125388             'click',
125389
125390             /**
125391              * @event deactivate
125392              * Fires when this tiem is deactivated
125393              * @param {Ext.menu.Item} item The deactivated item
125394              */
125395             'deactivate'
125396         );
125397
125398         if (me.plain) {
125399             cls.push(prefix + 'menu-item-plain');
125400         }
125401
125402         if (me.cls) {
125403             cls.push(me.cls);
125404         }
125405
125406         me.cls = cls.join(' ');
125407
125408         if (me.menu) {
125409             me.menu = Ext.menu.Manager.get(me.menu);
125410         }
125411
125412         me.callParent(arguments);
125413     },
125414
125415     onClick: function(e) {
125416         var me = this;
125417
125418         if (!me.href) {
125419             e.stopEvent();
125420         }
125421
125422         if (me.disabled) {
125423             return;
125424         }
125425
125426         if (me.hideOnClick) {
125427             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
125428         }
125429
125430         Ext.callback(me.handler, me.scope || me, [me, e]);
125431         me.fireEvent('click', me, e);
125432
125433         if (!me.hideOnClick) {
125434             me.focus();
125435         }
125436     },
125437
125438     onDestroy: function() {
125439         var me = this;
125440
125441         clearTimeout(me.expandMenuTimer);
125442         clearTimeout(me.hideMenuTimer);
125443         clearTimeout(me.deferHideParentMenusTimer);
125444
125445         if (me.menu) {
125446             delete me.menu.parentItem;
125447             delete me.menu.parentMenu;
125448             delete me.menu.ownerCt;
125449             if (me.destroyMenu !== false) {
125450                 me.menu.destroy();
125451             }
125452         }
125453         me.callParent(arguments);
125454     },
125455
125456     onRender: function(ct, pos) {
125457         var me = this,
125458             blank = Ext.BLANK_IMAGE_URL;
125459
125460         Ext.applyIf(me.renderData, {
125461             href: me.href || '#',
125462             hrefTarget: me.hrefTarget,
125463             icon: me.icon || blank,
125464             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
125465             menu: Ext.isDefined(me.menu),
125466             plain: me.plain,
125467             text: me.text,
125468             blank: blank
125469         });
125470
125471         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
125472
125473         me.callParent(arguments);
125474     },
125475
125476     /**
125477      * Sets the {@link #click} handler of this item
125478      * @param {Function} fn The handler function
125479      * @param {Object} scope (optional) The scope of the handler function
125480      */
125481     setHandler: function(fn, scope) {
125482         this.handler = fn || null;
125483         this.scope = scope;
125484     },
125485
125486     /**
125487      * Sets the {@link #iconCls} of this item
125488      * @param {String} iconCls The CSS class to set to {@link #iconCls}
125489      */
125490     setIconCls: function(iconCls) {
125491         var me = this;
125492
125493         if (me.iconEl) {
125494             if (me.iconCls) {
125495                 me.iconEl.removeCls(me.iconCls);
125496             }
125497
125498             if (iconCls) {
125499                 me.iconEl.addCls(iconCls);
125500             }
125501         }
125502
125503         me.iconCls = iconCls;
125504     },
125505
125506     /**
125507      * Sets the {@link #text} of this item
125508      * @param {String} text The {@link #text}
125509      */
125510     setText: function(text) {
125511         var me = this,
125512             el = me.textEl || me.el;
125513
125514         me.text = text;
125515
125516         if (me.rendered) {
125517             el.update(text || '');
125518             // cannot just call doComponentLayout due to stretchmax
125519             me.ownerCt.redoComponentLayout();
125520         }
125521     }
125522 });
125523
125524 /**
125525  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
125526  *
125527  *     @example
125528  *     Ext.create('Ext.menu.Menu', {
125529  *         width: 100,
125530  *         height: 110,
125531  *         floating: false,  // usually you want this set to True (default)
125532  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125533  *         items: [{
125534  *             xtype: 'menucheckitem',
125535  *             text: 'select all'
125536  *         },{
125537  *             xtype: 'menucheckitem',
125538  *             text: 'select specific',
125539  *         },{
125540  *             iconCls: 'add16',
125541  *             text: 'icon item'
125542  *         },{
125543  *             text: 'regular item'
125544  *         }]
125545  *     });
125546  */
125547 Ext.define('Ext.menu.CheckItem', {
125548     extend: 'Ext.menu.Item',
125549     alias: 'widget.menucheckitem',
125550
125551     /**
125552      * @cfg {String} checkedCls
125553      * The CSS class used by {@link #cls} to show the checked state.
125554      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
125555      */
125556     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
125557     /**
125558      * @cfg {String} uncheckedCls
125559      * The CSS class used by {@link #cls} to show the unchecked state.
125560      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
125561      */
125562     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
125563     /**
125564      * @cfg {String} groupCls
125565      * The CSS class applied to this item's icon image to denote being a part of a radio group.
125566      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
125567      * Any specified {@link #iconCls} overrides this.
125568      */
125569     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
125570
125571     /**
125572      * @cfg {Boolean} hideOnClick
125573      * Whether to not to hide the owning menu when this item is clicked.
125574      * Defaults to `false` for checkbox items, and to `true` for radio group items.
125575      */
125576     hideOnClick: false,
125577
125578     afterRender: function() {
125579         var me = this;
125580         this.callParent();
125581         me.checked = !me.checked;
125582         me.setChecked(!me.checked, true);
125583     },
125584
125585     initComponent: function() {
125586         var me = this;
125587         me.addEvents(
125588             /**
125589              * @event beforecheckchange
125590              * Fires before a change event. Return false to cancel.
125591              * @param {Ext.menu.CheckItem} this
125592              * @param {Boolean} checked
125593              */
125594             'beforecheckchange',
125595
125596             /**
125597              * @event checkchange
125598              * Fires after a change event.
125599              * @param {Ext.menu.CheckItem} this
125600              * @param {Boolean} checked
125601              */
125602             'checkchange'
125603         );
125604
125605         me.callParent(arguments);
125606
125607         Ext.menu.Manager.registerCheckable(me);
125608
125609         if (me.group) {
125610             if (!me.iconCls) {
125611                 me.iconCls = me.groupCls;
125612             }
125613             if (me.initialConfig.hideOnClick !== false) {
125614                 me.hideOnClick = true;
125615             }
125616         }
125617     },
125618
125619     /**
125620      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
125621      * will still be accessible
125622      */
125623     disableCheckChange: function() {
125624         var me = this;
125625
125626         if (me.iconEl) {
125627             me.iconEl.addCls(me.disabledCls);
125628         }
125629         me.checkChangeDisabled = true;
125630     },
125631
125632     /**
125633      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
125634      */
125635     enableCheckChange: function() {
125636         var me = this;
125637
125638         me.iconEl.removeCls(me.disabledCls);
125639         me.checkChangeDisabled = false;
125640     },
125641
125642     onClick: function(e) {
125643         var me = this;
125644         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
125645             me.setChecked(!me.checked);
125646         }
125647         this.callParent([e]);
125648     },
125649
125650     onDestroy: function() {
125651         Ext.menu.Manager.unregisterCheckable(this);
125652         this.callParent(arguments);
125653     },
125654
125655     /**
125656      * Sets the checked state of the item
125657      * @param {Boolean} checked True to check, false to uncheck
125658      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
125659      */
125660     setChecked: function(checked, suppressEvents) {
125661         var me = this;
125662         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
125663             if (me.el) {
125664                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
125665             }
125666             me.checked = checked;
125667             Ext.menu.Manager.onCheckChange(me, checked);
125668             if (!suppressEvents) {
125669                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
125670                 me.fireEvent('checkchange', me, checked);
125671             }
125672         }
125673     }
125674 });
125675
125676 /**
125677  * @class Ext.menu.KeyNav
125678  * @private
125679  */
125680 Ext.define('Ext.menu.KeyNav', {
125681     extend: 'Ext.util.KeyNav',
125682
125683     requires: ['Ext.FocusManager'],
125684     
125685     constructor: function(menu) {
125686         var me = this;
125687
125688         me.menu = menu;
125689         me.callParent([menu.el, {
125690             down: me.down,
125691             enter: me.enter,
125692             esc: me.escape,
125693             left: me.left,
125694             right: me.right,
125695             space: me.enter,
125696             tab: me.tab,
125697             up: me.up
125698         }]);
125699     },
125700
125701     down: function(e) {
125702         var me = this,
125703             fi = me.menu.focusedItem;
125704
125705         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
125706             return true;
125707         }
125708         me.focusNextItem(1);
125709     },
125710
125711     enter: function(e) {
125712         var menu = this.menu,
125713             focused = menu.focusedItem;
125714  
125715         if (menu.activeItem) {
125716             menu.onClick(e);
125717         } else if (focused && focused.isFormField) {
125718             // prevent stopEvent being called
125719             return true;
125720         }
125721     },
125722
125723     escape: function(e) {
125724         Ext.menu.Manager.hideAll();
125725     },
125726
125727     focusNextItem: function(step) {
125728         var menu = this.menu,
125729             items = menu.items,
125730             focusedItem = menu.focusedItem,
125731             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
125732             idx = startIdx + step;
125733
125734         while (idx != startIdx) {
125735             if (idx < 0) {
125736                 idx = items.length - 1;
125737             } else if (idx >= items.length) {
125738                 idx = 0;
125739             }
125740
125741             var item = items.getAt(idx);
125742             if (menu.canActivateItem(item)) {
125743                 menu.setActiveItem(item);
125744                 break;
125745             }
125746             idx += step;
125747         }
125748     },
125749
125750     isWhitelisted: function(item) {
125751         return Ext.FocusManager.isWhitelisted(item);
125752     },
125753
125754     left: function(e) {
125755         var menu = this.menu,
125756             fi = menu.focusedItem,
125757             ai = menu.activeItem;
125758
125759         if (fi && this.isWhitelisted(fi)) {
125760             return true;
125761         }
125762
125763         menu.hide();
125764         if (menu.parentMenu) {
125765             menu.parentMenu.focus();
125766         }
125767     },
125768
125769     right: function(e) {
125770         var menu = this.menu,
125771             fi = menu.focusedItem,
125772             ai = menu.activeItem,
125773             am;
125774
125775         if (fi && this.isWhitelisted(fi)) {
125776             return true;
125777         }
125778
125779         if (ai) {
125780             am = menu.activeItem.menu;
125781             if (am) {
125782                 ai.expandMenu(0);
125783                 Ext.defer(function() {
125784                     am.setActiveItem(am.items.getAt(0));
125785                 }, 25);
125786             }
125787         }
125788     },
125789
125790     tab: function(e) {
125791         var me = this;
125792
125793         if (e.shiftKey) {
125794             me.up(e);
125795         } else {
125796             me.down(e);
125797         }
125798     },
125799
125800     up: function(e) {
125801         var me = this,
125802             fi = me.menu.focusedItem;
125803
125804         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
125805             return true;
125806         }
125807         me.focusNextItem(-1);
125808     }
125809 });
125810 /**
125811  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
125812  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
125813  *
125814  *     @example
125815  *     Ext.create('Ext.menu.Menu', {
125816  *         width: 100,
125817  *         height: 100,
125818  *         floating: false,  // usually you want this set to True (default)
125819  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125820  *         items: [{
125821  *             text: 'icon item',
125822  *             iconCls: 'add16'
125823  *         },{
125824  *             xtype: 'menuseparator'
125825  *         },{
125826  *            text: 'seperator above',
125827  *         },{
125828  *            text: 'regular item',
125829  *         }]
125830  *     });
125831  */
125832 Ext.define('Ext.menu.Separator', {
125833     extend: 'Ext.menu.Item',
125834     alias: 'widget.menuseparator',
125835
125836     /**
125837      * @cfg {String} activeCls @hide
125838      */
125839
125840     /**
125841      * @cfg {Boolean} canActivate @hide
125842      */
125843     canActivate: false,
125844
125845     /**
125846      * @cfg {Boolean} clickHideDelay @hide
125847      */
125848
125849     /**
125850      * @cfg {Boolean} destroyMenu @hide
125851      */
125852
125853     /**
125854      * @cfg {Boolean} disabledCls @hide
125855      */
125856
125857     focusable: false,
125858
125859     /**
125860      * @cfg {String} href @hide
125861      */
125862
125863     /**
125864      * @cfg {String} hrefTarget @hide
125865      */
125866
125867     /**
125868      * @cfg {Boolean} hideOnClick @hide
125869      */
125870     hideOnClick: false,
125871
125872     /**
125873      * @cfg {String} icon @hide
125874      */
125875
125876     /**
125877      * @cfg {String} iconCls @hide
125878      */
125879
125880     /**
125881      * @cfg {Object} menu @hide
125882      */
125883
125884     /**
125885      * @cfg {String} menuAlign @hide
125886      */
125887
125888     /**
125889      * @cfg {Number} menuExpandDelay @hide
125890      */
125891
125892     /**
125893      * @cfg {Number} menuHideDelay @hide
125894      */
125895
125896     /**
125897      * @cfg {Boolean} plain @hide
125898      */
125899     plain: true,
125900
125901     /**
125902      * @cfg {String} separatorCls
125903      * The CSS class used by the separator item to show the incised line.
125904      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
125905      */
125906     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
125907
125908     /**
125909      * @cfg {String} text @hide
125910      */
125911     text: '&#160;',
125912
125913     onRender: function(ct, pos) {
125914         var me = this,
125915             sepCls = me.separatorCls;
125916
125917         me.cls += ' ' + sepCls;
125918
125919         me.callParent(arguments);
125920     }
125921 });
125922 /**
125923  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
125924  *
125925  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
125926  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
125927  *
125928  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
125929  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
125930  * in line with the other menu items.
125931  *
125932  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
125933  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
125934  *
125935  *     @example
125936  *     Ext.create('Ext.menu.Menu', {
125937  *         width: 100,
125938  *         height: 100,
125939  *         margin: '0 0 10 0',
125940  *         floating: false,  // usually you want this set to True (default)
125941  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125942  *         items: [{
125943  *             text: 'regular item 1'
125944  *         },{
125945  *             text: 'regular item 2'
125946  *         },{
125947  *             text: 'regular item 3'
125948  *         }]
125949  *     });
125950  *
125951  *     Ext.create('Ext.menu.Menu', {
125952  *         width: 100,
125953  *         height: 100,
125954  *         plain: true,
125955  *         floating: false,  // usually you want this set to True (default)
125956  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125957  *         items: [{
125958  *             text: 'plain item 1'
125959  *         },{
125960  *             text: 'plain item 2'
125961  *         },{
125962  *             text: 'plain item 3'
125963  *         }]
125964  *     });
125965  */
125966 Ext.define('Ext.menu.Menu', {
125967     extend: 'Ext.panel.Panel',
125968     alias: 'widget.menu',
125969     requires: [
125970         'Ext.layout.container.Fit',
125971         'Ext.layout.container.VBox',
125972         'Ext.menu.CheckItem',
125973         'Ext.menu.Item',
125974         'Ext.menu.KeyNav',
125975         'Ext.menu.Manager',
125976         'Ext.menu.Separator'
125977     ],
125978
125979     /**
125980      * @property {Ext.menu.Menu} parentMenu
125981      * The parent Menu of this Menu.
125982      */
125983
125984     /**
125985      * @cfg {Boolean} allowOtherMenus
125986      * True to allow multiple menus to be displayed at the same time.
125987      */
125988     allowOtherMenus: false,
125989
125990     /**
125991      * @cfg {String} ariaRole @hide
125992      */
125993     ariaRole: 'menu',
125994
125995     /**
125996      * @cfg {Boolean} autoRender @hide
125997      * floating is true, so autoRender always happens
125998      */
125999
126000     /**
126001      * @cfg {String} defaultAlign
126002      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
126003      * relative to its element of origin.
126004      */
126005     defaultAlign: 'tl-bl?',
126006
126007     /**
126008      * @cfg {Boolean} floating
126009      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
126010      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
126011      * used as a child item of another {@link Ext.container.Container Container}.
126012      */
126013     floating: true,
126014
126015     /**
126016      * @cfg {Boolean} @hide
126017      * Menus are constrained to the document body by default
126018      */
126019     constrain: true,
126020
126021     /**
126022      * @cfg {Boolean} [hidden=undefined]
126023      * True to initially render the Menu as hidden, requiring to be shown manually.
126024      *
126025      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
126026      */
126027     hidden: true,
126028
126029     hideMode: 'visibility',
126030
126031     /**
126032      * @cfg {Boolean} ignoreParentClicks
126033      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
126034      * so that the submenu is not dismissed when clicking the parent item.
126035      */
126036     ignoreParentClicks: false,
126037
126038     isMenu: true,
126039
126040     /**
126041      * @cfg {String/Object} layout @hide
126042      */
126043
126044     /**
126045      * @cfg {Boolean} showSeparator
126046      * True to show the icon separator.
126047      */
126048     showSeparator : true,
126049
126050     /**
126051      * @cfg {Number} minWidth
126052      * The minimum width of the Menu.
126053      */
126054     minWidth: 120,
126055
126056     /**
126057      * @cfg {Boolean} [plain=false]
126058      * True to remove the incised line down the left side of the menu and to not indent general Component items.
126059      */
126060
126061     initComponent: function() {
126062         var me = this,
126063             prefix = Ext.baseCSSPrefix,
126064             cls = [prefix + 'menu'],
126065             bodyCls = me.bodyCls ? [me.bodyCls] : [];
126066
126067         me.addEvents(
126068             /**
126069              * @event click
126070              * Fires when this menu is clicked
126071              * @param {Ext.menu.Menu} menu The menu which has been clicked
126072              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
126073              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
126074              */
126075             'click',
126076
126077             /**
126078              * @event mouseenter
126079              * Fires when the mouse enters this menu
126080              * @param {Ext.menu.Menu} menu The menu
126081              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126082              */
126083             'mouseenter',
126084
126085             /**
126086              * @event mouseleave
126087              * Fires when the mouse leaves this menu
126088              * @param {Ext.menu.Menu} menu The menu
126089              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126090              */
126091             'mouseleave',
126092
126093             /**
126094              * @event mouseover
126095              * Fires when the mouse is hovering over this menu
126096              * @param {Ext.menu.Menu} menu The menu
126097              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
126098              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126099              */
126100             'mouseover'
126101         );
126102
126103         Ext.menu.Manager.register(me);
126104
126105         // Menu classes
126106         if (me.plain) {
126107             cls.push(prefix + 'menu-plain');
126108         }
126109         me.cls = cls.join(' ');
126110
126111         // Menu body classes
126112         bodyCls.unshift(prefix + 'menu-body');
126113         me.bodyCls = bodyCls.join(' ');
126114
126115         // Internal vbox layout, with scrolling overflow
126116         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
126117         // options if we wish to allow for such configurations on the Menu.
126118         // e.g., scrolling speed, vbox align stretch, etc.
126119         me.layout = {
126120             type: 'vbox',
126121             align: 'stretchmax',
126122             autoSize: true,
126123             clearInnerCtOnLayout: true,
126124             overflowHandler: 'Scroller'
126125         };
126126
126127         // hidden defaults to false if floating is configured as false
126128         if (me.floating === false && me.initialConfig.hidden !== true) {
126129             me.hidden = false;
126130         }
126131
126132         me.callParent(arguments);
126133
126134         me.on('beforeshow', function() {
126135             var hasItems = !!me.items.length;
126136             // FIXME: When a menu has its show cancelled because of no items, it
126137             // gets a visibility: hidden applied to it (instead of the default display: none)
126138             // Not sure why, but we remove this style when we want to show again.
126139             if (hasItems && me.rendered) {
126140                 me.el.setStyle('visibility', null);
126141             }
126142             return hasItems;
126143         });
126144     },
126145
126146     afterRender: function(ct) {
126147         var me = this,
126148             prefix = Ext.baseCSSPrefix,
126149             space = '&#160;';
126150
126151         me.callParent(arguments);
126152
126153         // TODO: Move this to a subTemplate When we support them in the future
126154         if (me.showSeparator) {
126155             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
126156                 cls: prefix + 'menu-icon-separator',
126157                 html: space
126158             });
126159         }
126160
126161         me.focusEl = me.el.createChild({
126162             cls: prefix + 'menu-focus',
126163             tabIndex: '-1',
126164             html: space
126165         });
126166
126167         me.mon(me.el, {
126168             click: me.onClick,
126169             mouseover: me.onMouseOver,
126170             scope: me
126171         });
126172         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
126173
126174         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
126175             me.iconSepEl.setHeight(me.el.getHeight());
126176         }
126177
126178         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
126179     },
126180
126181     afterLayout: function() {
126182         var me = this;
126183         me.callParent(arguments);
126184
126185         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
126186         // floating elements inherit their parent's width, making them the width of
126187         // document.body instead of the width of their contents.
126188         // This includes left/right dock items.
126189         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
126190             var innerCt = me.layout.getRenderTarget(),
126191                 innerCtWidth = 0,
126192                 dis = me.dockedItems,
126193                 l = dis.length,
126194                 i = 0,
126195                 di, clone, newWidth;
126196
126197             innerCtWidth = innerCt.getWidth();
126198
126199             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
126200
126201             // First set the body to the new width
126202             me.body.setWidth(newWidth);
126203
126204             // Now we calculate additional width (docked items) and set the el's width
126205             for (; i < l, di = dis.getAt(i); i++) {
126206                 if (di.dock == 'left' || di.dock == 'right') {
126207                     newWidth += di.getWidth();
126208                 }
126209             }
126210             me.el.setWidth(newWidth);
126211         }
126212     },
126213     
126214     getBubbleTarget: function(){
126215         return this.parentMenu || this.callParent();
126216     },
126217
126218     /**
126219      * Returns whether a menu item can be activated or not.
126220      * @return {Boolean}
126221      */
126222     canActivateItem: function(item) {
126223         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
126224     },
126225
126226     /**
126227      * Deactivates the current active item on the menu, if one exists.
126228      */
126229     deactivateActiveItem: function() {
126230         var me = this;
126231
126232         if (me.activeItem) {
126233             me.activeItem.deactivate();
126234             if (!me.activeItem.activated) {
126235                 delete me.activeItem;
126236             }
126237         }
126238
126239         // only blur if focusedItem is not a filter
126240         if (me.focusedItem && !me.filtered) {
126241             me.focusedItem.blur();
126242             if (!me.focusedItem.$focused) {
126243                 delete me.focusedItem;
126244             }
126245         }
126246     },
126247
126248     clearStretch: function () {
126249         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
126250         // reconsider them unless we clear the dimensions on the el's here:
126251         if (this.rendered) {
126252             this.items.each(function (item) {
126253                 // each menuItem component needs to layout again, so clear its cache
126254                 if (item.componentLayout) {
126255                     delete item.componentLayout.lastComponentSize;
126256                 }
126257                 if (item.el) {
126258                     item.el.setWidth(null);
126259                 }
126260             });
126261         }
126262     },
126263
126264     onAdd: function () {
126265         var me = this;
126266
126267         me.clearStretch();
126268         me.callParent(arguments);
126269
126270         if (Ext.isIE6 || Ext.isIE7) {
126271             // TODO - why does this need to be done (and not ok to do now)?
126272             Ext.Function.defer(me.doComponentLayout, 10, me);
126273         }
126274     },
126275
126276     onRemove: function () {
126277         this.clearStretch();
126278         this.callParent(arguments);
126279
126280     },
126281
126282     redoComponentLayout: function () {
126283         if (this.rendered) {
126284             this.clearStretch();
126285             this.doComponentLayout();
126286         }
126287     },
126288
126289     // inherit docs
126290     getFocusEl: function() {
126291         return this.focusEl;
126292     },
126293
126294     // inherit docs
126295     hide: function() {
126296         this.deactivateActiveItem();
126297         this.callParent(arguments);
126298     },
126299
126300     // private
126301     getItemFromEvent: function(e) {
126302         return this.getChildByElement(e.getTarget());
126303     },
126304
126305     lookupComponent: function(cmp) {
126306         var me = this;
126307
126308         if (Ext.isString(cmp)) {
126309             cmp = me.lookupItemFromString(cmp);
126310         } else if (Ext.isObject(cmp)) {
126311             cmp = me.lookupItemFromObject(cmp);
126312         }
126313
126314         // Apply our minWidth to all of our child components so it's accounted
126315         // for in our VBox layout
126316         cmp.minWidth = cmp.minWidth || me.minWidth;
126317
126318         return cmp;
126319     },
126320
126321     // private
126322     lookupItemFromObject: function(cmp) {
126323         var me = this,
126324             prefix = Ext.baseCSSPrefix,
126325             cls,
126326             intercept;
126327
126328         if (!cmp.isComponent) {
126329             if (!cmp.xtype) {
126330                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
126331             } else {
126332                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
126333             }
126334         }
126335
126336         if (cmp.isMenuItem) {
126337             cmp.parentMenu = me;
126338         }
126339
126340         if (!cmp.isMenuItem && !cmp.dock) {
126341             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
126342             intercept = Ext.Function.createInterceptor;
126343
126344             // Wrap focus/blur to control component focus
126345             cmp.focus = intercept(cmp.focus, function() {
126346                 this.$focused = true;
126347             }, cmp);
126348             cmp.blur = intercept(cmp.blur, function() {
126349                 this.$focused = false;
126350             }, cmp);
126351
126352             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
126353                 cls.push(prefix + 'menu-item-indent');
126354             }
126355
126356             if (cmp.rendered) {
126357                 cmp.el.addCls(cls);
126358             } else {
126359                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
126360             }
126361             cmp.isMenuItem = true;
126362         }
126363         return cmp;
126364     },
126365
126366     // private
126367     lookupItemFromString: function(cmp) {
126368         return (cmp == 'separator' || cmp == '-') ?
126369             Ext.createWidget('menuseparator')
126370             : Ext.createWidget('menuitem', {
126371                 canActivate: false,
126372                 hideOnClick: false,
126373                 plain: true,
126374                 text: cmp
126375             });
126376     },
126377
126378     onClick: function(e) {
126379         var me = this,
126380             item;
126381
126382         if (me.disabled) {
126383             e.stopEvent();
126384             return;
126385         }
126386
126387         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
126388             item = me.getItemFromEvent(e) || me.activeItem;
126389
126390             if (item) {
126391                 if (item.getXTypes().indexOf('menuitem') >= 0) {
126392                     if (!item.menu || !me.ignoreParentClicks) {
126393                         item.onClick(e);
126394                     } else {
126395                         e.stopEvent();
126396                     }
126397                 }
126398             }
126399             me.fireEvent('click', me, item, e);
126400         }
126401     },
126402
126403     onDestroy: function() {
126404         var me = this;
126405
126406         Ext.menu.Manager.unregister(me);
126407         if (me.rendered) {
126408             me.el.un(me.mouseMonitor);
126409             me.keyNav.destroy();
126410             delete me.keyNav;
126411         }
126412         me.callParent(arguments);
126413     },
126414
126415     onMouseLeave: function(e) {
126416         var me = this;
126417
126418         me.deactivateActiveItem();
126419
126420         if (me.disabled) {
126421             return;
126422         }
126423
126424         me.fireEvent('mouseleave', me, e);
126425     },
126426
126427     onMouseOver: function(e) {
126428         var me = this,
126429             fromEl = e.getRelatedTarget(),
126430             mouseEnter = !me.el.contains(fromEl),
126431             item = me.getItemFromEvent(e);
126432
126433         if (mouseEnter && me.parentMenu) {
126434             me.parentMenu.setActiveItem(me.parentItem);
126435             me.parentMenu.mouseMonitor.mouseenter();
126436         }
126437
126438         if (me.disabled) {
126439             return;
126440         }
126441
126442         if (item) {
126443             me.setActiveItem(item);
126444             if (item.activated && item.expandMenu) {
126445                 item.expandMenu();
126446             }
126447         }
126448         if (mouseEnter) {
126449             me.fireEvent('mouseenter', me, e);
126450         }
126451         me.fireEvent('mouseover', me, item, e);
126452     },
126453
126454     setActiveItem: function(item) {
126455         var me = this;
126456
126457         if (item && (item != me.activeItem && item != me.focusedItem)) {
126458             me.deactivateActiveItem();
126459             if (me.canActivateItem(item)) {
126460                 if (item.activate) {
126461                     item.activate();
126462                     if (item.activated) {
126463                         me.activeItem = item;
126464                         me.focusedItem = item;
126465                         me.focus();
126466                     }
126467                 } else {
126468                     item.focus();
126469                     me.focusedItem = item;
126470                 }
126471             }
126472             item.el.scrollIntoView(me.layout.getRenderTarget());
126473         }
126474     },
126475
126476     /**
126477      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
126478      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
126479      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
126480      * Defaults to `{@link #defaultAlign}`.
126481      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
126482      * @return {Ext.menu.Menu} This Menu.
126483      */
126484     showBy: function(cmp, pos, off) {
126485         var me = this,
126486             xy,
126487             region;
126488
126489         if (me.floating && cmp) {
126490             me.layout.autoSize = true;
126491
126492             // show off-screen first so that we can calc position without causing a visual jump
126493             me.doAutoRender();
126494             delete me.needsLayout;
126495
126496             // Component or Element
126497             cmp = cmp.el || cmp;
126498
126499             // Convert absolute to floatParent-relative coordinates if necessary.
126500             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
126501             if (me.floatParent) {
126502                 region = me.floatParent.getTargetEl().getViewRegion();
126503                 xy[0] -= region.x;
126504                 xy[1] -= region.y;
126505             }
126506             me.showAt(xy);
126507         }
126508         return me;
126509     },
126510
126511     doConstrain : function() {
126512         var me = this,
126513             y = me.el.getY(),
126514             max, full,
126515             vector,
126516             returnY = y, normalY, parentEl, scrollTop, viewHeight;
126517
126518         delete me.height;
126519         me.setSize();
126520         full = me.getHeight();
126521         if (me.floating) {
126522             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
126523             parentEl = Ext.fly(me.el.getScopeParent());
126524             scrollTop = parentEl.getScroll().top;
126525             viewHeight = parentEl.getViewSize().height;
126526             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
126527             //of the view.
126528             normalY = y - scrollTop;
126529             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
126530             if (full > viewHeight) {
126531                 max = viewHeight;
126532                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
126533                 returnY = y - normalY;
126534             } else if (max < full) {
126535                 returnY = y - (full - max);
126536                 max = full;
126537             }
126538         }else{
126539             max = me.getHeight();
126540         }
126541         // Always respect maxHeight
126542         if (me.maxHeight){
126543             max = Math.min(me.maxHeight, max);
126544         }
126545         if (full > max && max > 0){
126546             me.layout.autoSize = false;
126547             me.setHeight(max);
126548             if (me.showSeparator){
126549                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
126550             }
126551         }
126552         vector = me.getConstrainVector(me.el.getScopeParent());
126553         if (vector) {
126554             me.setPosition(me.getPosition()[0] + vector[0]);
126555         }
126556         me.el.setY(returnY);
126557     }
126558 });
126559
126560 /**
126561  * A menu containing a Ext.picker.Color Component.
126562  *
126563  * Notes:
126564  *
126565  *   - Although not listed here, the **constructor** for this class accepts all of the
126566  *     configuration options of {@link Ext.picker.Color}.
126567  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
126568  *     applied to the **initialConfig** property of the ColorMenu. Applying
126569  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
126570  *     affect the ColorPicker's configuration.
126571  *
126572  * Example:
126573  *
126574  *     @example
126575  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
126576  *         value: '000000'
126577  *     });
126578  *
126579  *     Ext.create('Ext.menu.Menu', {
126580  *         width: 100,
126581  *         height: 90,
126582  *         floating: false,  // usually you want this set to True (default)
126583  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126584  *         items: [{
126585  *             text: 'choose a color',
126586  *             menu: colorPicker
126587  *         },{
126588  *             iconCls: 'add16',
126589  *             text: 'icon item'
126590  *         },{
126591  *             text: 'regular item'
126592  *         }]
126593  *     });
126594  */
126595  Ext.define('Ext.menu.ColorPicker', {
126596      extend: 'Ext.menu.Menu',
126597
126598      alias: 'widget.colormenu',
126599
126600      requires: [
126601         'Ext.picker.Color'
126602      ],
126603
126604     /**
126605      * @cfg {Boolean} hideOnClick
126606      * False to continue showing the menu after a date is selected.
126607      */
126608     hideOnClick : true,
126609
126610     /**
126611      * @cfg {String} pickerId
126612      * An id to assign to the underlying color picker.
126613      */
126614     pickerId : null,
126615
126616     /**
126617      * @cfg {Number} maxHeight
126618      * @hide
126619      */
126620
126621     /**
126622      * @property {Ext.picker.Color} picker
126623      * The {@link Ext.picker.Color} instance for this ColorMenu
126624      */
126625
126626     /**
126627      * @event click
126628      * @hide
126629      */
126630
126631     /**
126632      * @event itemclick
126633      * @hide
126634      */
126635
126636     initComponent : function(){
126637         var me = this,
126638             cfg = Ext.apply({}, me.initialConfig);
126639
126640         // Ensure we don't get duplicate listeners
126641         delete cfg.listeners;
126642         Ext.apply(me, {
126643             plain: true,
126644             showSeparator: false,
126645             items: Ext.applyIf({
126646                 cls: Ext.baseCSSPrefix + 'menu-color-item',
126647                 id: me.pickerId,
126648                 xtype: 'colorpicker'
126649             }, cfg)
126650         });
126651
126652         me.callParent(arguments);
126653
126654         me.picker = me.down('colorpicker');
126655
126656         /**
126657          * @event select
126658          * @alias Ext.picker.Color#select
126659          */
126660         me.relayEvents(me.picker, ['select']);
126661
126662         if (me.hideOnClick) {
126663             me.on('select', me.hidePickerOnSelect, me);
126664         }
126665     },
126666
126667     /**
126668      * Hides picker on select if hideOnClick is true
126669      * @private
126670      */
126671     hidePickerOnSelect: function() {
126672         Ext.menu.Manager.hideAll();
126673     }
126674  });
126675 /**
126676  * A menu containing an Ext.picker.Date Component.
126677  *
126678  * Notes:
126679  *
126680  * - Although not listed here, the **constructor** for this class accepts all of the
126681  *   configuration options of **{@link Ext.picker.Date}**.
126682  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
126683  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
126684  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
126685  *
126686  * Example:
126687  *
126688  *     @example
126689  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
126690  *         handler: function(dp, date){
126691  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
126692  *         }
126693  *     });
126694  *
126695  *     Ext.create('Ext.menu.Menu', {
126696  *         width: 100,
126697  *         height: 90,
126698  *         floating: false,  // usually you want this set to True (default)
126699  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126700  *         items: [{
126701  *             text: 'choose a date',
126702  *             menu: dateMenu
126703  *         },{
126704  *             iconCls: 'add16',
126705  *             text: 'icon item'
126706  *         },{
126707  *             text: 'regular item'
126708  *         }]
126709  *     });
126710  */
126711  Ext.define('Ext.menu.DatePicker', {
126712      extend: 'Ext.menu.Menu',
126713
126714      alias: 'widget.datemenu',
126715
126716      requires: [
126717         'Ext.picker.Date'
126718      ],
126719
126720     /**
126721      * @cfg {Boolean} hideOnClick
126722      * False to continue showing the menu after a date is selected.
126723      */
126724     hideOnClick : true,
126725
126726     /**
126727      * @cfg {String} pickerId
126728      * An id to assign to the underlying date picker.
126729      */
126730     pickerId : null,
126731
126732     /**
126733      * @cfg {Number} maxHeight
126734      * @hide
126735      */
126736
126737     /**
126738      * @property {Ext.picker.Date} picker
126739      * The {@link Ext.picker.Date} instance for this DateMenu
126740      */
126741
126742     /**
126743      * @event click
126744      * @hide
126745      */
126746
126747     /**
126748      * @event itemclick
126749      * @hide
126750      */
126751
126752     initComponent : function(){
126753         var me = this;
126754
126755         Ext.apply(me, {
126756             showSeparator: false,
126757             plain: true,
126758             border: false,
126759             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
126760             items: Ext.applyIf({
126761                 cls: Ext.baseCSSPrefix + 'menu-date-item',
126762                 id: me.pickerId,
126763                 xtype: 'datepicker'
126764             }, me.initialConfig)
126765         });
126766
126767         me.callParent(arguments);
126768
126769         me.picker = me.down('datepicker');
126770         /**
126771          * @event select
126772          * @alias Ext.picker.Date#select
126773          */
126774         me.relayEvents(me.picker, ['select']);
126775
126776         if (me.hideOnClick) {
126777             me.on('select', me.hidePickerOnSelect, me);
126778         }
126779     },
126780
126781     hidePickerOnSelect: function() {
126782         Ext.menu.Manager.hideAll();
126783     }
126784  });
126785 /**
126786  * This class is used to display small visual icons in the header of a panel. There are a set of
126787  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
126788  * can be used to provide a function that will respond to any click events. In general, this class
126789  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
126790  * configuration on the Panel itself.
126791  *
126792  *     @example
126793  *     Ext.create('Ext.panel.Panel', {
126794  *         width: 200,
126795  *         height: 200,
126796  *         renderTo: document.body,
126797  *         title: 'A Panel',
126798  *         tools: [{
126799  *             type: 'help',
126800  *             handler: function(){
126801  *                 // show help here
126802  *             }
126803  *         }, {
126804  *             itemId: 'refresh',
126805  *             type: 'refresh',
126806  *             hidden: true,
126807  *             handler: function(){
126808  *                 // do refresh
126809  *             }
126810  *         }, {
126811  *             type: 'search',
126812  *             handler: function(event, target, owner, tool){
126813  *                 // do search
126814  *                 owner.child('#refresh').show();
126815  *             }
126816  *         }]
126817  *     });
126818  */
126819 Ext.define('Ext.panel.Tool', {
126820     extend: 'Ext.Component',
126821     requires: ['Ext.tip.QuickTipManager'],
126822     alias: 'widget.tool',
126823
126824     baseCls: Ext.baseCSSPrefix + 'tool',
126825     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
126826     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
126827     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
126828     ariaRole: 'button',
126829     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
126830
126831     /**
126832      * @cfg {Function} handler
126833      * A function to execute when the tool is clicked. Arguments passed are:
126834      *
126835      * - **event** : Ext.EventObject - The click event.
126836      * - **toolEl** : Ext.Element - The tool Element.
126837      * - **owner** : Ext.panel.Header - The host panel header.
126838      * - **tool** : Ext.panel.Tool - The tool object
126839      */
126840
126841     /**
126842      * @cfg {Object} scope
126843      * The scope to execute the {@link #handler} function. Defaults to the tool.
126844      */
126845
126846     /**
126847      * @cfg {String} type
126848      * The type of tool to render. The following types are available:
126849      *
126850      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
126851      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
126852      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
126853      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
126854      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
126855      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
126856      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
126857      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
126858      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
126859      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
126860      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
126861      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
126862      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
126863      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
126864      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
126865      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
126866      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
126867      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
126868      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
126869      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
126870      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
126871      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
126872      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
126873      */
126874
126875     /**
126876      * @cfg {String/Object} tooltip
126877      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
126878      * object
126879      */
126880
126881      /**
126882      * @cfg {String} tooltipType
126883      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
126884      */
126885     tooltipType: 'qtip',
126886
126887     /**
126888      * @cfg {Boolean} stopEvent
126889      * Specify as false to allow click event to propagate.
126890      */
126891     stopEvent: true,
126892
126893     initComponent: function() {
126894         var me = this;
126895         me.addEvents(
126896             /**
126897              * @event click
126898              * Fires when the tool is clicked
126899              * @param {Ext.panel.Tool} this
126900              * @param {Ext.EventObject} e The event object
126901              */
126902             'click'
126903         );
126904
126905
126906         me.type = me.type || me.id;
126907
126908         Ext.applyIf(me.renderData, {
126909             baseCls: me.baseCls,
126910             blank: Ext.BLANK_IMAGE_URL,
126911             type: me.type
126912         });
126913
126914         me.addChildEls('toolEl');
126915
126916         // alias qtip, should use tooltip since it's what we have in the docs
126917         me.tooltip = me.tooltip || me.qtip;
126918         me.callParent();
126919     },
126920
126921     // inherit docs
126922     afterRender: function() {
126923         var me = this,
126924             attr;
126925
126926         me.callParent(arguments);
126927         if (me.tooltip) {
126928             if (Ext.isObject(me.tooltip)) {
126929                 Ext.tip.QuickTipManager.register(Ext.apply({
126930                     target: me.id
126931                 }, me.tooltip));
126932             }
126933             else {
126934                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
126935                 me.toolEl.dom.setAttribute(attr, me.tooltip);
126936             }
126937         }
126938
126939         me.mon(me.toolEl, {
126940             click: me.onClick,
126941             mousedown: me.onMouseDown,
126942             mouseover: me.onMouseOver,
126943             mouseout: me.onMouseOut,
126944             scope: me
126945         });
126946     },
126947
126948     /**
126949      * Sets the type of the tool. Allows the icon to be changed.
126950      * @param {String} type The new type. See the {@link #type} config.
126951      * @return {Ext.panel.Tool} this
126952      */
126953     setType: function(type) {
126954         var me = this;
126955
126956         me.type = type;
126957         if (me.rendered) {
126958             me.toolEl.dom.className = me.baseCls + '-' + type;
126959         }
126960         return me;
126961     },
126962
126963     /**
126964      * Binds this tool to a component.
126965      * @private
126966      * @param {Ext.Component} component The component
126967      */
126968     bindTo: function(component) {
126969         this.owner = component;
126970     },
126971
126972     /**
126973      * Called when the tool element is clicked
126974      * @private
126975      * @param {Ext.EventObject} e
126976      * @param {HTMLElement} target The target element
126977      */
126978     onClick: function(e, target) {
126979         var me = this,
126980             owner;
126981
126982         if (me.disabled) {
126983             return false;
126984         }
126985         owner = me.owner || me.ownerCt;
126986
126987         //remove the pressed + over class
126988         me.el.removeCls(me.toolPressedCls);
126989         me.el.removeCls(me.toolOverCls);
126990
126991         if (me.stopEvent !== false) {
126992             e.stopEvent();
126993         }
126994
126995         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
126996         me.fireEvent('click', me, e);
126997         return true;
126998     },
126999
127000     // inherit docs
127001     onDestroy: function(){
127002         if (Ext.isObject(this.tooltip)) {
127003             Ext.tip.QuickTipManager.unregister(this.id);
127004         }
127005         this.callParent();
127006     },
127007
127008     /**
127009      * Called when the user presses their mouse button down on a tool
127010      * Adds the press class ({@link #toolPressedCls})
127011      * @private
127012      */
127013     onMouseDown: function() {
127014         if (this.disabled) {
127015             return false;
127016         }
127017
127018         this.el.addCls(this.toolPressedCls);
127019     },
127020
127021     /**
127022      * Called when the user rolls over a tool
127023      * Adds the over class ({@link #toolOverCls})
127024      * @private
127025      */
127026     onMouseOver: function() {
127027         if (this.disabled) {
127028             return false;
127029         }
127030         this.el.addCls(this.toolOverCls);
127031     },
127032
127033     /**
127034      * Called when the user rolls out from a tool.
127035      * Removes the over class ({@link #toolOverCls})
127036      * @private
127037      */
127038     onMouseOut: function() {
127039         this.el.removeCls(this.toolOverCls);
127040     }
127041 });
127042 /**
127043  * @class Ext.resizer.Handle
127044  * @extends Ext.Component
127045  *
127046  * Provides a handle for 9-point resizing of Elements or Components.
127047  */
127048 Ext.define('Ext.resizer.Handle', {
127049     extend: 'Ext.Component',
127050     handleCls: '',
127051     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
127052     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
127053     // which will be passed in as a region configuration.
127054     region: '',
127055
127056     onRender: function() {
127057         this.addCls(
127058             this.baseHandleCls,
127059             this.baseHandleCls + '-' + this.region,
127060             this.handleCls
127061         );
127062         this.callParent(arguments);
127063         this.el.unselectable();
127064     }
127065 });
127066
127067 /**
127068  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
127069  * (or component's element) and positioned absolute.
127070  *
127071  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
127072  * The original element can be accessed through the originalTarget property.
127073  *
127074  * Here is the list of valid resize handles:
127075  *
127076  *     Value   Description
127077  *     ------  -------------------
127078  *      'n'     north
127079  *      's'     south
127080  *      'e'     east
127081  *      'w'     west
127082  *      'nw'    northwest
127083  *      'sw'    southwest
127084  *      'se'    southeast
127085  *      'ne'    northeast
127086  *      'all'   all
127087  *
127088  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
127089  *
127090  * Here's an example showing the creation of a typical Resizer:
127091  *
127092  *     Ext.create('Ext.resizer.Resizer', {
127093  *         el: 'elToResize',
127094  *         handles: 'all',
127095  *         minWidth: 200,
127096  *         minHeight: 100,
127097  *         maxWidth: 500,
127098  *         maxHeight: 400,
127099  *         pinned: true
127100  *     });
127101  */
127102 Ext.define('Ext.resizer.Resizer', {
127103     mixins: {
127104         observable: 'Ext.util.Observable'
127105     },
127106     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
127107
127108     alternateClassName: 'Ext.Resizable',
127109
127110     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
127111     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
127112     overCls:   Ext.baseCSSPrefix + 'resizable-over',
127113     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
127114
127115     /**
127116      * @cfg {Boolean} dynamic
127117      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
127118      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
127119      * configured as {@link Ext.Component#resizable}.
127120      *
127121      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
127122      * updated on mouseup.
127123      */
127124     dynamic: true,
127125
127126     /**
127127      * @cfg {String} handles
127128      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
127129      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
127130      * of `'n s e w ne nw se sw'`.
127131      */
127132     handles: 's e se',
127133
127134     /**
127135      * @cfg {Number} height
127136      * Optional. The height to set target to in pixels
127137      */
127138     height : null,
127139
127140     /**
127141      * @cfg {Number} width
127142      * Optional. The width to set the target to in pixels
127143      */
127144     width : null,
127145
127146     /**
127147      * @cfg {Number} heightIncrement
127148      * The increment to snap the height resize in pixels.
127149      */
127150     heightIncrement : 0,
127151
127152     /**
127153      * @cfg {Number} widthIncrement
127154      * The increment to snap the width resize in pixels.
127155      */
127156     widthIncrement : 0,
127157
127158     /**
127159      * @cfg {Number} minHeight
127160      * The minimum height for the element
127161      */
127162     minHeight : 20,
127163
127164     /**
127165      * @cfg {Number} minWidth
127166      * The minimum width for the element
127167      */
127168     minWidth : 20,
127169
127170     /**
127171      * @cfg {Number} maxHeight
127172      * The maximum height for the element
127173      */
127174     maxHeight : 10000,
127175
127176     /**
127177      * @cfg {Number} maxWidth
127178      * The maximum width for the element
127179      */
127180     maxWidth : 10000,
127181
127182     /**
127183      * @cfg {Boolean} pinned
127184      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
127185      */
127186     pinned: false,
127187
127188     /**
127189      * @cfg {Boolean} preserveRatio
127190      * True to preserve the original ratio between height and width during resize
127191      */
127192     preserveRatio: false,
127193
127194     /**
127195      * @cfg {Boolean} transparent
127196      * True for transparent handles. This is only applied at config time.
127197      */
127198     transparent: false,
127199
127200     /**
127201      * @cfg {Ext.Element/Ext.util.Region} constrainTo
127202      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
127203      */
127204
127205     possiblePositions: {
127206         n:  'north',
127207         s:  'south',
127208         e:  'east',
127209         w:  'west',
127210         se: 'southeast',
127211         sw: 'southwest',
127212         nw: 'northwest',
127213         ne: 'northeast'
127214     },
127215
127216     /**
127217      * @cfg {Ext.Element/Ext.Component} target
127218      * The Element or Component to resize.
127219      */
127220
127221     /**
127222      * @property {Ext.Element} el
127223      * Outer element for resizing behavior.
127224      */
127225
127226     constructor: function(config) {
127227         var me = this,
127228             target,
127229             tag,
127230             handles = me.handles,
127231             handleCls,
127232             possibles,
127233             len,
127234             i = 0,
127235             pos;
127236
127237         this.addEvents(
127238             /**
127239              * @event beforeresize
127240              * Fired before resize is allowed. Return false to cancel resize.
127241              * @param {Ext.resizer.Resizer} this
127242              * @param {Number} width The start width
127243              * @param {Number} height The start height
127244              * @param {Ext.EventObject} e The mousedown event
127245              */
127246             'beforeresize',
127247             /**
127248              * @event resizedrag
127249              * Fires during resizing. Return false to cancel resize.
127250              * @param {Ext.resizer.Resizer} this
127251              * @param {Number} width The new width
127252              * @param {Number} height The new height
127253              * @param {Ext.EventObject} e The mousedown event
127254              */
127255             'resizedrag',
127256             /**
127257              * @event resize
127258              * Fired after a resize.
127259              * @param {Ext.resizer.Resizer} this
127260              * @param {Number} width The new width
127261              * @param {Number} height The new height
127262              * @param {Ext.EventObject} e The mouseup event
127263              */
127264             'resize'
127265         );
127266
127267         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
127268             target = config;
127269             config = arguments[1] || {};
127270             config.target = target;
127271         }
127272         // will apply config to this
127273         me.mixins.observable.constructor.call(me, config);
127274
127275         // If target is a Component, ensure that we pull the element out.
127276         // Resizer must examine the underlying Element.
127277         target = me.target;
127278         if (target) {
127279             if (target.isComponent) {
127280                 me.el = target.getEl();
127281                 if (target.minWidth) {
127282                     me.minWidth = target.minWidth;
127283                 }
127284                 if (target.minHeight) {
127285                     me.minHeight = target.minHeight;
127286                 }
127287                 if (target.maxWidth) {
127288                     me.maxWidth = target.maxWidth;
127289                 }
127290                 if (target.maxHeight) {
127291                     me.maxHeight = target.maxHeight;
127292                 }
127293                 if (target.floating) {
127294                     if (!this.hasOwnProperty('handles')) {
127295                         this.handles = 'n ne e se s sw w nw';
127296                     }
127297                 }
127298             } else {
127299                 me.el = me.target = Ext.get(target);
127300             }
127301         }
127302         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
127303         else {
127304             me.target = me.el = Ext.get(me.el);
127305         }
127306
127307         // Tags like textarea and img cannot
127308         // have children and therefore must
127309         // be wrapped
127310         tag = me.el.dom.tagName;
127311         if (tag == 'TEXTAREA' || tag == 'IMG') {
127312             /**
127313              * @property {Ext.Element/Ext.Component} originalTarget
127314              * Reference to the original resize target if the element of the original resize target was an IMG or a
127315              * TEXTAREA which must be wrapped in a DIV.
127316              */
127317             me.originalTarget = me.target;
127318             me.target = me.el = me.el.wrap({
127319                 cls: me.wrapCls,
127320                 id: me.el.id + '-rzwrap'
127321             });
127322
127323             // Transfer originalTarget's positioning/sizing
127324             me.el.setPositioning(me.originalTarget.getPositioning());
127325             me.originalTarget.clearPositioning();
127326             var box = me.originalTarget.getBox();
127327             me.el.setBox(box);
127328         }
127329
127330         // Position the element, this enables us to absolute position
127331         // the handles within this.el
127332         me.el.position();
127333         if (me.pinned) {
127334             me.el.addCls(me.pinnedCls);
127335         }
127336
127337         /**
127338          * @property {Ext.resizer.ResizeTracker} resizeTracker
127339          */
127340         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
127341             disabled: me.disabled,
127342             target: me.target,
127343             constrainTo: me.constrainTo,
127344             overCls: me.overCls,
127345             throttle: me.throttle,
127346             originalTarget: me.originalTarget,
127347             delegate: '.' + me.handleCls,
127348             dynamic: me.dynamic,
127349             preserveRatio: me.preserveRatio,
127350             heightIncrement: me.heightIncrement,
127351             widthIncrement: me.widthIncrement,
127352             minHeight: me.minHeight,
127353             maxHeight: me.maxHeight,
127354             minWidth: me.minWidth,
127355             maxWidth: me.maxWidth
127356         });
127357
127358         // Relay the ResizeTracker's superclass events as our own resize events
127359         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
127360         me.resizeTracker.on('drag', me.onResize, me);
127361         me.resizeTracker.on('dragend', me.onResizeEnd, me);
127362
127363         if (me.handles == 'all') {
127364             me.handles = 'n s e w ne nw se sw';
127365         }
127366
127367         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
127368         possibles = me.possiblePositions;
127369         len = handles.length;
127370         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
127371
127372         for(; i < len; i++){
127373             // if specified and possible, create
127374             if (handles[i] && possibles[handles[i]]) {
127375                 pos = possibles[handles[i]];
127376                 // store a reference in this.east, this.west, etc
127377
127378                 me[pos] = Ext.create('Ext.Component', {
127379                     owner: this,
127380                     region: pos,
127381                     cls: handleCls + pos,
127382                     renderTo: me.el
127383                 });
127384                 me[pos].el.unselectable();
127385                 if (me.transparent) {
127386                     me[pos].el.setOpacity(0);
127387                 }
127388             }
127389         }
127390
127391         // Constrain within configured maxima
127392         if (Ext.isNumber(me.width)) {
127393             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
127394         }
127395         if (Ext.isNumber(me.height)) {
127396             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
127397         }
127398
127399         // Size the element
127400         if (me.width != null || me.height != null) {
127401             if (me.originalTarget) {
127402                 me.originalTarget.setWidth(me.width);
127403                 me.originalTarget.setHeight(me.height);
127404             }
127405             me.resizeTo(me.width, me.height);
127406         }
127407
127408         me.forceHandlesHeight();
127409     },
127410
127411     disable: function() {
127412         this.resizeTracker.disable();
127413     },
127414
127415     enable: function() {
127416         this.resizeTracker.enable();
127417     },
127418
127419     /**
127420      * @private Relay the Tracker's mousedown event as beforeresize
127421      * @param tracker The Resizer
127422      * @param e The Event
127423      */
127424     onBeforeResize: function(tracker, e) {
127425         var b = this.target.getBox();
127426         return this.fireEvent('beforeresize', this, b.width, b.height, e);
127427     },
127428
127429     /**
127430      * @private Relay the Tracker's drag event as resizedrag
127431      * @param tracker The Resizer
127432      * @param e The Event
127433      */
127434     onResize: function(tracker, e) {
127435         var me = this,
127436             b = me.target.getBox();
127437         me.forceHandlesHeight();
127438         return me.fireEvent('resizedrag', me, b.width, b.height, e);
127439     },
127440
127441     /**
127442      * @private Relay the Tracker's dragend event as resize
127443      * @param tracker The Resizer
127444      * @param e The Event
127445      */
127446     onResizeEnd: function(tracker, e) {
127447         var me = this,
127448             b = me.target.getBox();
127449         me.forceHandlesHeight();
127450         return me.fireEvent('resize', me, b.width, b.height, e);
127451     },
127452
127453     /**
127454      * Perform a manual resize and fires the 'resize' event.
127455      * @param {Number} width
127456      * @param {Number} height
127457      */
127458     resizeTo : function(width, height){
127459         this.target.setSize(width, height);
127460         this.fireEvent('resize', this, width, height, null);
127461     },
127462
127463     /**
127464      * Returns the element that was configured with the el or target config property. If a component was configured with
127465      * the target property then this will return the element of this component.
127466      *
127467      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127468      * nodes. The original element can be accessed through the originalTarget property.
127469      * @return {Ext.Element} element
127470      */
127471     getEl : function() {
127472         return this.el;
127473     },
127474
127475     /**
127476      * Returns the element or component that was configured with the target config property.
127477      *
127478      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127479      * nodes. The original element can be accessed through the originalTarget property.
127480      * @return {Ext.Element/Ext.Component}
127481      */
127482     getTarget: function() {
127483         return this.target;
127484     },
127485
127486     destroy: function() {
127487         var h;
127488         for (var i = 0, l = this.handles.length; i < l; i++) {
127489             h = this[this.possiblePositions[this.handles[i]]];
127490             delete h.owner;
127491             Ext.destroy(h);
127492         }
127493     },
127494
127495     /**
127496      * @private
127497      * Fix IE6 handle height issue.
127498      */
127499     forceHandlesHeight : function() {
127500         var me = this,
127501             handle;
127502         if (Ext.isIE6) {
127503             handle = me.east;
127504             if (handle) {
127505                 handle.setHeight(me.el.getHeight());
127506             }
127507             handle = me.west;
127508             if (handle) {
127509                 handle.setHeight(me.el.getHeight());
127510             }
127511             me.el.repaint();
127512         }
127513     }
127514 });
127515
127516 /**
127517  * @class Ext.resizer.ResizeTracker
127518  * @extends Ext.dd.DragTracker
127519  * Private utility class for Ext.resizer.Resizer.
127520  * @private
127521  */
127522 Ext.define('Ext.resizer.ResizeTracker', {
127523     extend: 'Ext.dd.DragTracker',
127524     dynamic: true,
127525     preserveRatio: false,
127526
127527     // Default to no constraint
127528     constrainTo: null,
127529     
127530     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
127531
127532     constructor: function(config) {
127533         var me = this;
127534
127535         if (!config.el) {
127536             if (config.target.isComponent) {
127537                 me.el = config.target.getEl();
127538             } else {
127539                 me.el = config.target;
127540             }
127541         }
127542         this.callParent(arguments);
127543
127544         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
127545         if (me.preserveRatio && me.minWidth && me.minHeight) {
127546             var widthRatio = me.minWidth / me.el.getWidth(),
127547                 heightRatio = me.minHeight / me.el.getHeight();
127548
127549             // largest ratio of minimum:size must be preserved.
127550             // So if a 400x200 pixel image has
127551             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
127552             if (heightRatio > widthRatio) {
127553                 me.minWidth = me.el.getWidth() * heightRatio;
127554             } else {
127555                 me.minHeight = me.el.getHeight() * widthRatio;
127556             }
127557         }
127558
127559         // If configured as throttled, create an instance version of resize which calls
127560         // a throttled function to perform the resize operation.
127561         if (me.throttle) {
127562             var throttledResizeFn = Ext.Function.createThrottled(function() {
127563                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127564                 }, me.throttle);
127565
127566             me.resize = function(box, direction, atEnd) {
127567                 if (atEnd) {
127568                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127569                 } else {
127570                     throttledResizeFn.apply(null, arguments);
127571                 }
127572             };
127573         }
127574     },
127575
127576     onBeforeStart: function(e) {
127577         // record the startBox
127578         this.startBox = this.el.getBox();
127579     },
127580
127581     /**
127582      * @private
127583      * Returns the object that will be resized on every mousemove event.
127584      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
127585      */
127586     getDynamicTarget: function() {
127587         var me = this,
127588             target = me.target;
127589             
127590         if (me.dynamic) {
127591             return target;
127592         } else if (!me.proxy) {
127593             me.proxy = me.createProxy(target);
127594         }
127595         me.proxy.show();
127596         return me.proxy;
127597     },
127598     
127599     /**
127600      * Create a proxy for this resizer
127601      * @param {Ext.Component/Ext.Element} target The target
127602      * @return {Ext.Element} A proxy element
127603      */
127604     createProxy: function(target){
127605         var proxy,
127606             cls = this.proxyCls,
127607             renderTo;
127608             
127609         if (target.isComponent) {
127610             proxy = target.getProxy().addCls(cls);
127611         } else {
127612             renderTo = Ext.getBody();
127613             if (Ext.scopeResetCSS) {
127614                 renderTo = Ext.getBody().createChild({
127615                     cls: Ext.baseCSSPrefix + 'reset'
127616                 });
127617             }
127618             proxy = target.createProxy({
127619                 tag: 'div',
127620                 cls: cls,
127621                 id: target.id + '-rzproxy'
127622             }, renderTo);
127623         }
127624         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
127625         return proxy;
127626     },
127627
127628     onStart: function(e) {
127629         // returns the Ext.ResizeHandle that the user started dragging
127630         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
127631
127632         // If we are using a proxy, ensure it is sized.
127633         if (!this.dynamic) {
127634             this.resize(this.startBox, {
127635                 horizontal: 'none',
127636                 vertical: 'none'
127637             });
127638         }
127639     },
127640
127641     onDrag: function(e) {
127642         // dynamic resizing, update dimensions during resize
127643         if (this.dynamic || this.proxy) {
127644             this.updateDimensions(e);
127645         }
127646     },
127647
127648     updateDimensions: function(e, atEnd) {
127649         var me = this,
127650             region = me.activeResizeHandle.region,
127651             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
127652             box = me.startBox,
127653             ratio,
127654             widthAdjust = 0,
127655             heightAdjust = 0,
127656             snappedWidth,
127657             snappedHeight,
127658             adjustX = 0,
127659             adjustY = 0,
127660             dragRatio,
127661             horizDir = offset[0] < 0 ? 'right' : 'left',
127662             vertDir = offset[1] < 0 ? 'down' : 'up',
127663             oppositeCorner,
127664             axis; // 1 = x, 2 = y, 3 = x and y.
127665
127666         switch (region) {
127667             case 'south':
127668                 heightAdjust = offset[1];
127669                 axis = 2;
127670                 break;
127671             case 'north':
127672                 heightAdjust = -offset[1];
127673                 adjustY = -heightAdjust;
127674                 axis = 2;
127675                 break;
127676             case 'east':
127677                 widthAdjust = offset[0];
127678                 axis = 1;
127679                 break;
127680             case 'west':
127681                 widthAdjust = -offset[0];
127682                 adjustX = -widthAdjust;
127683                 axis = 1;
127684                 break;
127685             case 'northeast':
127686                 heightAdjust = -offset[1];
127687                 adjustY = -heightAdjust;
127688                 widthAdjust = offset[0];
127689                 oppositeCorner = [box.x, box.y + box.height];
127690                 axis = 3;
127691                 break;
127692             case 'southeast':
127693                 heightAdjust = offset[1];
127694                 widthAdjust = offset[0];
127695                 oppositeCorner = [box.x, box.y];
127696                 axis = 3;
127697                 break;
127698             case 'southwest':
127699                 widthAdjust = -offset[0];
127700                 adjustX = -widthAdjust;
127701                 heightAdjust = offset[1];
127702                 oppositeCorner = [box.x + box.width, box.y];
127703                 axis = 3;
127704                 break;
127705             case 'northwest':
127706                 heightAdjust = -offset[1];
127707                 adjustY = -heightAdjust;
127708                 widthAdjust = -offset[0];
127709                 adjustX = -widthAdjust;
127710                 oppositeCorner = [box.x + box.width, box.y + box.height];
127711                 axis = 3;
127712                 break;
127713         }
127714
127715         var newBox = {
127716             width: box.width + widthAdjust,
127717             height: box.height + heightAdjust,
127718             x: box.x + adjustX,
127719             y: box.y + adjustY
127720         };
127721
127722         // Snap value between stops according to configured increments
127723         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
127724         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
127725         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
127726             switch (region) {
127727                 case 'northeast':
127728                     newBox.y -= snappedHeight - newBox.height;
127729                     break;
127730                 case 'north':
127731                     newBox.y -= snappedHeight - newBox.height;
127732                     break;
127733                 case 'southwest':
127734                     newBox.x -= snappedWidth - newBox.width;
127735                     break;
127736                 case 'west':
127737                     newBox.x -= snappedWidth - newBox.width;
127738                     break;
127739                 case 'northwest':
127740                     newBox.x -= snappedWidth - newBox.width;
127741                     newBox.y -= snappedHeight - newBox.height;
127742             }
127743             newBox.width = snappedWidth;
127744             newBox.height = snappedHeight;
127745         }
127746
127747         // out of bounds
127748         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
127749             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
127750
127751             // Re-adjust the X position if we were dragging the west side
127752             if (adjustX) {
127753                 newBox.x = box.x + (box.width - newBox.width);
127754             }
127755         } else {
127756             me.lastX = newBox.x;
127757         }
127758         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
127759             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
127760
127761             // Re-adjust the Y position if we were dragging the north side
127762             if (adjustY) {
127763                 newBox.y = box.y + (box.height - newBox.height);
127764             }
127765         } else {
127766             me.lastY = newBox.y;
127767         }
127768
127769         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
127770         if (me.preserveRatio || e.shiftKey) {
127771             var newHeight,
127772                 newWidth;
127773
127774             ratio = me.startBox.width / me.startBox.height;
127775
127776             // Calculate aspect ratio constrained values.
127777             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
127778             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
127779
127780             // X axis: width-only change, height must obey
127781             if (axis == 1) {
127782                 newBox.height = newHeight;
127783             }
127784
127785             // Y axis: height-only change, width must obey
127786             else if (axis == 2) {
127787                 newBox.width = newWidth;
127788             }
127789
127790             // Corner drag.
127791             else {
127792                 // Drag ratio is the ratio of the mouse point from the opposite corner.
127793                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
127794                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
127795
127796                 // If drag ratio > aspect ratio then width is dominant and height must obey
127797                 if (dragRatio > ratio) {
127798                     newBox.height = newHeight;
127799                 } else {
127800                     newBox.width = newWidth;
127801                 }
127802
127803                 // Handle dragging start coordinates
127804                 if (region == 'northeast') {
127805                     newBox.y = box.y - (newBox.height - box.height);
127806                 } else if (region == 'northwest') {
127807                     newBox.y = box.y - (newBox.height - box.height);
127808                     newBox.x = box.x - (newBox.width - box.width);
127809                 } else if (region == 'southwest') {
127810                     newBox.x = box.x - (newBox.width - box.width);
127811                 }
127812             }
127813         }
127814
127815         if (heightAdjust === 0) {
127816             vertDir = 'none';
127817         }
127818         if (widthAdjust === 0) {
127819             horizDir = 'none';
127820         }
127821         me.resize(newBox, {
127822             horizontal: horizDir,
127823             vertical: vertDir
127824         }, atEnd);
127825     },
127826
127827     getResizeTarget: function(atEnd) {
127828         return atEnd ? this.target : this.getDynamicTarget();
127829     },
127830
127831     resize: function(box, direction, atEnd) {
127832         var target = this.getResizeTarget(atEnd);
127833         if (target.isComponent) {
127834             if (target.floating) {
127835                 target.setPagePosition(box.x, box.y);
127836             }
127837             target.setSize(box.width, box.height);
127838         } else {
127839             target.setBox(box);
127840             // update the originalTarget if this was wrapped.
127841             if (this.originalTarget) {
127842                 this.originalTarget.setBox(box);
127843             }
127844         }
127845     },
127846
127847     onEnd: function(e) {
127848         this.updateDimensions(e, true);
127849         if (this.proxy) {
127850             this.proxy.hide();
127851         }
127852     }
127853 });
127854
127855 /**
127856  * @class Ext.resizer.SplitterTracker
127857  * @extends Ext.dd.DragTracker
127858  * Private utility class for Ext.Splitter.
127859  * @private
127860  */
127861 Ext.define('Ext.resizer.SplitterTracker', {
127862     extend: 'Ext.dd.DragTracker',
127863     requires: ['Ext.util.Region'],
127864     enabled: true,
127865     
127866     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
127867
127868     getPrevCmp: function() {
127869         var splitter = this.getSplitter();
127870         return splitter.previousSibling();
127871     },
127872
127873     getNextCmp: function() {
127874         var splitter = this.getSplitter();
127875         return splitter.nextSibling();
127876     },
127877
127878     // ensure the tracker is enabled, store boxes of previous and next
127879     // components and calculate the constrain region
127880     onBeforeStart: function(e) {
127881         var me = this,
127882             prevCmp = me.getPrevCmp(),
127883             nextCmp = me.getNextCmp(),
127884             collapseEl = me.getSplitter().collapseEl,
127885             overlay;
127886             
127887         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
127888             return false;
127889         }
127890
127891         // SplitterTracker is disabled if any of its adjacents are collapsed.
127892         if (nextCmp.collapsed || prevCmp.collapsed) {
127893             return false;
127894         }
127895         
127896         overlay = me.overlay =  Ext.getBody().createChild({
127897             cls: me.overlayCls, 
127898             html: '&#160;'
127899         });
127900         overlay.unselectable();
127901         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
127902         overlay.show();
127903         
127904         // store boxes of previous and next
127905         me.prevBox  = prevCmp.getEl().getBox();
127906         me.nextBox  = nextCmp.getEl().getBox();
127907         me.constrainTo = me.calculateConstrainRegion();
127908     },
127909
127910     // We move the splitter el. Add the proxy class.
127911     onStart: function(e) {
127912         var splitter = this.getSplitter();
127913         splitter.addCls(splitter.baseCls + '-active');
127914     },
127915
127916     // calculate the constrain Region in which the splitter el may be moved.
127917     calculateConstrainRegion: function() {
127918         var me         = this,
127919             splitter   = me.getSplitter(),
127920             splitWidth = splitter.getWidth(),
127921             defaultMin = splitter.defaultSplitMin,
127922             orient     = splitter.orientation,
127923             prevBox    = me.prevBox,
127924             prevCmp    = me.getPrevCmp(),
127925             nextBox    = me.nextBox,
127926             nextCmp    = me.getNextCmp(),
127927             // prev and nextConstrainRegions are the maximumBoxes minus the
127928             // minimumBoxes. The result is always the intersection
127929             // of these two boxes.
127930             prevConstrainRegion, nextConstrainRegion;
127931
127932         // vertical splitters, so resizing left to right
127933         if (orient === 'vertical') {
127934
127935             // Region constructor accepts (top, right, bottom, left)
127936             // anchored/calculated from the left
127937             prevConstrainRegion = Ext.create('Ext.util.Region',
127938                 prevBox.y,
127939                 // Right boundary is x + maxWidth if there IS a maxWidth.
127940                 // Otherwise it is calculated based upon the minWidth of the next Component
127941                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
127942                 prevBox.bottom,
127943                 prevBox.x + (prevCmp.minWidth || defaultMin)
127944             );
127945             // anchored/calculated from the right
127946             nextConstrainRegion = Ext.create('Ext.util.Region',
127947                 nextBox.y,
127948                 nextBox.right - (nextCmp.minWidth || defaultMin),
127949                 nextBox.bottom,
127950                 // Left boundary is right - maxWidth if there IS a maxWidth.
127951                 // Otherwise it is calculated based upon the minWidth of the previous Component
127952                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
127953             );
127954         } else {
127955             // anchored/calculated from the top
127956             prevConstrainRegion = Ext.create('Ext.util.Region',
127957                 prevBox.y + (prevCmp.minHeight || defaultMin),
127958                 prevBox.right,
127959                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
127960                 // Otherwise it is calculated based upon the minWidth of the next Component
127961                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
127962                 prevBox.x
127963             );
127964             // anchored/calculated from the bottom
127965             nextConstrainRegion = Ext.create('Ext.util.Region',
127966                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
127967                 // Otherwise it is calculated based upon the minHeight of the previous Component
127968                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
127969                 nextBox.right,
127970                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
127971                 nextBox.x
127972             );
127973         }
127974
127975         // intersection of the two regions to provide region draggable
127976         return prevConstrainRegion.intersect(nextConstrainRegion);
127977     },
127978
127979     // Performs the actual resizing of the previous and next components
127980     performResize: function(e) {
127981         var me       = this,
127982             offset   = me.getOffset('dragTarget'),
127983             splitter = me.getSplitter(),
127984             orient   = splitter.orientation,
127985             prevCmp  = me.getPrevCmp(),
127986             nextCmp  = me.getNextCmp(),
127987             owner    = splitter.ownerCt,
127988             layout   = owner.getLayout();
127989
127990         // Inhibit automatic container layout caused by setSize calls below.
127991         owner.suspendLayout = true;
127992
127993         if (orient === 'vertical') {
127994             if (prevCmp) {
127995                 if (!prevCmp.maintainFlex) {
127996                     delete prevCmp.flex;
127997                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
127998                 }
127999             }
128000             if (nextCmp) {
128001                 if (!nextCmp.maintainFlex) {
128002                     delete nextCmp.flex;
128003                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
128004                 }
128005             }
128006         // verticals
128007         } else {
128008             if (prevCmp) {
128009                 if (!prevCmp.maintainFlex) {
128010                     delete prevCmp.flex;
128011                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
128012                 }
128013             }
128014             if (nextCmp) {
128015                 if (!nextCmp.maintainFlex) {
128016                     delete nextCmp.flex;
128017                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
128018                 }
128019             }
128020         }
128021         delete owner.suspendLayout;
128022         layout.onLayout();
128023     },
128024
128025     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
128026     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
128027     // regardless (by onBeforeStart).
128028     endDrag: function () {
128029         var me = this;
128030
128031         if (me.overlay) {
128032              me.overlay.remove();
128033              delete me.overlay;
128034         }
128035
128036         me.callParent(arguments); // this calls onEnd
128037     },
128038
128039     // perform the resize and remove the proxy class from the splitter el
128040     onEnd: function(e) {
128041         var me = this,
128042             splitter = me.getSplitter();
128043             
128044         splitter.removeCls(splitter.baseCls + '-active');
128045         me.performResize();
128046     },
128047
128048     // Track the proxy and set the proper XY coordinates
128049     // while constraining the drag
128050     onDrag: function(e) {
128051         var me        = this,
128052             offset    = me.getOffset('dragTarget'),
128053             splitter  = me.getSplitter(),
128054             splitEl   = splitter.getEl(),
128055             orient    = splitter.orientation;
128056
128057         if (orient === "vertical") {
128058             splitEl.setX(me.startRegion.left + offset[0]);
128059         } else {
128060             splitEl.setY(me.startRegion.top + offset[1]);
128061         }
128062     },
128063
128064     getSplitter: function() {
128065         return Ext.getCmp(this.getDragCt().id);
128066     }
128067 });
128068 /**
128069  * @class Ext.selection.CellModel
128070  * @extends Ext.selection.Model
128071  */
128072 Ext.define('Ext.selection.CellModel', {
128073     extend: 'Ext.selection.Model',
128074     alias: 'selection.cellmodel',
128075     requires: ['Ext.util.KeyNav'],
128076
128077     /**
128078      * @cfg {Boolean} enableKeyNav
128079      * Turns on/off keyboard navigation within the grid.
128080      */
128081     enableKeyNav: true,
128082
128083     /**
128084      * @cfg {Boolean} preventWrap
128085      * Set this configuration to true to prevent wrapping around of selection as
128086      * a user navigates to the first or last column.
128087      */
128088     preventWrap: false,
128089
128090     constructor: function(){
128091         this.addEvents(
128092             /**
128093              * @event deselect
128094              * Fired after a cell is deselected
128095              * @param {Ext.selection.CellModel} this
128096              * @param {Ext.data.Model} record The record of the deselected cell
128097              * @param {Number} row The row index deselected
128098              * @param {Number} column The column index deselected
128099              */
128100             'deselect',
128101
128102             /**
128103              * @event select
128104              * Fired after a cell is selected
128105              * @param {Ext.selection.CellModel} this
128106              * @param {Ext.data.Model} record The record of the selected cell
128107              * @param {Number} row The row index selected
128108              * @param {Number} column The column index selected
128109              */
128110             'select'
128111         );
128112         this.callParent(arguments);
128113     },
128114
128115     bindComponent: function(view) {
128116         var me = this;
128117         me.primaryView = view;
128118         me.views = me.views || [];
128119         me.views.push(view);
128120         me.bind(view.getStore(), true);
128121
128122         view.on({
128123             cellmousedown: me.onMouseDown,
128124             refresh: me.onViewRefresh,
128125             scope: me
128126         });
128127
128128         if (me.enableKeyNav) {
128129             me.initKeyNav(view);
128130         }
128131     },
128132
128133     initKeyNav: function(view) {
128134         var me = this;
128135
128136         if (!view.rendered) {
128137             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128138             return;
128139         }
128140
128141         view.el.set({
128142             tabIndex: -1
128143         });
128144
128145         // view.el has tabIndex -1 to allow for
128146         // keyboard events to be passed to it.
128147         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
128148             up: me.onKeyUp,
128149             down: me.onKeyDown,
128150             right: me.onKeyRight,
128151             left: me.onKeyLeft,
128152             tab: me.onKeyTab,
128153             scope: me
128154         });
128155     },
128156
128157     getHeaderCt: function() {
128158         return this.primaryView.headerCt;
128159     },
128160
128161     onKeyUp: function(e, t) {
128162         this.move('up', e);
128163     },
128164
128165     onKeyDown: function(e, t) {
128166         this.move('down', e);
128167     },
128168
128169     onKeyLeft: function(e, t) {
128170         this.move('left', e);
128171     },
128172
128173     onKeyRight: function(e, t) {
128174         this.move('right', e);
128175     },
128176
128177     move: function(dir, e) {
128178         var me = this,
128179             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
128180         if (pos) {
128181             me.setCurrentPosition(pos);
128182         }
128183         return pos;
128184     },
128185
128186     /**
128187      * Returns the current position in the format {row: row, column: column}
128188      */
128189     getCurrentPosition: function() {
128190         return this.position;
128191     },
128192
128193     /**
128194      * Sets the current position
128195      * @param {Object} position The position to set.
128196      */
128197     setCurrentPosition: function(pos) {
128198         var me = this;
128199
128200         if (me.position) {
128201             me.onCellDeselect(me.position);
128202         }
128203         if (pos) {
128204             me.onCellSelect(pos);
128205         }
128206         me.position = pos;
128207     },
128208
128209     /**
128210      * Set the current position based on where the user clicks.
128211      * @private
128212      */
128213     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
128214         this.setCurrentPosition({
128215             row: rowIndex,
128216             column: cellIndex
128217         });
128218     },
128219
128220     // notify the view that the cell has been selected to update the ui
128221     // appropriately and bring the cell into focus
128222     onCellSelect: function(position) {
128223         var me = this,
128224             store = me.view.getStore(),
128225             record = store.getAt(position.row);
128226
128227         me.doSelect(record);
128228         me.primaryView.onCellSelect(position);
128229         // TODO: Remove temporary cellFocus call here.
128230         me.primaryView.onCellFocus(position);
128231         me.fireEvent('select', me, record, position.row, position.column);
128232     },
128233
128234     // notify view that the cell has been deselected to update the ui
128235     // appropriately
128236     onCellDeselect: function(position) {
128237         var me = this,
128238             store = me.view.getStore(),
128239             record = store.getAt(position.row);
128240
128241         me.doDeselect(record);
128242         me.primaryView.onCellDeselect(position);
128243         me.fireEvent('deselect', me, record, position.row, position.column);
128244     },
128245
128246     onKeyTab: function(e, t) {
128247         var me = this,
128248             direction = e.shiftKey ? 'left' : 'right',
128249             editingPlugin = me.view.editingPlugin,
128250             position = me.move(direction, e);
128251
128252         if (editingPlugin && position && me.wasEditing) {
128253             editingPlugin.startEditByPosition(position);
128254         }
128255         delete me.wasEditing;
128256     },
128257
128258     onEditorTab: function(editingPlugin, e) {
128259         var me = this,
128260             direction = e.shiftKey ? 'left' : 'right',
128261             position  = me.move(direction, e);
128262
128263         if (position) {
128264             editingPlugin.startEditByPosition(position);
128265             me.wasEditing = true;
128266         }
128267     },
128268
128269     refresh: function() {
128270         var pos = this.getCurrentPosition();
128271         if (pos) {
128272             this.onCellSelect(pos);
128273         }
128274     },
128275
128276     onViewRefresh: function() {
128277         var pos = this.getCurrentPosition();
128278         if (pos) {
128279             this.onCellDeselect(pos);
128280             this.setCurrentPosition(null);
128281         }
128282     },
128283
128284     selectByPosition: function(position) {
128285         this.setCurrentPosition(position);
128286     }
128287 });
128288 /**
128289  * @class Ext.selection.RowModel
128290  * @extends Ext.selection.Model
128291  */
128292 Ext.define('Ext.selection.RowModel', {
128293     extend: 'Ext.selection.Model',
128294     alias: 'selection.rowmodel',
128295     requires: ['Ext.util.KeyNav'],
128296
128297     /**
128298      * @private
128299      * Number of pixels to scroll to the left/right when pressing
128300      * left/right keys.
128301      */
128302     deltaScroll: 5,
128303
128304     /**
128305      * @cfg {Boolean} enableKeyNav
128306      *
128307      * Turns on/off keyboard navigation within the grid.
128308      */
128309     enableKeyNav: true,
128310     
128311     /**
128312      * @cfg {Boolean} [ignoreRightMouseSelection=true]
128313      * True to ignore selections that are made when using the right mouse button if there are
128314      * records that are already selected. If no records are selected, selection will continue 
128315      * as normal
128316      */
128317     ignoreRightMouseSelection: true,
128318
128319     constructor: function(){
128320         this.addEvents(
128321             /**
128322              * @event beforedeselect
128323              * Fired before a record is deselected. If any listener returns false, the
128324              * deselection is cancelled.
128325              * @param {Ext.selection.RowModel} this
128326              * @param {Ext.data.Model} record The deselected record
128327              * @param {Number} index The row index deselected
128328              */
128329             'beforedeselect',
128330
128331             /**
128332              * @event beforeselect
128333              * Fired before a record is selected. If any listener returns false, the
128334              * selection is cancelled.
128335              * @param {Ext.selection.RowModel} this
128336              * @param {Ext.data.Model} record The selected record
128337              * @param {Number} index The row index selected
128338              */
128339             'beforeselect',
128340
128341             /**
128342              * @event deselect
128343              * Fired after a record is deselected
128344              * @param {Ext.selection.RowModel} this
128345              * @param {Ext.data.Model} record The deselected record
128346              * @param {Number} index The row index deselected
128347              */
128348             'deselect',
128349
128350             /**
128351              * @event select
128352              * Fired after a record is selected
128353              * @param {Ext.selection.RowModel} this
128354              * @param {Ext.data.Model} record The selected record
128355              * @param {Number} index The row index selected
128356              */
128357             'select'
128358         );
128359         this.callParent(arguments);
128360     },
128361
128362     bindComponent: function(view) {
128363         var me = this;
128364
128365         me.views = me.views || [];
128366         me.views.push(view);
128367         me.bind(view.getStore(), true);
128368
128369         view.on({
128370             itemmousedown: me.onRowMouseDown,
128371             scope: me
128372         });
128373
128374         if (me.enableKeyNav) {
128375             me.initKeyNav(view);
128376         }
128377     },
128378
128379     initKeyNav: function(view) {
128380         var me = this;
128381
128382         if (!view.rendered) {
128383             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128384             return;
128385         }
128386
128387         view.el.set({
128388             tabIndex: -1
128389         });
128390
128391         // view.el has tabIndex -1 to allow for
128392         // keyboard events to be passed to it.
128393         me.keyNav = new Ext.util.KeyNav(view.el, {
128394             up: me.onKeyUp,
128395             down: me.onKeyDown,
128396             right: me.onKeyRight,
128397             left: me.onKeyLeft,
128398             pageDown: me.onKeyPageDown,
128399             pageUp: me.onKeyPageUp,
128400             home: me.onKeyHome,
128401             end: me.onKeyEnd,
128402             scope: me
128403         });
128404         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
128405     },
128406
128407     // Returns the number of rows currently visible on the screen or
128408     // false if there were no rows. This assumes that all rows are
128409     // of the same height and the first view is accurate.
128410     getRowsVisible: function() {
128411         var rowsVisible = false,
128412             view = this.views[0],
128413             row = view.getNode(0),
128414             rowHeight, gridViewHeight;
128415
128416         if (row) {
128417             rowHeight = Ext.fly(row).getHeight();
128418             gridViewHeight = view.el.getHeight();
128419             rowsVisible = Math.floor(gridViewHeight / rowHeight);
128420         }
128421
128422         return rowsVisible;
128423     },
128424
128425     // go to last visible record in grid.
128426     onKeyEnd: function(e, t) {
128427         var me = this,
128428             last = me.store.getAt(me.store.getCount() - 1);
128429
128430         if (last) {
128431             if (e.shiftKey) {
128432                 me.selectRange(last, me.lastFocused || 0);
128433                 me.setLastFocused(last);
128434             } else if (e.ctrlKey) {
128435                 me.setLastFocused(last);
128436             } else {
128437                 me.doSelect(last);
128438             }
128439         }
128440     },
128441
128442     // go to first visible record in grid.
128443     onKeyHome: function(e, t) {
128444         var me = this,
128445             first = me.store.getAt(0);
128446
128447         if (first) {
128448             if (e.shiftKey) {
128449                 me.selectRange(first, me.lastFocused || 0);
128450                 me.setLastFocused(first);
128451             } else if (e.ctrlKey) {
128452                 me.setLastFocused(first);
128453             } else {
128454                 me.doSelect(first, false);
128455             }
128456         }
128457     },
128458
128459     // Go one page up from the lastFocused record in the grid.
128460     onKeyPageUp: function(e, t) {
128461         var me = this,
128462             rowsVisible = me.getRowsVisible(),
128463             selIdx,
128464             prevIdx,
128465             prevRecord,
128466             currRec;
128467
128468         if (rowsVisible) {
128469             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128470             prevIdx = selIdx - rowsVisible;
128471             if (prevIdx < 0) {
128472                 prevIdx = 0;
128473             }
128474             prevRecord = me.store.getAt(prevIdx);
128475             if (e.shiftKey) {
128476                 currRec = me.store.getAt(selIdx);
128477                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
128478                 me.setLastFocused(prevRecord);
128479             } else if (e.ctrlKey) {
128480                 e.preventDefault();
128481                 me.setLastFocused(prevRecord);
128482             } else {
128483                 me.doSelect(prevRecord);
128484             }
128485
128486         }
128487     },
128488
128489     // Go one page down from the lastFocused record in the grid.
128490     onKeyPageDown: function(e, t) {
128491         var me = this,
128492             rowsVisible = me.getRowsVisible(),
128493             selIdx,
128494             nextIdx,
128495             nextRecord,
128496             currRec;
128497
128498         if (rowsVisible) {
128499             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128500             nextIdx = selIdx + rowsVisible;
128501             if (nextIdx >= me.store.getCount()) {
128502                 nextIdx = me.store.getCount() - 1;
128503             }
128504             nextRecord = me.store.getAt(nextIdx);
128505             if (e.shiftKey) {
128506                 currRec = me.store.getAt(selIdx);
128507                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
128508                 me.setLastFocused(nextRecord);
128509             } else if (e.ctrlKey) {
128510                 // some browsers, this means go thru browser tabs
128511                 // attempt to stop.
128512                 e.preventDefault();
128513                 me.setLastFocused(nextRecord);
128514             } else {
128515                 me.doSelect(nextRecord);
128516             }
128517         }
128518     },
128519
128520     // Select/Deselect based on pressing Spacebar.
128521     // Assumes a SIMPLE selectionmode style
128522     onKeyPress: function(e, t) {
128523         if (e.getKey() === e.SPACE) {
128524             e.stopEvent();
128525             var me = this,
128526                 record = me.lastFocused;
128527
128528             if (record) {
128529                 if (me.isSelected(record)) {
128530                     me.doDeselect(record, false);
128531                 } else {
128532                     me.doSelect(record, true);
128533                 }
128534             }
128535         }
128536     },
128537
128538     // Navigate one record up. This could be a selection or
128539     // could be simply focusing a record for discontiguous
128540     // selection. Provides bounds checking.
128541     onKeyUp: function(e, t) {
128542         var me = this,
128543             view = me.views[0],
128544             idx  = me.store.indexOf(me.lastFocused),
128545             record;
128546
128547         if (idx > 0) {
128548             // needs to be the filtered count as thats what
128549             // will be visible.
128550             record = me.store.getAt(idx - 1);
128551             if (e.shiftKey && me.lastFocused) {
128552                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128553                     me.doDeselect(me.lastFocused, true);
128554                     me.setLastFocused(record);
128555                 } else if (!me.isSelected(me.lastFocused)) {
128556                     me.doSelect(me.lastFocused, true);
128557                     me.doSelect(record, true);
128558                 } else {
128559                     me.doSelect(record, true);
128560                 }
128561             } else if (e.ctrlKey) {
128562                 me.setLastFocused(record);
128563             } else {
128564                 me.doSelect(record);
128565                 //view.focusRow(idx - 1);
128566             }
128567         }
128568         // There was no lastFocused record, and the user has pressed up
128569         // Ignore??
128570         //else if (this.selected.getCount() == 0) {
128571         //
128572         //    this.doSelect(record);
128573         //    //view.focusRow(idx - 1);
128574         //}
128575     },
128576
128577     // Navigate one record down. This could be a selection or
128578     // could be simply focusing a record for discontiguous
128579     // selection. Provides bounds checking.
128580     onKeyDown: function(e, t) {
128581         var me = this,
128582             view = me.views[0],
128583             idx  = me.store.indexOf(me.lastFocused),
128584             record;
128585
128586         // needs to be the filtered count as thats what
128587         // will be visible.
128588         if (idx + 1 < me.store.getCount()) {
128589             record = me.store.getAt(idx + 1);
128590             if (me.selected.getCount() === 0) {
128591                 me.doSelect(record);
128592                 //view.focusRow(idx + 1);
128593             } else if (e.shiftKey && me.lastFocused) {
128594                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128595                     me.doDeselect(me.lastFocused, true);
128596                     me.setLastFocused(record);
128597                 } else if (!me.isSelected(me.lastFocused)) {
128598                     me.doSelect(me.lastFocused, true);
128599                     me.doSelect(record, true);
128600                 } else {
128601                     me.doSelect(record, true);
128602                 }
128603             } else if (e.ctrlKey) {
128604                 me.setLastFocused(record);
128605             } else {
128606                 me.doSelect(record);
128607                 //view.focusRow(idx + 1);
128608             }
128609         }
128610     },
128611
128612     scrollByDeltaX: function(delta) {
128613         var view    = this.views[0],
128614             section = view.up(),
128615             hScroll = section.horizontalScroller;
128616
128617         if (hScroll) {
128618             hScroll.scrollByDeltaX(delta);
128619         }
128620     },
128621
128622     onKeyLeft: function(e, t) {
128623         this.scrollByDeltaX(-this.deltaScroll);
128624     },
128625
128626     onKeyRight: function(e, t) {
128627         this.scrollByDeltaX(this.deltaScroll);
128628     },
128629
128630     // Select the record with the event included so that
128631     // we can take into account ctrlKey, shiftKey, etc
128632     onRowMouseDown: function(view, record, item, index, e) {
128633         view.el.focus();
128634         if (!this.allowRightMouseSelection(e)) {
128635             return;
128636         }
128637         this.selectWithEvent(record, e);
128638     },
128639     
128640     /**
128641      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
128642      * option.
128643      * @private
128644      * @param {Ext.EventObject} e The event
128645      * @return {Boolean} False if the selection should not proceed
128646      */
128647     allowRightMouseSelection: function(e) {
128648         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
128649         if (disallow) {
128650             disallow = this.hasSelection();
128651         }
128652         return !disallow;
128653     },
128654
128655     // Allow the GridView to update the UI by
128656     // adding/removing a CSS class from the row.
128657     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
128658         var me      = this,
128659             views   = me.views,
128660             viewsLn = views.length,
128661             store   = me.store,
128662             rowIdx  = store.indexOf(record),
128663             eventName = isSelected ? 'select' : 'deselect',
128664             i = 0;
128665
128666         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
128667                 commitFn() !== false) {
128668
128669             for (; i < viewsLn; i++) {
128670                 if (isSelected) {
128671                     views[i].onRowSelect(rowIdx, suppressEvent);
128672                 } else {
128673                     views[i].onRowDeselect(rowIdx, suppressEvent);
128674                 }
128675             }
128676
128677             if (!suppressEvent) {
128678                 me.fireEvent(eventName, me, record, rowIdx);
128679             }
128680         }
128681     },
128682
128683     // Provide indication of what row was last focused via
128684     // the gridview.
128685     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
128686         var views   = this.views,
128687             viewsLn = views.length,
128688             store   = this.store,
128689             rowIdx,
128690             i = 0;
128691
128692         if (oldFocused) {
128693             rowIdx = store.indexOf(oldFocused);
128694             if (rowIdx != -1) {
128695                 for (; i < viewsLn; i++) {
128696                     views[i].onRowFocus(rowIdx, false);
128697                 }
128698             }
128699         }
128700
128701         if (newFocused) {
128702             rowIdx = store.indexOf(newFocused);
128703             if (rowIdx != -1) {
128704                 for (i = 0; i < viewsLn; i++) {
128705                     views[i].onRowFocus(rowIdx, true, supressFocus);
128706                 }
128707             }
128708         }
128709     },
128710
128711     onEditorTab: function(editingPlugin, e) {
128712         var me = this,
128713             view = me.views[0],
128714             record = editingPlugin.getActiveRecord(),
128715             header = editingPlugin.getActiveColumn(),
128716             position = view.getPosition(record, header),
128717             direction = e.shiftKey ? 'left' : 'right',
128718             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
128719
128720         if (newPosition) {
128721             editingPlugin.startEditByPosition(newPosition);
128722         }
128723     },
128724
128725     selectByPosition: function(position) {
128726         var record = this.store.getAt(position.row);
128727         this.select(record);
128728     }
128729 });
128730 /**
128731  * @class Ext.selection.CheckboxModel
128732  * @extends Ext.selection.RowModel
128733  *
128734  * A selection model that renders a column of checkboxes that can be toggled to
128735  * select or deselect rows. The default mode for this selection model is MULTI.
128736  *
128737  * The selection model will inject a header for the checkboxes in the first view
128738  * and according to the 'injectCheckbox' configuration.
128739  */
128740 Ext.define('Ext.selection.CheckboxModel', {
128741     alias: 'selection.checkboxmodel',
128742     extend: 'Ext.selection.RowModel',
128743
128744     /**
128745      * @cfg {String} mode
128746      * Modes of selection.
128747      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
128748      */
128749     mode: 'MULTI',
128750
128751     /**
128752      * @cfg {Number/Boolean/String} injectCheckbox
128753      * Instructs the SelectionModel whether or not to inject the checkbox header
128754      * automatically or not. (Note: By not placing the checkbox in manually, the
128755      * grid view will need to be rendered 2x on initial render.)
128756      * Supported values are a Number index, false and the strings 'first' and 'last'.
128757      */
128758     injectCheckbox: 0,
128759
128760     /**
128761      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
128762      * checkbox column.
128763      */
128764     checkOnly: false,
128765
128766     headerWidth: 24,
128767
128768     // private
128769     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
128770
128771     bindComponent: function(view) {
128772         var me = this;
128773
128774         me.sortable = false;
128775         me.callParent(arguments);
128776         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
128777             // if we have a locked header, only hook up to the first
128778             view.headerCt.on('headerclick', me.onHeaderClick, me);
128779             me.addCheckbox(true);
128780             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
128781         }
128782     },
128783
128784     hasLockedHeader: function(){
128785         var hasLocked = false;
128786         Ext.each(this.views, function(view){
128787             if (view.headerCt.lockedCt) {
128788                 hasLocked = true;
128789                 return false;
128790             }
128791         });
128792         return hasLocked;
128793     },
128794
128795     /**
128796      * Add the header checkbox to the header row
128797      * @private
128798      * @param {Boolean} initial True if we're binding for the first time.
128799      */
128800     addCheckbox: function(initial){
128801         var me = this,
128802             checkbox = me.injectCheckbox,
128803             view = me.views[0],
128804             headerCt = view.headerCt;
128805
128806         if (checkbox !== false) {
128807             if (checkbox == 'first') {
128808                 checkbox = 0;
128809             } else if (checkbox == 'last') {
128810                 checkbox = headerCt.getColumnCount();
128811             }
128812             headerCt.add(checkbox,  me.getHeaderConfig());
128813         }
128814
128815         if (initial !== true) {
128816             view.refresh();
128817         }
128818     },
128819
128820     /**
128821      * Toggle the ui header between checked and unchecked state.
128822      * @param {Boolean} isChecked
128823      * @private
128824      */
128825     toggleUiHeader: function(isChecked) {
128826         var view     = this.views[0],
128827             headerCt = view.headerCt,
128828             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
128829
128830         if (checkHd) {
128831             if (isChecked) {
128832                 checkHd.el.addCls(this.checkerOnCls);
128833             } else {
128834                 checkHd.el.removeCls(this.checkerOnCls);
128835             }
128836         }
128837     },
128838
128839     /**
128840      * Toggle between selecting all and deselecting all when clicking on
128841      * a checkbox header.
128842      */
128843     onHeaderClick: function(headerCt, header, e) {
128844         if (header.isCheckerHd) {
128845             e.stopEvent();
128846             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
128847             if (isChecked) {
128848                 // We have to supress the event or it will scrollTo the change
128849                 this.deselectAll(true);
128850             } else {
128851                 // We have to supress the event or it will scrollTo the change
128852                 this.selectAll(true);
128853             }
128854         }
128855     },
128856
128857     /**
128858      * Retrieve a configuration to be used in a HeaderContainer.
128859      * This should be used when injectCheckbox is set to false.
128860      */
128861     getHeaderConfig: function() {
128862         var me = this;
128863
128864         return {
128865             isCheckerHd: true,
128866             text : '&#160;',
128867             width: me.headerWidth,
128868             sortable: false,
128869             draggable: false,
128870             resizable: false,
128871             hideable: false,
128872             menuDisabled: true,
128873             dataIndex: '',
128874             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
128875             renderer: Ext.Function.bind(me.renderer, me),
128876             locked: me.hasLockedHeader()
128877         };
128878     },
128879
128880     /**
128881      * Generates the HTML to be rendered in the injected checkbox column for each row.
128882      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
128883      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
128884      */
128885     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
128886         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
128887         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
128888     },
128889
128890     // override
128891     onRowMouseDown: function(view, record, item, index, e) {
128892         view.el.focus();
128893         var me = this,
128894             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
128895             
128896         if (!me.allowRightMouseSelection(e)) {
128897             return;
128898         }
128899
128900         // checkOnly set, but we didn't click on a checker.
128901         if (me.checkOnly && !checker) {
128902             return;
128903         }
128904
128905         if (checker) {
128906             var mode = me.getSelectionMode();
128907             // dont change the mode if its single otherwise
128908             // we would get multiple selection
128909             if (mode !== 'SINGLE') {
128910                 me.setSelectionMode('SIMPLE');
128911             }
128912             me.selectWithEvent(record, e);
128913             me.setSelectionMode(mode);
128914         } else {
128915             me.selectWithEvent(record, e);
128916         }
128917     },
128918
128919     /**
128920      * Synchronize header checker value as selection changes.
128921      * @private
128922      */
128923     onSelectChange: function() {
128924         this.callParent(arguments);
128925
128926         // check to see if all records are selected
128927         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
128928         this.toggleUiHeader(hdSelectStatus);
128929     }
128930 });
128931
128932 /**
128933  * @class Ext.selection.TreeModel
128934  * @extends Ext.selection.RowModel
128935  *
128936  * Adds custom behavior for left/right keyboard navigation for use with a tree.
128937  * Depends on the view having an expand and collapse method which accepts a
128938  * record.
128939  * 
128940  * @private
128941  */
128942 Ext.define('Ext.selection.TreeModel', {
128943     extend: 'Ext.selection.RowModel',
128944     alias: 'selection.treemodel',
128945     
128946     // typically selection models prune records from the selection
128947     // model when they are removed, because the TreeView constantly
128948     // adds/removes records as they are expanded/collapsed
128949     pruneRemoved: false,
128950     
128951     onKeyRight: function(e, t) {
128952         var focused = this.getLastFocused(),
128953             view    = this.view;
128954             
128955         if (focused) {
128956             // tree node is already expanded, go down instead
128957             // this handles both the case where we navigate to firstChild and if
128958             // there are no children to the nextSibling
128959             if (focused.isExpanded()) {
128960                 this.onKeyDown(e, t);
128961             // if its not a leaf node, expand it
128962             } else if (!focused.isLeaf()) {
128963                 view.expand(focused);
128964             }
128965         }
128966     },
128967     
128968     onKeyLeft: function(e, t) {
128969         var focused = this.getLastFocused(),
128970             view    = this.view,
128971             viewSm  = view.getSelectionModel(),
128972             parentNode, parentRecord;
128973
128974         if (focused) {
128975             parentNode = focused.parentNode;
128976             // if focused node is already expanded, collapse it
128977             if (focused.isExpanded()) {
128978                 view.collapse(focused);
128979             // has a parentNode and its not root
128980             // TODO: this needs to cover the case where the root isVisible
128981             } else if (parentNode && !parentNode.isRoot()) {
128982                 // Select a range of records when doing multiple selection.
128983                 if (e.shiftKey) {
128984                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
128985                     viewSm.setLastFocused(parentNode);
128986                 // just move focus, not selection
128987                 } else if (e.ctrlKey) {
128988                     viewSm.setLastFocused(parentNode);
128989                 // select it
128990                 } else {
128991                     viewSm.select(parentNode);
128992                 }
128993             }
128994         }
128995     },
128996     
128997     onKeyPress: function(e, t) {
128998         var key = e.getKey(),
128999             selected, 
129000             checked;
129001         
129002         if (key === e.SPACE || key === e.ENTER) {
129003             e.stopEvent();
129004             selected = this.getLastSelected();
129005             if (selected) {
129006                 this.view.onCheckChange(selected);
129007             }
129008         } else {
129009             this.callParent(arguments);
129010         }
129011     }
129012 });
129013
129014 /**
129015  * @class Ext.slider.Thumb
129016  * @extends Ext.Base
129017  * @private
129018  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
129019  * be created internally by an {@link Ext.slider.Multi Multi slider}.
129020  */
129021 Ext.define('Ext.slider.Thumb', {
129022     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
129023     /**
129024      * @private
129025      * @property {Number} topThumbZIndex
129026      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
129027      */
129028     topZIndex: 10000,
129029
129030     /**
129031      * @cfg {Ext.slider.MultiSlider} slider (required)
129032      * The Slider to render to.
129033      */
129034
129035     /**
129036      * Creates new slider thumb.
129037      * @param {Object} config (optional) Config object.
129038      */
129039     constructor: function(config) {
129040         var me = this;
129041
129042         /**
129043          * @property {Ext.slider.MultiSlider} slider
129044          * The slider this thumb is contained within
129045          */
129046         Ext.apply(me, config || {}, {
129047             cls: Ext.baseCSSPrefix + 'slider-thumb',
129048
129049             /**
129050              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
129051              */
129052             constrain: false
129053         });
129054         me.callParent([config]);
129055
129056         if (me.slider.vertical) {
129057             Ext.apply(me, Ext.slider.Thumb.Vertical);
129058         }
129059     },
129060
129061     /**
129062      * Renders the thumb into a slider
129063      */
129064     render: function() {
129065         var me = this;
129066
129067         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
129068         if (me.disabled) {
129069             me.disable();
129070         }
129071         me.initEvents();
129072     },
129073
129074     /**
129075      * @private
129076      * move the thumb
129077      */
129078     move: function(v, animate){
129079         if(!animate){
129080             this.el.setLeft(v);
129081         }else{
129082             Ext.create('Ext.fx.Anim', {
129083                 target: this.el,
129084                 duration: 350,
129085                 to: {
129086                     left: v
129087                 }
129088             });
129089         }
129090     },
129091
129092     /**
129093      * @private
129094      * Bring thumb dom element to front.
129095      */
129096     bringToFront: function() {
129097         this.el.setStyle('zIndex', this.topZIndex);
129098     },
129099
129100     /**
129101      * @private
129102      * Send thumb dom element to back.
129103      */
129104     sendToBack: function() {
129105         this.el.setStyle('zIndex', '');
129106     },
129107
129108     /**
129109      * Enables the thumb if it is currently disabled
129110      */
129111     enable: function() {
129112         var me = this;
129113
129114         me.disabled = false;
129115         if (me.el) {
129116             me.el.removeCls(me.slider.disabledCls);
129117         }
129118     },
129119
129120     /**
129121      * Disables the thumb if it is currently enabled
129122      */
129123     disable: function() {
129124         var me = this;
129125
129126         me.disabled = true;
129127         if (me.el) {
129128             me.el.addCls(me.slider.disabledCls);
129129         }
129130     },
129131
129132     /**
129133      * Sets up an Ext.dd.DragTracker for this thumb
129134      */
129135     initEvents: function() {
129136         var me = this,
129137             el = me.el;
129138
129139         me.tracker = Ext.create('Ext.dd.DragTracker', {
129140             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
129141             onStart      : Ext.Function.bind(me.onDragStart, me),
129142             onDrag       : Ext.Function.bind(me.onDrag, me),
129143             onEnd        : Ext.Function.bind(me.onDragEnd, me),
129144             tolerance    : 3,
129145             autoStart    : 300,
129146             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
129147         });
129148
129149         me.tracker.initEl(el);
129150     },
129151
129152     /**
129153      * @private
129154      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
129155      * this returns false to disable the DragTracker too.
129156      * @return {Boolean} False if the slider is currently disabled
129157      */
129158     onBeforeDragStart : function(e) {
129159         if (this.disabled) {
129160             return false;
129161         } else {
129162             this.slider.promoteThumb(this);
129163             return true;
129164         }
129165     },
129166
129167     /**
129168      * @private
129169      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
129170      * to the thumb and fires the 'dragstart' event
129171      */
129172     onDragStart: function(e){
129173         var me = this;
129174
129175         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129176         me.dragging = true;
129177         me.dragStartValue = me.value;
129178
129179         me.slider.fireEvent('dragstart', me.slider, e, me);
129180     },
129181
129182     /**
129183      * @private
129184      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
129185      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
129186      */
129187     onDrag: function(e) {
129188         var me       = this,
129189             slider   = me.slider,
129190             index    = me.index,
129191             newValue = me.getNewValue(),
129192             above,
129193             below;
129194
129195         if (me.constrain) {
129196             above = slider.thumbs[index + 1];
129197             below = slider.thumbs[index - 1];
129198
129199             if (below !== undefined && newValue <= below.value) {
129200                 newValue = below.value;
129201             }
129202
129203             if (above !== undefined && newValue >= above.value) {
129204                 newValue = above.value;
129205             }
129206         }
129207
129208         slider.setValue(index, newValue, false);
129209         slider.fireEvent('drag', slider, e, me);
129210     },
129211
129212     getNewValue: function() {
129213         var slider = this.slider,
129214             pos = slider.innerEl.translatePoints(this.tracker.getXY());
129215
129216         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
129217     },
129218
129219     /**
129220      * @private
129221      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
129222      * fires the 'changecomplete' event with the new value
129223      */
129224     onDragEnd: function(e) {
129225         var me     = this,
129226             slider = me.slider,
129227             value  = me.value;
129228
129229         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129230
129231         me.dragging = false;
129232         slider.fireEvent('dragend', slider, e);
129233
129234         if (me.dragStartValue != value) {
129235             slider.fireEvent('changecomplete', slider, value, me);
129236         }
129237     },
129238
129239     destroy: function() {
129240         Ext.destroy(this.tracker);
129241     },
129242     statics: {
129243         // Method overrides to support vertical dragging of thumb within slider
129244         Vertical: {
129245             getNewValue: function() {
129246                 var slider   = this.slider,
129247                     innerEl  = slider.innerEl,
129248                     pos      = innerEl.translatePoints(this.tracker.getXY()),
129249                     bottom   = innerEl.getHeight() - pos.top;
129250
129251                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
129252             },
129253             move: function(v, animate) {
129254                 if (!animate) {
129255                     this.el.setBottom(v);
129256                 } else {
129257                     Ext.create('Ext.fx.Anim', {
129258                         target: this.el,
129259                         duration: 350,
129260                         to: {
129261                             bottom: v
129262                         }
129263                     });
129264                 }
129265             }
129266         }
129267     }
129268 });
129269
129270 /**
129271  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
129272  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
129273  * options to the slider directly.
129274  *
129275  *     @example
129276  *     Ext.create('Ext.slider.Single', {
129277  *         width: 214,
129278  *         minValue: 0,
129279  *         maxValue: 100,
129280  *         useTips: true,
129281  *         renderTo: Ext.getBody()
129282  *     });
129283  *
129284  * Optionally provide your own tip text by passing tipText:
129285  *
129286  *     @example
129287  *     Ext.create('Ext.slider.Single', {
129288  *         width: 214,
129289  *         minValue: 0,
129290  *         maxValue: 100,
129291  *         useTips: true,
129292  *         tipText: function(thumb){
129293  *             return Ext.String.format('**{0}% complete**', thumb.value);
129294  *         },
129295  *         renderTo: Ext.getBody()
129296  *     });
129297  */
129298 Ext.define('Ext.slider.Tip', {
129299     extend: 'Ext.tip.Tip',
129300     minWidth: 10,
129301     alias: 'widget.slidertip',
129302     offsets : [0, -10],
129303
129304     isSliderTip: true,
129305
129306     init: function(slider) {
129307         var me = this;
129308
129309         slider.on({
129310             scope    : me,
129311             dragstart: me.onSlide,
129312             drag     : me.onSlide,
129313             dragend  : me.hide,
129314             destroy  : me.destroy
129315         });
129316     },
129317     /**
129318      * @private
129319      * Called whenever a dragstart or drag event is received on the associated Thumb.
129320      * Aligns the Tip with the Thumb's new position.
129321      * @param {Ext.slider.MultiSlider} slider The slider
129322      * @param {Ext.EventObject} e The Event object
129323      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
129324      */
129325     onSlide : function(slider, e, thumb) {
129326         var me = this;
129327         me.show();
129328         me.update(me.getText(thumb));
129329         me.doComponentLayout();
129330         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
129331     },
129332
129333     /**
129334      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
129335      * Thumb that the Tip is attached to. Override to customize.
129336      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
129337      * @return {String} The text to display in the tip
129338      */
129339     getText : function(thumb) {
129340         return String(thumb.value);
129341     }
129342 });
129343 /**
129344  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
129345  * and animation. Can be added as an item to any container.
129346  *
129347  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
129348  *
129349  *     @example
129350  *     Ext.create('Ext.slider.Multi', {
129351  *         width: 200,
129352  *         values: [25, 50, 75],
129353  *         increment: 5,
129354  *         minValue: 0,
129355  *         maxValue: 100,
129356  *
129357  *         // this defaults to true, setting to false allows the thumbs to pass each other
129358  *         constrainThumbs: false,
129359  *         renderTo: Ext.getBody()
129360  *     });
129361  */
129362 Ext.define('Ext.slider.Multi', {
129363     extend: 'Ext.form.field.Base',
129364     alias: 'widget.multislider',
129365     alternateClassName: 'Ext.slider.MultiSlider',
129366
129367     requires: [
129368         'Ext.slider.Thumb',
129369         'Ext.slider.Tip',
129370         'Ext.Number',
129371         'Ext.util.Format',
129372         'Ext.Template',
129373         'Ext.layout.component.field.Slider'
129374     ],
129375
129376     // note: {id} here is really {inputId}, but {cmpId} is available
129377     fieldSubTpl: [
129378         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
129379             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
129380                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
129381                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
129382                 '</div>',
129383             '</div>',
129384         '</div>',
129385         {
129386             disableFormats: true,
129387             compiled: true
129388         }
129389     ],
129390
129391     /**
129392      * @cfg {Number} value
129393      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
129394      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
129395      */
129396
129397     /**
129398      * @cfg {Number[]} values
129399      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
129400      * in this array. This will take precedence over the single {@link #value} config.
129401      */
129402
129403     /**
129404      * @cfg {Boolean} vertical
129405      * Orient the Slider vertically rather than horizontally.
129406      */
129407     vertical: false,
129408
129409     /**
129410      * @cfg {Number} minValue
129411      * The minimum value for the Slider.
129412      */
129413     minValue: 0,
129414
129415     /**
129416      * @cfg {Number} maxValue
129417      * The maximum value for the Slider.
129418      */
129419     maxValue: 100,
129420
129421     /**
129422      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
129423      *
129424      * To disable rounding, configure as **false**.
129425      */
129426     decimalPrecision: 0,
129427
129428     /**
129429      * @cfg {Number} keyIncrement
129430      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
129431      * config is larger, it will be used instead.
129432      */
129433     keyIncrement: 1,
129434
129435     /**
129436      * @cfg {Number} increment
129437      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
129438      */
129439     increment: 0,
129440
129441     /**
129442      * @private
129443      * @property {Number[]} clickRange
129444      * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom],
129445      * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top'
129446      * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range
129447      */
129448     clickRange: [5,15],
129449
129450     /**
129451      * @cfg {Boolean} clickToChange
129452      * Determines whether or not clicking on the Slider axis will change the slider.
129453      */
129454     clickToChange : true,
129455
129456     /**
129457      * @cfg {Boolean} animate
129458      * Turn on or off animation.
129459      */
129460     animate: true,
129461
129462     /**
129463      * @property {Boolean} dragging
129464      * True while the thumb is in a drag operation
129465      */
129466     dragging: false,
129467
129468     /**
129469      * @cfg {Boolean} constrainThumbs
129470      * True to disallow thumbs from overlapping one another.
129471      */
129472     constrainThumbs: true,
129473
129474     componentLayout: 'sliderfield',
129475
129476     /**
129477      * @cfg {Boolean} useTips
129478      * True to use an Ext.slider.Tip to display tips for the value.
129479      */
129480     useTips : true,
129481
129482     /**
129483      * @cfg {Function} tipText
129484      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
129485      * plugin.
129486      */
129487     tipText : null,
129488
129489     ariaRole: 'slider',
129490
129491     // private override
129492     initValue: function() {
129493         var me = this,
129494             extValue = Ext.value,
129495             // Fallback for initial values: values config -> value config -> minValue config -> 0
129496             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
129497             i = 0,
129498             len = values.length;
129499
129500         // Store for use in dirty check
129501         me.originalValue = values;
129502
129503         // Add a thumb for each value
129504         for (; i < len; i++) {
129505             me.addThumb(values[i]);
129506         }
129507     },
129508
129509     // private override
129510     initComponent : function() {
129511         var me = this,
129512             tipPlug,
129513             hasTip;
129514
129515         /**
129516          * @property {Array} thumbs
129517          * Array containing references to each thumb
129518          */
129519         me.thumbs = [];
129520
129521         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
129522
129523         me.addEvents(
129524             /**
129525              * @event beforechange
129526              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
129527              * event and prevent the slider from changing.
129528              * @param {Ext.slider.Multi} slider The slider
129529              * @param {Number} newValue The new value which the slider is being changed to.
129530              * @param {Number} oldValue The old value which the slider was previously.
129531              */
129532             'beforechange',
129533
129534             /**
129535              * @event change
129536              * Fires when the slider value is changed.
129537              * @param {Ext.slider.Multi} slider The slider
129538              * @param {Number} newValue The new value which the slider has been changed to.
129539              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129540              */
129541             'change',
129542
129543             /**
129544              * @event changecomplete
129545              * Fires when the slider value is changed by the user and any drag operations have completed.
129546              * @param {Ext.slider.Multi} slider The slider
129547              * @param {Number} newValue The new value which the slider has been changed to.
129548              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129549              */
129550             'changecomplete',
129551
129552             /**
129553              * @event dragstart
129554              * Fires after a drag operation has started.
129555              * @param {Ext.slider.Multi} slider The slider
129556              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129557              */
129558             'dragstart',
129559
129560             /**
129561              * @event drag
129562              * Fires continuously during the drag operation while the mouse is moving.
129563              * @param {Ext.slider.Multi} slider The slider
129564              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129565              */
129566             'drag',
129567
129568             /**
129569              * @event dragend
129570              * Fires after the drag operation has completed.
129571              * @param {Ext.slider.Multi} slider The slider
129572              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129573              */
129574             'dragend'
129575         );
129576
129577         if (me.vertical) {
129578             Ext.apply(me, Ext.slider.Multi.Vertical);
129579         }
129580
129581         me.callParent();
129582
129583         // only can use it if it exists.
129584         if (me.useTips) {
129585             tipPlug = me.tipText ? {getText: me.tipText} : {};
129586             me.plugins = me.plugins || [];
129587             Ext.each(me.plugins, function(plug){
129588                 if (plug.isSliderTip) {
129589                     hasTip = true;
129590                     return false;
129591                 }
129592             });
129593             if (!hasTip) {
129594                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
129595             }
129596         }
129597     },
129598
129599     /**
129600      * Creates a new thumb and adds it to the slider
129601      * @param {Number} value The initial value to set on the thumb. Defaults to 0
129602      * @return {Ext.slider.Thumb} The thumb
129603      */
129604     addThumb: function(value) {
129605         var me = this,
129606             thumb = Ext.create('Ext.slider.Thumb', {
129607             value    : value,
129608             slider   : me,
129609             index    : me.thumbs.length,
129610             constrain: me.constrainThumbs
129611         });
129612         me.thumbs.push(thumb);
129613
129614         //render the thumb now if needed
129615         if (me.rendered) {
129616             thumb.render();
129617         }
129618
129619         return thumb;
129620     },
129621
129622     /**
129623      * @private
129624      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
129625      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
129626      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
129627      * range, which can result in the user not being able to move any of them.
129628      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
129629      */
129630     promoteThumb: function(topThumb) {
129631         var thumbs = this.thumbs,
129632             ln = thumbs.length,
129633             zIndex, thumb, i;
129634
129635         for (i = 0; i < ln; i++) {
129636             thumb = thumbs[i];
129637
129638             if (thumb == topThumb) {
129639                 thumb.bringToFront();
129640             } else {
129641                 thumb.sendToBack();
129642             }
129643         }
129644     },
129645
129646     // private override
129647     onRender : function() {
129648         var me = this,
129649             i = 0,
129650             thumbs = me.thumbs,
129651             len = thumbs.length,
129652             thumb;
129653
129654         Ext.applyIf(me.subTplData, {
129655             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
129656             minValue: me.minValue,
129657             maxValue: me.maxValue,
129658             value: me.value
129659         });
129660
129661         me.addChildEls('endEl', 'innerEl', 'focusEl');
129662
129663         me.callParent(arguments);
129664
129665         //render each thumb
129666         for (; i < len; i++) {
129667             thumbs[i].render();
129668         }
129669
129670         //calculate the size of half a thumb
129671         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
129672         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
129673
129674     },
129675
129676     /**
129677      * Utility method to set the value of the field when the slider changes.
129678      * @param {Object} slider The slider object.
129679      * @param {Object} v The new value.
129680      * @private
129681      */
129682     onChange : function(slider, v) {
129683         this.setValue(v, undefined, true);
129684     },
129685
129686     /**
129687      * @private
129688      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
129689      */
129690     initEvents : function() {
129691         var me = this;
129692
129693         me.mon(me.el, {
129694             scope    : me,
129695             mousedown: me.onMouseDown,
129696             keydown  : me.onKeyDown,
129697             change : me.onChange
129698         });
129699
129700         me.focusEl.swallowEvent("click", true);
129701     },
129702
129703     /**
129704      * @private
129705      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
129706      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
129707      * @param {Ext.EventObject} e The click event
129708      */
129709     onMouseDown : function(e) {
129710         var me = this,
129711             thumbClicked = false,
129712             i = 0,
129713             thumbs = me.thumbs,
129714             len = thumbs.length,
129715             local;
129716
129717         if (me.disabled) {
129718             return;
129719         }
129720
129721         //see if the click was on any of the thumbs
129722         for (; i < len; i++) {
129723             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
129724         }
129725
129726         if (me.clickToChange && !thumbClicked) {
129727             local = me.innerEl.translatePoints(e.getXY());
129728             me.onClickChange(local);
129729         }
129730         me.focus();
129731     },
129732
129733     /**
129734      * @private
129735      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
129736      * Only changes the value if the click was within this.clickRange.
129737      * @param {Object} local Object containing top and left values for the click event.
129738      */
129739     onClickChange : function(local) {
129740         var me = this,
129741             thumb, index;
129742
129743         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
129744             //find the nearest thumb to the click event
129745             thumb = me.getNearest(local, 'left');
129746             if (!thumb.disabled) {
129747                 index = thumb.index;
129748                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
129749             }
129750         }
129751     },
129752
129753     /**
129754      * @private
129755      * Returns the nearest thumb to a click event, along with its distance
129756      * @param {Object} local Object containing top and left values from a click event
129757      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
129758      * @return {Object} The closest thumb object and its distance from the click event
129759      */
129760     getNearest: function(local, prop) {
129761         var me = this,
129762             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
129763             clickValue = me.reverseValue(localValue),
129764             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
129765             index = 0,
129766             nearest = null,
129767             thumbs = me.thumbs,
129768             i = 0,
129769             len = thumbs.length,
129770             thumb,
129771             value,
129772             dist;
129773
129774         for (; i < len; i++) {
129775             thumb = me.thumbs[i];
129776             value = thumb.value;
129777             dist  = Math.abs(value - clickValue);
129778
129779             if (Math.abs(dist <= nearestDistance)) {
129780                 nearest = thumb;
129781                 index = i;
129782                 nearestDistance = dist;
129783             }
129784         }
129785         return nearest;
129786     },
129787
129788     /**
129789      * @private
129790      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
129791      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
129792      * @param {Ext.EventObject} e The Event object
129793      */
129794     onKeyDown : function(e) {
129795         /*
129796          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
129797          * There's no real sane default for it, so leave it like this until we come up
129798          * with a better way of doing it.
129799          */
129800         var me = this,
129801             k,
129802             val;
129803
129804         if(me.disabled || me.thumbs.length !== 1) {
129805             e.preventDefault();
129806             return;
129807         }
129808         k = e.getKey();
129809
129810         switch(k) {
129811             case e.UP:
129812             case e.RIGHT:
129813                 e.stopEvent();
129814                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
129815                 me.setValue(0, val, undefined, true);
129816             break;
129817             case e.DOWN:
129818             case e.LEFT:
129819                 e.stopEvent();
129820                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
129821                 me.setValue(0, val, undefined, true);
129822             break;
129823             default:
129824                 e.preventDefault();
129825         }
129826     },
129827
129828     // private
129829     afterRender : function() {
129830         var me = this,
129831             i = 0,
129832             thumbs = me.thumbs,
129833             len = thumbs.length,
129834             thumb,
129835             v;
129836
129837         me.callParent(arguments);
129838
129839         for (; i < len; i++) {
129840             thumb = thumbs[i];
129841
129842             if (thumb.value !== undefined) {
129843                 v = me.normalizeValue(thumb.value);
129844                 if (v !== thumb.value) {
129845                     // delete this.value;
129846                     me.setValue(i, v, false);
129847                 } else {
129848                     thumb.move(me.translateValue(v), false);
129849                 }
129850             }
129851         }
129852     },
129853
129854     /**
129855      * @private
129856      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
129857      * the ratio is 2
129858      * @return {Number} The ratio of pixels to mapped values
129859      */
129860     getRatio : function() {
129861         var w = this.innerEl.getWidth(),
129862             v = this.maxValue - this.minValue;
129863         return v === 0 ? w : (w/v);
129864     },
129865
129866     /**
129867      * @private
129868      * Returns a snapped, constrained value when given a desired value
129869      * @param {Number} value Raw number value
129870      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
129871      */
129872     normalizeValue : function(v) {
129873         var me = this;
129874
129875         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
129876         v = Ext.util.Format.round(v, me.decimalPrecision);
129877         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
129878         return v;
129879     },
129880
129881     /**
129882      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
129883      * value will be changed.
129884      * @param {Number} val The new minimum value
129885      */
129886     setMinValue : function(val) {
129887         var me = this,
129888             i = 0,
129889             thumbs = me.thumbs,
129890             len = thumbs.length,
129891             t;
129892
129893         me.minValue = val;
129894         if (me.rendered) {
129895             me.inputEl.dom.setAttribute('aria-valuemin', val);
129896         }
129897
129898         for (; i < len; ++i) {
129899             t = thumbs[i];
129900             t.value = t.value < val ? val : t.value;
129901         }
129902         me.syncThumbs();
129903     },
129904
129905     /**
129906      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
129907      * value will be changed.
129908      * @param {Number} val The new maximum value
129909      */
129910     setMaxValue : function(val) {
129911         var me = this,
129912             i = 0,
129913             thumbs = me.thumbs,
129914             len = thumbs.length,
129915             t;
129916
129917         me.maxValue = val;
129918         if (me.rendered) {
129919             me.inputEl.dom.setAttribute('aria-valuemax', val);
129920         }
129921
129922         for (; i < len; ++i) {
129923             t = thumbs[i];
129924             t.value = t.value > val ? val : t.value;
129925         }
129926         me.syncThumbs();
129927     },
129928
129929     /**
129930      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
129931      * maxValue.
129932      * @param {Number} index Index of the thumb to move
129933      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
129934      * @param {Boolean} [animate=true] Turn on or off animation
129935      */
129936     setValue : function(index, value, animate, changeComplete) {
129937         var me = this,
129938             thumb = me.thumbs[index];
129939
129940         // ensures value is contstrained and snapped
129941         value = me.normalizeValue(value);
129942
129943         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
129944             thumb.value = value;
129945             if (me.rendered) {
129946                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
129947                 // Perhaps this should go on each thumb element rather than the outer element.
129948                 me.inputEl.set({
129949                     'aria-valuenow': value,
129950                     'aria-valuetext': value
129951                 });
129952
129953                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
129954
129955                 me.fireEvent('change', me, value, thumb);
129956                 if (changeComplete) {
129957                     me.fireEvent('changecomplete', me, value, thumb);
129958                 }
129959             }
129960         }
129961     },
129962
129963     /**
129964      * @private
129965      */
129966     translateValue : function(v) {
129967         var ratio = this.getRatio();
129968         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
129969     },
129970
129971     /**
129972      * @private
129973      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
129974      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
129975      * returns 200
129976      * @param {Number} pos The position along the slider to return a mapped value for
129977      * @return {Number} The mapped value for the given position
129978      */
129979     reverseValue : function(pos) {
129980         var ratio = this.getRatio();
129981         return (pos + (this.minValue * ratio)) / ratio;
129982     },
129983
129984     // private
129985     focus : function() {
129986         this.focusEl.focus(10);
129987     },
129988
129989     //private
129990     onDisable: function() {
129991         var me = this,
129992             i = 0,
129993             thumbs = me.thumbs,
129994             len = thumbs.length,
129995             thumb,
129996             el,
129997             xy;
129998
129999         me.callParent();
130000
130001         for (; i < len; i++) {
130002             thumb = thumbs[i];
130003             el = thumb.el;
130004
130005             thumb.disable();
130006
130007             if(Ext.isIE) {
130008                 //IE breaks when using overflow visible and opacity other than 1.
130009                 //Create a place holder for the thumb and display it.
130010                 xy = el.getXY();
130011                 el.hide();
130012
130013                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
130014
130015                 if (!me.thumbHolder) {
130016                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
130017                 }
130018
130019                 me.thumbHolder.show().setXY(xy);
130020             }
130021         }
130022     },
130023
130024     //private
130025     onEnable: function() {
130026         var me = this,
130027             i = 0,
130028             thumbs = me.thumbs,
130029             len = thumbs.length,
130030             thumb,
130031             el;
130032
130033         this.callParent();
130034
130035         for (; i < len; i++) {
130036             thumb = thumbs[i];
130037             el = thumb.el;
130038
130039             thumb.enable();
130040
130041             if (Ext.isIE) {
130042                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
130043
130044                 if (me.thumbHolder) {
130045                     me.thumbHolder.hide();
130046                 }
130047
130048                 el.show();
130049                 me.syncThumbs();
130050             }
130051         }
130052     },
130053
130054     /**
130055      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
130056      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
130057      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
130058      */
130059     syncThumbs : function() {
130060         if (this.rendered) {
130061             var thumbs = this.thumbs,
130062                 length = thumbs.length,
130063                 i = 0;
130064
130065             for (; i < length; i++) {
130066                 thumbs[i].move(this.translateValue(thumbs[i].value));
130067             }
130068         }
130069     },
130070
130071     /**
130072      * Returns the current value of the slider
130073      * @param {Number} index The index of the thumb to return a value for
130074      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
130075      * no index is given.
130076      */
130077     getValue : function(index) {
130078         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
130079     },
130080
130081     /**
130082      * Returns an array of values - one for the location of each thumb
130083      * @return {Number[]} The set of thumb values
130084      */
130085     getValues: function() {
130086         var values = [],
130087             i = 0,
130088             thumbs = this.thumbs,
130089             len = thumbs.length;
130090
130091         for (; i < len; i++) {
130092             values.push(thumbs[i].value);
130093         }
130094
130095         return values;
130096     },
130097
130098     getSubmitValue: function() {
130099         var me = this;
130100         return (me.disabled || !me.submitValue) ? null : me.getValue();
130101     },
130102
130103     reset: function() {
130104         var me = this,
130105             Array = Ext.Array;
130106         Array.forEach(Array.from(me.originalValue), function(val, i) {
130107             me.setValue(i, val);
130108         });
130109         me.clearInvalid();
130110         // delete here so we reset back to the original state
130111         delete me.wasValid;
130112     },
130113
130114     // private
130115     beforeDestroy : function() {
130116         var me = this;
130117
130118         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
130119         Ext.each(me.thumbs, function(thumb) {
130120             Ext.destroy(thumb);
130121         }, me);
130122
130123         me.callParent();
130124     },
130125
130126     statics: {
130127         // Method overrides to support slider with vertical orientation
130128         Vertical: {
130129             getRatio : function() {
130130                 var h = this.innerEl.getHeight(),
130131                     v = this.maxValue - this.minValue;
130132                 return h/v;
130133             },
130134
130135             onClickChange : function(local) {
130136                 var me = this,
130137                     thumb, index, bottom;
130138
130139                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
130140                     thumb = me.getNearest(local, 'top');
130141                     if (!thumb.disabled) {
130142                         index = thumb.index;
130143                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
130144
130145                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
130146                     }
130147                 }
130148             }
130149         }
130150     }
130151 });
130152
130153 /**
130154  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130155  * and animation. Can be added as an item to any container.
130156  *
130157  *     @example
130158  *     Ext.create('Ext.slider.Single', {
130159  *         width: 200,
130160  *         value: 50,
130161  *         increment: 10,
130162  *         minValue: 0,
130163  *         maxValue: 100,
130164  *         renderTo: Ext.getBody()
130165  *     });
130166  *
130167  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
130168  */
130169 Ext.define('Ext.slider.Single', {
130170     extend: 'Ext.slider.Multi',
130171     alias: ['widget.slider', 'widget.sliderfield'],
130172     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
130173
130174     /**
130175      * Returns the current value of the slider
130176      * @return {Number} The current value of the slider
130177      */
130178     getValue: function() {
130179         // just returns the value of the first thumb, which should be the only one in a single slider
130180         return this.callParent([0]);
130181     },
130182
130183     /**
130184      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
130185      * maxValue.
130186      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
130187      * @param {Boolean} [animate] Turn on or off animation
130188      */
130189     setValue: function(value, animate) {
130190         var args = Ext.toArray(arguments),
130191             len  = args.length;
130192
130193         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
130194         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
130195         // signature without the required index. The index will always be 0 for a single slider
130196         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
130197             args.unshift(0);
130198         }
130199
130200         return this.callParent(args);
130201     },
130202
130203     // private
130204     getNearest : function(){
130205         // Since there's only 1 thumb, it's always the nearest
130206         return this.thumbs[0];
130207     }
130208 });
130209
130210 /**
130211  * @author Ed Spencer
130212  * @class Ext.tab.Tab
130213  * @extends Ext.button.Button
130214  *
130215  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
130216  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
130217  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
130218  */
130219 Ext.define('Ext.tab.Tab', {
130220     extend: 'Ext.button.Button',
130221     alias: 'widget.tab',
130222
130223     requires: [
130224         'Ext.layout.component.Tab',
130225         'Ext.util.KeyNav'
130226     ],
130227
130228     componentLayout: 'tab',
130229
130230     isTab: true,
130231
130232     baseCls: Ext.baseCSSPrefix + 'tab',
130233
130234     /**
130235      * @cfg {String} activeCls
130236      * The CSS class to be applied to a Tab when it is active.
130237      * Providing your own CSS for this class enables you to customize the active state.
130238      */
130239     activeCls: 'active',
130240
130241     /**
130242      * @cfg {String} disabledCls
130243      * The CSS class to be applied to a Tab when it is disabled.
130244      */
130245
130246     /**
130247      * @cfg {String} closableCls
130248      * The CSS class which is added to the tab when it is closable
130249      */
130250     closableCls: 'closable',
130251
130252     /**
130253      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
130254      */
130255     closable: true,
130256
130257     /**
130258      * @cfg {String} closeText
130259      * The accessible text label for the close button link; only used when {@link #closable} = true.
130260      */
130261     closeText: 'Close Tab',
130262
130263     /**
130264      * @property {Boolean} active
130265      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
130266      */
130267     active: false,
130268
130269     /**
130270      * @property closable
130271      * @type Boolean
130272      * True if the tab is currently closable
130273      */
130274
130275     scale: false,
130276
130277     position: 'top',
130278
130279     initComponent: function() {
130280         var me = this;
130281
130282         me.addEvents(
130283             /**
130284              * @event activate
130285              * Fired when the tab is activated.
130286              * @param {Ext.tab.Tab} this
130287              */
130288             'activate',
130289
130290             /**
130291              * @event deactivate
130292              * Fired when the tab is deactivated.
130293              * @param {Ext.tab.Tab} this
130294              */
130295             'deactivate',
130296
130297             /**
130298              * @event beforeclose
130299              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
130300              * false from any listener to stop the close event being fired
130301              * @param {Ext.tab.Tab} tab The Tab object
130302              */
130303             'beforeclose',
130304
130305             /**
130306              * @event close
130307              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
130308              * @param {Ext.tab.Tab} tab The Tab object
130309              */
130310             'close'
130311         );
130312
130313         me.callParent(arguments);
130314
130315         if (me.card) {
130316             me.setCard(me.card);
130317         }
130318     },
130319
130320     /**
130321      * @ignore
130322      */
130323     onRender: function() {
130324         var me = this,
130325             tabBar = me.up('tabbar'),
130326             tabPanel = me.up('tabpanel');
130327
130328         me.addClsWithUI(me.position);
130329
130330         // Set all the state classNames, as they need to include the UI
130331         // me.disabledCls = me.getClsWithUIs('disabled');
130332
130333         me.syncClosableUI();
130334
130335         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
130336         if (!me.minWidth) {
130337             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
130338             if (!me.minWidth && tabPanel) {
130339                 me.minWidth = tabPanel.minTabWidth;
130340             }
130341             if (me.minWidth && me.iconCls) {
130342                 me.minWidth += 25;
130343             }
130344         }
130345         if (!me.maxWidth) {
130346             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
130347             if (!me.maxWidth && tabPanel) {
130348                 me.maxWidth = tabPanel.maxTabWidth;
130349             }
130350         }
130351
130352         me.callParent(arguments);
130353
130354         if (me.active) {
130355             me.activate(true);
130356         }
130357
130358         me.syncClosableElements();
130359
130360         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
130361             enter: me.onEnterKey,
130362             del: me.onDeleteKey,
130363             scope: me
130364         });
130365     },
130366
130367     // inherit docs
130368     enable : function(silent) {
130369         var me = this;
130370
130371         me.callParent(arguments);
130372
130373         me.removeClsWithUI(me.position + '-disabled');
130374
130375         return me;
130376     },
130377
130378     // inherit docs
130379     disable : function(silent) {
130380         var me = this;
130381
130382         me.callParent(arguments);
130383
130384         me.addClsWithUI(me.position + '-disabled');
130385
130386         return me;
130387     },
130388
130389     /**
130390      * @ignore
130391      */
130392     onDestroy: function() {
130393         var me = this;
130394
130395         if (me.closeEl) {
130396             me.closeEl.un('click', Ext.EventManager.preventDefault);
130397             me.closeEl = null;
130398         }
130399
130400         Ext.destroy(me.keyNav);
130401         delete me.keyNav;
130402
130403         me.callParent(arguments);
130404     },
130405
130406     /**
130407      * Sets the tab as either closable or not
130408      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
130409      * close button will appear on the tab)
130410      */
130411     setClosable: function(closable) {
130412         var me = this;
130413
130414         // Closable must be true if no args
130415         closable = (!arguments.length || !!closable);
130416
130417         if (me.closable != closable) {
130418             me.closable = closable;
130419
130420             // set property on the user-facing item ('card'):
130421             if (me.card) {
130422                 me.card.closable = closable;
130423             }
130424
130425             me.syncClosableUI();
130426
130427             if (me.rendered) {
130428                 me.syncClosableElements();
130429
130430                 // Tab will change width to accommodate close icon
130431                 me.doComponentLayout();
130432                 if (me.ownerCt) {
130433                     me.ownerCt.doLayout();
130434                 }
130435             }
130436         }
130437     },
130438
130439     /**
130440      * This method ensures that the closeBtn element exists or not based on 'closable'.
130441      * @private
130442      */
130443     syncClosableElements: function () {
130444         var me = this;
130445
130446         if (me.closable) {
130447             if (!me.closeEl) {
130448                 me.closeEl = me.el.createChild({
130449                     tag: 'a',
130450                     cls: me.baseCls + '-close-btn',
130451                     href: '#',
130452                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
130453                     title: me.closeText
130454                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
130455             }
130456         } else {
130457             var closeEl = me.closeEl;
130458             if (closeEl) {
130459                 closeEl.un('click', Ext.EventManager.preventDefault);
130460                 closeEl.remove();
130461                 me.closeEl = null;
130462             }
130463         }
130464     },
130465
130466     /**
130467      * This method ensures that the UI classes are added or removed based on 'closable'.
130468      * @private
130469      */
130470     syncClosableUI: function () {
130471         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
130472
130473         if (me.closable) {
130474             me.addClsWithUI(classes);
130475         } else {
130476             me.removeClsWithUI(classes);
130477         }
130478     },
130479
130480     /**
130481      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
130482      * belongs to and would not need to be done by the developer
130483      * @param {Ext.Component} card The card to set
130484      */
130485     setCard: function(card) {
130486         var me = this;
130487
130488         me.card = card;
130489         me.setText(me.title || card.title);
130490         me.setIconCls(me.iconCls || card.iconCls);
130491     },
130492
130493     /**
130494      * @private
130495      * Listener attached to click events on the Tab's close button
130496      */
130497     onCloseClick: function() {
130498         var me = this;
130499
130500         if (me.fireEvent('beforeclose', me) !== false) {
130501             if (me.tabBar) {
130502                 if (me.tabBar.closeTab(me) === false) {
130503                     // beforeclose on the panel vetoed the event, stop here
130504                     return;
130505                 }
130506             } else {
130507                 // if there's no tabbar, fire the close event
130508                 me.fireEvent('close', me);
130509             }
130510         }
130511     },
130512
130513     /**
130514      * Fires the close event on the tab.
130515      * @private
130516      */
130517     fireClose: function(){
130518         this.fireEvent('close', this);
130519     },
130520
130521     /**
130522      * @private
130523      */
130524     onEnterKey: function(e) {
130525         var me = this;
130526
130527         if (me.tabBar) {
130528             me.tabBar.onClick(e, me.el);
130529         }
130530     },
130531
130532    /**
130533      * @private
130534      */
130535     onDeleteKey: function(e) {
130536         var me = this;
130537
130538         if (me.closable) {
130539             me.onCloseClick();
130540         }
130541     },
130542
130543     // @private
130544     activate : function(supressEvent) {
130545         var me = this;
130546
130547         me.active = true;
130548         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130549
130550         if (supressEvent !== true) {
130551             me.fireEvent('activate', me);
130552         }
130553     },
130554
130555     // @private
130556     deactivate : function(supressEvent) {
130557         var me = this;
130558
130559         me.active = false;
130560         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130561
130562         if (supressEvent !== true) {
130563             me.fireEvent('deactivate', me);
130564         }
130565     }
130566 });
130567
130568 /**
130569  * @author Ed Spencer
130570  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
130571  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
130572  */
130573 Ext.define('Ext.tab.Bar', {
130574     extend: 'Ext.panel.Header',
130575     alias: 'widget.tabbar',
130576     baseCls: Ext.baseCSSPrefix + 'tab-bar',
130577
130578     requires: [
130579         'Ext.tab.Tab',
130580         'Ext.FocusManager'
130581     ],
130582
130583     isTabBar: true,
130584     
130585     /**
130586      * @cfg {String} title @hide
130587      */
130588     
130589     /**
130590      * @cfg {String} iconCls @hide
130591      */
130592
130593     // @private
130594     defaultType: 'tab',
130595
130596     /**
130597      * @cfg {Boolean} plain
130598      * True to not show the full background on the tabbar
130599      */
130600     plain: false,
130601
130602     // @private
130603     renderTpl: [
130604         '<div id="{id}-body" class="{baseCls}-body <tpl if="bodyCls"> {bodyCls}</tpl> <tpl if="ui"> {baseCls}-body-{ui}<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>',
130605         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
130606     ],
130607
130608     /**
130609      * @cfg {Number} minTabWidth
130610      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
130611      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
130612      */
130613
130614     /**
130615      * @cfg {Number} maxTabWidth
130616      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
130617      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
130618      */
130619
130620     // @private
130621     initComponent: function() {
130622         var me = this,
130623             keys;
130624
130625         if (me.plain) {
130626             me.setUI(me.ui + '-plain');
130627         }
130628
130629         me.addClsWithUI(me.dock);
130630
130631         me.addEvents(
130632             /**
130633              * @event change
130634              * Fired when the currently-active tab has changed
130635              * @param {Ext.tab.Bar} tabBar The TabBar
130636              * @param {Ext.tab.Tab} tab The new Tab
130637              * @param {Ext.Component} card The card that was just shown in the TabPanel
130638              */
130639             'change'
130640         );
130641
130642         me.addChildEls('body', 'strip');
130643         me.callParent(arguments);
130644
130645         // TabBar must override the Header's align setting.
130646         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
130647         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
130648
130649         me.remove(me.titleCmp);
130650         delete me.titleCmp;
130651
130652         // Subscribe to Ext.FocusManager for key navigation
130653         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
130654         Ext.FocusManager.subscribe(me, {
130655             keys: keys
130656         });
130657
130658         Ext.apply(me.renderData, {
130659             bodyCls: me.bodyCls
130660         });
130661     },
130662
130663     // @private
130664     onAdd: function(tab) {
130665         tab.position = this.dock;
130666         this.callParent(arguments);
130667     },
130668     
130669     onRemove: function(tab) {
130670         var me = this;
130671         
130672         if (tab === me.previousTab) {
130673             me.previousTab = null;
130674         }
130675         if (me.items.getCount() === 0) {
130676             me.activeTab = null;
130677         }
130678         me.callParent(arguments);    
130679     },
130680
130681     // @private
130682     afterRender: function() {
130683         var me = this;
130684
130685         me.mon(me.el, {
130686             scope: me,
130687             click: me.onClick,
130688             delegate: '.' + Ext.baseCSSPrefix + 'tab'
130689         });
130690         me.callParent(arguments);
130691
130692     },
130693
130694     afterComponentLayout : function() {
130695         var me = this;
130696
130697         me.callParent(arguments);
130698         me.strip.setWidth(me.el.getWidth());
130699     },
130700
130701     // @private
130702     onClick: function(e, target) {
130703         // The target might not be a valid tab el.
130704         var tab = Ext.getCmp(target.id),
130705             tabPanel = this.tabPanel;
130706
130707         target = e.getTarget();
130708
130709         if (tab && tab.isDisabled && !tab.isDisabled()) {
130710             if (tab.closable && target === tab.closeEl.dom) {
130711                 tab.onCloseClick();
130712             } else {
130713                 if (tabPanel) {
130714                     // TabPanel will card setActiveTab of the TabBar
130715                     tabPanel.setActiveTab(tab.card);
130716                 } else {
130717                     this.setActiveTab(tab);
130718                 }
130719                 tab.focus();
130720             }
130721         }
130722     },
130723
130724     /**
130725      * @private
130726      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
130727      * @param {Ext.tab.Tab} tab The tab to close
130728      */
130729     closeTab: function(tab) {
130730         var me = this,
130731             card = tab.card,
130732             tabPanel = me.tabPanel,
130733             nextTab;
130734
130735         if (card && card.fireEvent('beforeclose', card) === false) {
130736             return false;
130737         }
130738
130739         if (tab.active && me.items.getCount() > 1) {
130740             nextTab = me.previousTab || tab.next('tab') || me.items.first();
130741             me.setActiveTab(nextTab);
130742             if (tabPanel) {
130743                 tabPanel.setActiveTab(nextTab.card);
130744             }
130745         }
130746         /*
130747          * force the close event to fire. By the time this function returns,
130748          * the tab is already destroyed and all listeners have been purged
130749          * so the tab can't fire itself.
130750          */
130751         tab.fireClose();
130752         me.remove(tab);
130753
130754         if (tabPanel && card) {
130755             card.fireEvent('close', card);
130756             tabPanel.remove(card);
130757         }
130758
130759         if (nextTab) {
130760             nextTab.focus();
130761         }
130762     },
130763
130764     /**
130765      * @private
130766      * Marks the given tab as active
130767      * @param {Ext.tab.Tab} tab The tab to mark active
130768      */
130769     setActiveTab: function(tab) {
130770         if (tab.disabled) {
130771             return;
130772         }
130773         var me = this;
130774         if (me.activeTab) {
130775             me.previousTab = me.activeTab;
130776             me.activeTab.deactivate();
130777         }
130778         tab.activate();
130779
130780         if (me.rendered) {
130781             me.layout.layout();
130782             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
130783         }
130784         me.activeTab = tab;
130785         me.fireEvent('change', me, tab, tab.card);
130786     }
130787 });
130788
130789 /**
130790  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
130791  *
130792  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
130793  * layout purposes, but also have special support for containing child Components
130794  * (`{@link Ext.container.Container#items items}`) that are managed using a
130795  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
130796  *
130797  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
130798  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
130799  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
130800  *
130801  * ## TabPanel's layout
130802  *
130803  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
130804  * Panels added to the TabPanel will have their header hidden by default because the Tab will
130805  * automatically take the Panel's configured title and icon.
130806  *
130807  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
130808  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
130809  * This means that a TabPanel will not display any configured title, and will not display any configured
130810  * header {@link Ext.panel.Panel#tools tools}.
130811  *
130812  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
130813  * `{@link Ext.container.Container#layout layout: 'fit'}`.
130814  *
130815  * ## Controlling tabs
130816  *
130817  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
130818  * by specifying the tabConfig option:
130819  *
130820  *     @example
130821  *     Ext.create('Ext.tab.Panel', {
130822  *         width: 400,
130823  *         height: 400,
130824  *         renderTo: document.body,
130825  *         items: [{
130826  *             title: 'Foo'
130827  *         }, {
130828  *             title: 'Bar',
130829  *             tabConfig: {
130830  *                 title: 'Custom Title',
130831  *                 tooltip: 'A button tooltip'
130832  *             }
130833  *         }]
130834  *     });
130835  *
130836  * # Examples
130837  *
130838  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
130839  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
130840  * active by default.
130841  *
130842  *     @example
130843  *     Ext.create('Ext.tab.Panel', {
130844  *         width: 300,
130845  *         height: 200,
130846  *         activeTab: 0,
130847  *         items: [
130848  *             {
130849  *                 title: 'Tab 1',
130850  *                 bodyPadding: 10,
130851  *                 html : 'A simple tab'
130852  *             },
130853  *             {
130854  *                 title: 'Tab 2',
130855  *                 html : 'Another one'
130856  *             }
130857  *         ],
130858  *         renderTo : Ext.getBody()
130859  *     });
130860  *
130861  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
130862  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
130863  * tab property on the child item.
130864  *
130865  *     @example
130866  *     var tabs = Ext.create('Ext.tab.Panel', {
130867  *         width: 400,
130868  *         height: 400,
130869  *         renderTo: document.body,
130870  *         items: [{
130871  *             title: 'Home',
130872  *             html: 'Home',
130873  *             itemId: 'home'
130874  *         }, {
130875  *             title: 'Users',
130876  *             html: 'Users',
130877  *             itemId: 'users',
130878  *             hidden: true
130879  *         }, {
130880  *             title: 'Tickets',
130881  *             html: 'Tickets',
130882  *             itemId: 'tickets'
130883  *         }]
130884  *     });
130885  *
130886  *     setTimeout(function(){
130887  *         tabs.child('#home').tab.hide();
130888  *         var users = tabs.child('#users');
130889  *         users.tab.show();
130890  *         tabs.setActiveTab(users);
130891  *     }, 1000);
130892  *
130893  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
130894  *
130895  *     @example
130896  *     Ext.create('Ext.tab.Panel', {
130897  *         width: 300,
130898  *         height: 200,
130899  *         activeTab: 0,
130900  *         plain: true,
130901  *         items: [
130902  *             {
130903  *                 title: 'Tab 1',
130904  *                 bodyPadding: 10,
130905  *                 html : 'A simple tab'
130906  *             },
130907  *             {
130908  *                 title: 'Tab 2',
130909  *                 html : 'Another one'
130910  *             }
130911  *         ],
130912  *         renderTo : Ext.getBody()
130913  *     });
130914  *
130915  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
130916  * position where the tabs are displayed. The available options for this are `'top'` (default) and
130917  * `'bottom'`.
130918  *
130919  *     @example
130920  *     Ext.create('Ext.tab.Panel', {
130921  *         width: 300,
130922  *         height: 200,
130923  *         activeTab: 0,
130924  *         bodyPadding: 10,
130925  *         tabPosition: 'bottom',
130926  *         items: [
130927  *             {
130928  *                 title: 'Tab 1',
130929  *                 html : 'A simple tab'
130930  *             },
130931  *             {
130932  *                 title: 'Tab 2',
130933  *                 html : 'Another one'
130934  *             }
130935  *         ],
130936  *         renderTo : Ext.getBody()
130937  *     });
130938  *
130939  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
130940  * current active tab. You can either give it an index or an instance of a tab. For example:
130941  *
130942  *     @example
130943  *     var tabs = Ext.create('Ext.tab.Panel', {
130944  *         items: [
130945  *             {
130946  *                 id   : 'my-tab',
130947  *                 title: 'Tab 1',
130948  *                 html : 'A simple tab'
130949  *             },
130950  *             {
130951  *                 title: 'Tab 2',
130952  *                 html : 'Another one'
130953  *             }
130954  *         ],
130955  *         renderTo : Ext.getBody()
130956  *     });
130957  *
130958  *     var tab = Ext.getCmp('my-tab');
130959  *
130960  *     Ext.create('Ext.button.Button', {
130961  *         renderTo: Ext.getBody(),
130962  *         text    : 'Select the first tab',
130963  *         scope   : this,
130964  *         handler : function() {
130965  *             tabs.setActiveTab(tab);
130966  *         }
130967  *     });
130968  *
130969  *     Ext.create('Ext.button.Button', {
130970  *         text    : 'Select the second tab',
130971  *         scope   : this,
130972  *         handler : function() {
130973  *             tabs.setActiveTab(1);
130974  *         },
130975  *         renderTo : Ext.getBody()
130976  *     });
130977  *
130978  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
130979  *
130980  *     @example
130981  *     var tabs = Ext.create('Ext.tab.Panel', {
130982  *         items: [
130983  *             {
130984  *                 title: 'Tab 1',
130985  *                 html : 'A simple tab'
130986  *             },
130987  *             {
130988  *                 title: 'Tab 2',
130989  *                 html : 'Another one'
130990  *             }
130991  *         ],
130992  *         renderTo : Ext.getBody()
130993  *     });
130994  *
130995  *     Ext.create('Ext.button.Button', {
130996  *         text    : 'Get active tab',
130997  *         scope   : this,
130998  *         handler : function() {
130999  *             var tab = tabs.getActiveTab();
131000  *             alert('Current tab: ' + tab.title);
131001  *         },
131002  *         renderTo : Ext.getBody()
131003  *     });
131004  *
131005  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
131006  * object for a panel.
131007  *
131008  *     @example
131009  *     var tabs = Ext.create('Ext.tab.Panel', {
131010  *         items: [
131011  *             {
131012  *                 title: 'Tab 1',
131013  *                 html : 'A simple tab'
131014  *             },
131015  *             {
131016  *                 title: 'Tab 2',
131017  *                 html : 'Another one'
131018  *             }
131019  *         ],
131020  *         renderTo : Ext.getBody()
131021  *     });
131022  *
131023  *     Ext.create('Ext.button.Button', {
131024  *         text    : 'New tab',
131025  *         scope   : this,
131026  *         handler : function() {
131027  *             var tab = tabs.add({
131028  *                 // we use the tabs.items property to get the length of current items/tabs
131029  *                 title: 'Tab ' + (tabs.items.length + 1),
131030  *                 html : 'Another one'
131031  *             });
131032  *
131033  *             tabs.setActiveTab(tab);
131034  *         },
131035  *         renderTo : Ext.getBody()
131036  *     });
131037  *
131038  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
131039  * with an config object for a panel.
131040  *
131041  *     @example
131042  *     var tabs = Ext.create('Ext.tab.Panel', {
131043  *         items: [
131044  *             {
131045  *                 title: 'Tab 1',
131046  *                 html : 'A simple tab'
131047  *             },
131048  *             {
131049  *                 id   : 'remove-this-tab',
131050  *                 title: 'Tab 2',
131051  *                 html : 'Another one'
131052  *             }
131053  *         ],
131054  *         renderTo : Ext.getBody()
131055  *     });
131056  *
131057  *     Ext.create('Ext.button.Button', {
131058  *         text    : 'Remove tab',
131059  *         scope   : this,
131060  *         handler : function() {
131061  *             var tab = Ext.getCmp('remove-this-tab');
131062  *             tabs.remove(tab);
131063  *         },
131064  *         renderTo : Ext.getBody()
131065  *     });
131066  */
131067 Ext.define('Ext.tab.Panel', {
131068     extend: 'Ext.panel.Panel',
131069     alias: 'widget.tabpanel',
131070     alternateClassName: ['Ext.TabPanel'],
131071
131072     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
131073
131074     /**
131075      * @cfg {String} tabPosition
131076      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
131077      */
131078     tabPosition : 'top',
131079
131080     /**
131081      * @cfg {String/Number} activeItem
131082      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
131083      */
131084
131085     /**
131086      * @cfg {String/Number/Ext.Component} activeTab
131087      * The tab to activate initially. Either an ID, index or the tab component itself.
131088      */
131089
131090     /**
131091      * @cfg {Object} tabBar
131092      * Optional configuration object for the internal {@link Ext.tab.Bar}.
131093      * If present, this is passed straight through to the TabBar's constructor
131094      */
131095
131096     /**
131097      * @cfg {Object} layout
131098      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
131099      * If present, this is passed straight through to the layout's constructor
131100      */
131101
131102     /**
131103      * @cfg {Boolean} removePanelHeader
131104      * True to instruct each Panel added to the TabContainer to not render its header element.
131105      * This is to ensure that the title of the panel does not appear twice.
131106      */
131107     removePanelHeader: true,
131108
131109     /**
131110      * @cfg {Boolean} plain
131111      * True to not show the full background on the TabBar.
131112      */
131113     plain: false,
131114
131115     /**
131116      * @cfg {String} itemCls
131117      * The class added to each child item of this TabPanel.
131118      */
131119     itemCls: 'x-tabpanel-child',
131120
131121     /**
131122      * @cfg {Number} minTabWidth
131123      * The minimum width for a tab in the {@link #tabBar}.
131124      */
131125     minTabWidth: undefined,
131126
131127     /**
131128      * @cfg {Number} maxTabWidth The maximum width for each tab.
131129      */
131130     maxTabWidth: undefined,
131131
131132     /**
131133      * @cfg {Boolean} deferredRender
131134      *
131135      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
131136      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
131137      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
131138      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
131139      * performance.
131140      *
131141      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
131142      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
131143      *
131144      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
131145      */
131146     deferredRender : true,
131147
131148     //inherit docs
131149     initComponent: function() {
131150         var me = this,
131151             dockedItems = me.dockedItems || [],
131152             activeTab = me.activeTab || 0;
131153
131154         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
131155             owner: me,
131156             deferredRender: me.deferredRender,
131157             itemCls: me.itemCls
131158         }, me.layout));
131159
131160         /**
131161          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
131162          */
131163         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
131164             dock: me.tabPosition,
131165             plain: me.plain,
131166             border: me.border,
131167             cardLayout: me.layout,
131168             tabPanel: me
131169         }));
131170
131171         if (dockedItems && !Ext.isArray(dockedItems)) {
131172             dockedItems = [dockedItems];
131173         }
131174
131175         dockedItems.push(me.tabBar);
131176         me.dockedItems = dockedItems;
131177
131178         me.addEvents(
131179             /**
131180              * @event
131181              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
131182              * the tabchange
131183              * @param {Ext.tab.Panel} tabPanel The TabPanel
131184              * @param {Ext.Component} newCard The card that is about to be activated
131185              * @param {Ext.Component} oldCard The card that is currently active
131186              */
131187             'beforetabchange',
131188
131189             /**
131190              * @event
131191              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
131192              * @param {Ext.tab.Panel} tabPanel The TabPanel
131193              * @param {Ext.Component} newCard The newly activated item
131194              * @param {Ext.Component} oldCard The previously active item
131195              */
131196             'tabchange'
131197         );
131198         me.callParent(arguments);
131199
131200         //set the active tab
131201         me.setActiveTab(activeTab);
131202         //set the active tab after initial layout
131203         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
131204     },
131205
131206     /**
131207      * @private
131208      * We have to wait until after the initial layout to visually activate the activeTab (if set).
131209      * The active tab has different margins than normal tabs, so if the initial layout happens with
131210      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
131211      * until after the initial layout avoids this issue.
131212      */
131213     afterInitialLayout: function() {
131214         var me = this,
131215             card = me.getComponent(me.activeTab);
131216
131217         if (card) {
131218             me.layout.setActiveItem(card);
131219         }
131220     },
131221
131222     /**
131223      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
131224      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
131225      */
131226     setActiveTab: function(card) {
131227         var me = this,
131228             previous;
131229
131230         card = me.getComponent(card);
131231         if (card) {
131232             previous = me.getActiveTab();
131233
131234             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
131235                 return false;
131236             }
131237
131238             me.tabBar.setActiveTab(card.tab);
131239             me.activeTab = card;
131240             if (me.rendered) {
131241                 me.layout.setActiveItem(card);
131242             }
131243
131244             if (previous && previous !== card) {
131245                 me.fireEvent('tabchange', me, card, previous);
131246             }
131247         }
131248     },
131249
131250     /**
131251      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
131252      * child component this will return whatever was configured in the {@link #activeTab} config option
131253      * @return {String/Number/Ext.Component} The currently active item
131254      */
131255     getActiveTab: function() {
131256         return this.activeTab;
131257     },
131258
131259     /**
131260      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
131261      * @return {Ext.tab.Bar} The TabBar
131262      */
131263     getTabBar: function() {
131264         return this.tabBar;
131265     },
131266
131267     /**
131268      * @ignore
131269      * Makes sure we have a Tab for each item added to the TabPanel
131270      */
131271     onAdd: function(item, index) {
131272         var me = this,
131273             cfg = item.tabConfig || {},
131274             defaultConfig = {
131275                 xtype: 'tab',
131276                 card: item,
131277                 disabled: item.disabled,
131278                 closable: item.closable,
131279                 hidden: item.hidden,
131280                 tabBar: me.tabBar
131281             };
131282
131283         if (item.closeText) {
131284             defaultConfig.closeText = item.closeText;
131285         }
131286         cfg = Ext.applyIf(cfg, defaultConfig);
131287         item.tab = me.tabBar.insert(index, cfg);
131288
131289         item.on({
131290             scope : me,
131291             enable: me.onItemEnable,
131292             disable: me.onItemDisable,
131293             beforeshow: me.onItemBeforeShow,
131294             iconchange: me.onItemIconChange,
131295             titlechange: me.onItemTitleChange
131296         });
131297
131298         if (item.isPanel) {
131299             if (me.removePanelHeader) {
131300                 item.preventHeader = true;
131301                 if (item.rendered) {
131302                     item.updateHeader();
131303                 }
131304             }
131305             if (item.isPanel && me.border) {
131306                 item.setBorder(false);
131307             }
131308         }
131309
131310         // ensure that there is at least one active tab
131311         if (this.rendered && me.items.getCount() === 1) {
131312             me.setActiveTab(0);
131313         }
131314     },
131315
131316     /**
131317      * @private
131318      * Enable corresponding tab when item is enabled.
131319      */
131320     onItemEnable: function(item){
131321         item.tab.enable();
131322     },
131323
131324     /**
131325      * @private
131326      * Disable corresponding tab when item is enabled.
131327      */
131328     onItemDisable: function(item){
131329         item.tab.disable();
131330     },
131331
131332     /**
131333      * @private
131334      * Sets activeTab before item is shown.
131335      */
131336     onItemBeforeShow: function(item) {
131337         if (item !== this.activeTab) {
131338             this.setActiveTab(item);
131339             return false;
131340         }
131341     },
131342
131343     /**
131344      * @private
131345      * Update the tab iconCls when panel iconCls has been set or changed.
131346      */
131347     onItemIconChange: function(item, newIconCls) {
131348         item.tab.setIconCls(newIconCls);
131349         this.getTabBar().doLayout();
131350     },
131351
131352     /**
131353      * @private
131354      * Update the tab title when panel title has been set or changed.
131355      */
131356     onItemTitleChange: function(item, newTitle) {
131357         item.tab.setText(newTitle);
131358         this.getTabBar().doLayout();
131359     },
131360
131361
131362     /**
131363      * @ignore
131364      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
131365      * so we can do preprocessing before then to find the card's index
131366      */
131367     doRemove: function(item, autoDestroy) {
131368         var me = this,
131369             items = me.items,
131370             // At this point the item hasn't been removed from the items collection.
131371             // As such, if we want to check if there are no more tabs left, we have to
131372             // check for one, as opposed to 0.
131373             hasItemsLeft = items.getCount() > 1;
131374
131375         if (me.destroying || !hasItemsLeft) {
131376             me.activeTab = null;
131377         } else if (item === me.activeTab) {
131378              me.setActiveTab(item.next() || items.getAt(0));
131379         }
131380         me.callParent(arguments);
131381
131382         // Remove the two references
131383         delete item.tab.card;
131384         delete item.tab;
131385     },
131386
131387     /**
131388      * @ignore
131389      * Makes sure we remove the corresponding Tab when an item is removed
131390      */
131391     onRemove: function(item, autoDestroy) {
131392         var me = this;
131393
131394         item.un({
131395             scope : me,
131396             enable: me.onItemEnable,
131397             disable: me.onItemDisable,
131398             beforeshow: me.onItemBeforeShow
131399         });
131400         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
131401             me.tabBar.remove(item.tab);
131402         }
131403     }
131404 });
131405
131406 /**
131407  * A simple element that adds extra horizontal space between items in a toolbar.
131408  * By default a 2px wide space is added via CSS specification:
131409  *
131410  *     .x-toolbar .x-toolbar-spacer {
131411  *         width: 2px;
131412  *     }
131413  *
131414  * Example:
131415  *
131416  *     @example
131417  *     Ext.create('Ext.panel.Panel', {
131418  *         title: 'Toolbar Spacer Example',
131419  *         width: 300,
131420  *         height: 200,
131421  *         tbar : [
131422  *             'Item 1',
131423  *             { xtype: 'tbspacer' }, // or ' '
131424  *             'Item 2',
131425  *             // space width is also configurable via javascript
131426  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
131427  *             'Item 3'
131428  *         ],
131429  *         renderTo: Ext.getBody()
131430  *     });
131431  */
131432 Ext.define('Ext.toolbar.Spacer', {
131433     extend: 'Ext.Component',
131434     alias: 'widget.tbspacer',
131435     alternateClassName: 'Ext.Toolbar.Spacer',
131436     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
131437     focusable: false
131438 });
131439 /**
131440  * @class Ext.tree.Column
131441  * @extends Ext.grid.column.Column
131442  * 
131443  * Provides indentation and folder structure markup for a Tree taking into account
131444  * depth and position within the tree hierarchy.
131445  * 
131446  * @private
131447  */
131448 Ext.define('Ext.tree.Column', {
131449     extend: 'Ext.grid.column.Column',
131450     alias: 'widget.treecolumn',
131451
131452     initComponent: function() {
131453         var origRenderer = this.renderer || this.defaultRenderer,
131454             origScope    = this.scope || window;
131455
131456         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
131457             var buf   = [],
131458                 format = Ext.String.format,
131459                 depth = record.getDepth(),
131460                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
131461                 elbowPrefix = treePrefix + 'elbow-',
131462                 expanderCls = treePrefix + 'expander',
131463                 imgText     = '<img src="{1}" class="{0}" />',
131464                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
131465                 formattedValue = origRenderer.apply(origScope, arguments),
131466                 href = record.get('href'),
131467                 target = record.get('hrefTarget'),
131468                 cls = record.get('cls');
131469
131470             while (record) {
131471                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
131472                     if (record.getDepth() === depth) {
131473                         buf.unshift(format(imgText,
131474                             treePrefix + 'icon ' + 
131475                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
131476                             (record.get('iconCls') || ''),
131477                             record.get('icon') || Ext.BLANK_IMAGE_URL
131478                         ));
131479                         if (record.get('checked') !== null) {
131480                             buf.unshift(format(
131481                                 checkboxText,
131482                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
131483                                 record.get('checked') ? 'aria-checked="true"' : ''
131484                             ));
131485                             if (record.get('checked')) {
131486                                 metaData.tdCls += (' ' + treePrefix + 'checked');
131487                             }
131488                         }
131489                         if (record.isLast()) {
131490                             if (record.isExpandable()) {
131491                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131492                             } else {
131493                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
131494                             }
131495                             
131496                         } else {
131497                             if (record.isExpandable()) {
131498                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131499                             } else {
131500                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
131501                             }
131502                         }
131503                     } else {
131504                         if (record.isLast() || record.getDepth() === 0) {
131505                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
131506                         } else if (record.getDepth() !== 0) {
131507                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
131508                         }                      
131509                     }
131510                 }
131511                 record = record.parentNode;
131512             }
131513             if (href) {
131514                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
131515             } else {
131516                 buf.push(formattedValue);
131517             }
131518             if (cls) {
131519                 metaData.tdCls += ' ' + cls;
131520             }
131521             return buf.join('');
131522         };
131523         this.callParent(arguments);
131524     },
131525
131526     defaultRenderer: function(value) {
131527         return value;
131528     }
131529 });
131530 /**
131531  * Used as a view by {@link Ext.tree.Panel TreePanel}.
131532  */
131533 Ext.define('Ext.tree.View', {
131534     extend: 'Ext.view.Table',
131535     alias: 'widget.treeview',
131536
131537     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
131538     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
131539
131540     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
131541     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
131542     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
131543
131544     // Class to add to the node wrap element used to hold nodes when a parent is being
131545     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
131546     // for an ancestor node with this class.
131547     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
131548
131549     blockRefresh: true,
131550
131551     /** 
131552      * @cfg {Boolean} rootVisible
131553      * False to hide the root node.
131554      */
131555     rootVisible: true,
131556
131557     /** 
131558      * @cfg {Boolean} animate
131559      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
131560      */
131561
131562     expandDuration: 250,
131563     collapseDuration: 250,
131564     
131565     toggleOnDblClick: true,
131566
131567     initComponent: function() {
131568         var me = this;
131569         
131570         if (me.initialConfig.animate === undefined) {
131571             me.animate = Ext.enableFx;
131572         }
131573         
131574         me.store = Ext.create('Ext.data.NodeStore', {
131575             recursive: true,
131576             rootVisible: me.rootVisible,
131577             listeners: {
131578                 beforeexpand: me.onBeforeExpand,
131579                 expand: me.onExpand,
131580                 beforecollapse: me.onBeforeCollapse,
131581                 collapse: me.onCollapse,
131582                 scope: me
131583             }
131584         });
131585         
131586         if (me.node) {
131587             me.setRootNode(me.node);
131588         }
131589         me.animQueue = {};
131590         me.callParent(arguments);
131591     },
131592
131593     processUIEvent: function(e) {
131594         // If the clicked node is part of an animation, ignore the click.
131595         // This is because during a collapse animation, the associated Records
131596         // will already have been removed from the Store, and the event is not processable.
131597         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
131598             return false;
131599         }
131600         return this.callParent(arguments);
131601     },
131602
131603     onClear: function(){
131604         this.store.removeAll();    
131605     },
131606
131607     setRootNode: function(node) {
131608         var me = this;        
131609         me.store.setNode(node);
131610         me.node = node;
131611         if (!me.rootVisible) {
131612             node.expand();
131613         }
131614     },
131615     
131616     onRender: function() {
131617         var me = this,
131618             el;
131619
131620         me.callParent(arguments);
131621
131622         el = me.el;
131623         el.on({
131624             scope: me,
131625             delegate: me.expanderSelector,
131626             mouseover: me.onExpanderMouseOver,
131627             mouseout: me.onExpanderMouseOut
131628         });
131629         el.on({
131630             scope: me,
131631             delegate: me.checkboxSelector,
131632             click: me.onCheckboxChange
131633         });
131634     },
131635
131636     onCheckboxChange: function(e, t) {
131637         var me = this,
131638             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
131639             
131640         if (item) {
131641             me.onCheckChange(me.getRecord(item));
131642         }
131643     },
131644     
131645     onCheckChange: function(record){
131646         var checked = record.get('checked');
131647         if (Ext.isBoolean(checked)) {
131648             checked = !checked;
131649             record.set('checked', checked);
131650             this.fireEvent('checkchange', record, checked);
131651         }
131652     },
131653
131654     getChecked: function() {
131655         var checked = [];
131656         this.node.cascadeBy(function(rec){
131657             if (rec.get('checked')) {
131658                 checked.push(rec);
131659             }
131660         });
131661         return checked;
131662     },
131663     
131664     isItemChecked: function(rec){
131665         return rec.get('checked');
131666     },
131667
131668     createAnimWrap: function(record, index) {
131669         var thHtml = '',
131670             headerCt = this.panel.headerCt,
131671             headers = headerCt.getGridColumns(),
131672             i = 0, len = headers.length, item,
131673             node = this.getNode(record),
131674             tmpEl, nodeEl;
131675
131676         for (; i < len; i++) {
131677             item = headers[i];
131678             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
131679         }
131680
131681         nodeEl = Ext.get(node);        
131682         tmpEl = nodeEl.insertSibling({
131683             tag: 'tr',
131684             html: [
131685                 '<td colspan="' + headerCt.getColumnCount() + '">',
131686                     '<div class="' + this.nodeAnimWrapCls + '">',
131687                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
131688                             thHtml,
131689                         '</tbody></table>',
131690                     '</div>',
131691                 '</td>'
131692             ].join('')
131693         }, 'after');
131694
131695         return {
131696             record: record,
131697             node: node,
131698             el: tmpEl,
131699             expanding: false,
131700             collapsing: false,
131701             animating: false,
131702             animateEl: tmpEl.down('div'),
131703             targetEl: tmpEl.down('tbody')
131704         };
131705     },
131706
131707     getAnimWrap: function(parent) {
131708         if (!this.animate) {
131709             return null;
131710         }
131711
131712         // We are checking to see which parent is having the animation wrap
131713         while (parent) {
131714             if (parent.animWrap) {
131715                 return parent.animWrap;
131716             }
131717             parent = parent.parentNode;
131718         }
131719         return null;
131720     },
131721
131722     doAdd: function(nodes, records, index) {
131723         // If we are adding records which have a parent that is currently expanding
131724         // lets add them to the animation wrap
131725         var me = this,
131726             record = records[0],
131727             parent = record.parentNode,
131728             a = me.all.elements,
131729             relativeIndex = 0,
131730             animWrap = me.getAnimWrap(parent),
131731             targetEl, children, len;
131732
131733         if (!animWrap || !animWrap.expanding) {
131734             me.resetScrollers();
131735             return me.callParent(arguments);
131736         }
131737
131738         // We need the parent that has the animWrap, not the nodes parent
131739         parent = animWrap.record;
131740         
131741         // If there is an anim wrap we do our special magic logic
131742         targetEl = animWrap.targetEl;
131743         children = targetEl.dom.childNodes;
131744         
131745         // We subtract 1 from the childrens length because we have a tr in there with the th'es
131746         len = children.length - 1;
131747         
131748         // The relative index is the index in the full flat collection minus the index of the wraps parent
131749         relativeIndex = index - me.indexOf(parent) - 1;
131750         
131751         // If we are adding records to the wrap that have a higher relative index then there are currently children
131752         // it means we have to append the nodes to the wrap
131753         if (!len || relativeIndex >= len) {
131754             targetEl.appendChild(nodes);
131755         }
131756         // If there are already more children then the relative index it means we are adding child nodes of
131757         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
131758         else {
131759             // +1 because of the tr with th'es that is already there
131760             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
131761         }
131762
131763         // We also have to update the CompositeElementLite collection of the DataView
131764         Ext.Array.insert(a, index, nodes);
131765         
131766         // If we were in an animation we need to now change the animation
131767         // because the targetEl just got higher.
131768         if (animWrap.isAnimating) {
131769             me.onExpand(parent);
131770         }
131771     },
131772     
131773     beginBulkUpdate: function(){
131774         this.bulkUpdate = true;
131775         this.ownerCt.changingScrollbars = true;  
131776     },
131777     
131778     endBulkUpdate: function(){
131779         var me = this,
131780             ownerCt = me.ownerCt;
131781         
131782         me.bulkUpdate = false;
131783         me.ownerCt.changingScrollbars = true;  
131784         me.resetScrollers();  
131785     },
131786     
131787     onRemove : function(ds, record, index) {
131788         var me = this,
131789             bulk = me.bulkUpdate;
131790
131791         me.doRemove(record, index);
131792         if (!bulk) {
131793             me.updateIndexes(index);
131794         }
131795         if (me.store.getCount() === 0){
131796             me.refresh();
131797         }
131798         if (!bulk) {
131799             me.fireEvent('itemremove', record, index);
131800         }
131801     },
131802     
131803     doRemove: function(record, index) {
131804         // If we are adding records which have a parent that is currently expanding
131805         // lets add them to the animation wrap
131806         var me = this,
131807             parent = record.parentNode,
131808             all = me.all,
131809             animWrap = me.getAnimWrap(record),
131810             node = all.item(index).dom;
131811
131812         if (!animWrap || !animWrap.collapsing) {
131813             me.resetScrollers();
131814             return me.callParent(arguments);
131815         }
131816
131817         animWrap.targetEl.appendChild(node);
131818         all.removeElement(index);
131819     },
131820
131821     onBeforeExpand: function(parent, records, index) {
131822         var me = this,
131823             animWrap;
131824             
131825         if (!me.rendered || !me.animate) {
131826             return;
131827         }
131828
131829         if (me.getNode(parent)) {
131830             animWrap = me.getAnimWrap(parent);
131831             if (!animWrap) {
131832                 animWrap = parent.animWrap = me.createAnimWrap(parent);
131833                 animWrap.animateEl.setHeight(0);
131834             }
131835             else if (animWrap.collapsing) {
131836                 // If we expand this node while it is still expanding then we
131837                 // have to remove the nodes from the animWrap.
131838                 animWrap.targetEl.select(me.itemSelector).remove();
131839             } 
131840             animWrap.expanding = true;
131841             animWrap.collapsing = false;
131842         }
131843     },
131844
131845     onExpand: function(parent) {
131846         var me = this,
131847             queue = me.animQueue,
131848             id = parent.getId(),
131849             animWrap,
131850             animateEl, 
131851             targetEl,
131852             queueItem;        
131853         
131854         if (me.singleExpand) {
131855             me.ensureSingleExpand(parent);
131856         }
131857         
131858         animWrap = me.getAnimWrap(parent);
131859
131860         if (!animWrap) {
131861             me.resetScrollers();
131862             return;
131863         }
131864         
131865         animateEl = animWrap.animateEl;
131866         targetEl = animWrap.targetEl;
131867
131868         animateEl.stopAnimation();
131869         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131870         queue[id] = true;
131871         animateEl.slideIn('t', {
131872             duration: me.expandDuration,
131873             listeners: {
131874                 scope: me,
131875                 lastframe: function() {
131876                     // Move all the nodes out of the anim wrap to their proper location
131877                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
131878                     animWrap.el.remove();
131879                     me.resetScrollers();
131880                     delete animWrap.record.animWrap;
131881                     delete queue[id];
131882                 }
131883             }
131884         });
131885         
131886         animWrap.isAnimating = true;
131887     },
131888     
131889     resetScrollers: function(){
131890         if (!this.bulkUpdate) {
131891             var panel = this.panel;
131892             
131893             panel.determineScrollbars();
131894             panel.invalidateScroller();
131895         }
131896     },
131897
131898     onBeforeCollapse: function(parent, records, index) {
131899         var me = this,
131900             animWrap;
131901             
131902         if (!me.rendered || !me.animate) {
131903             return;
131904         }
131905
131906         if (me.getNode(parent)) {
131907             animWrap = me.getAnimWrap(parent);
131908             if (!animWrap) {
131909                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
131910             }
131911             else if (animWrap.expanding) {
131912                 // If we collapse this node while it is still expanding then we
131913                 // have to remove the nodes from the animWrap.
131914                 animWrap.targetEl.select(this.itemSelector).remove();
131915             }
131916             animWrap.expanding = false;
131917             animWrap.collapsing = true;
131918         }
131919     },
131920     
131921     onCollapse: function(parent) {
131922         var me = this,
131923             queue = me.animQueue,
131924             id = parent.getId(),
131925             animWrap = me.getAnimWrap(parent),
131926             animateEl, targetEl;
131927
131928         if (!animWrap) {
131929             me.resetScrollers();
131930             return;
131931         }
131932         
131933         animateEl = animWrap.animateEl;
131934         targetEl = animWrap.targetEl;
131935
131936         queue[id] = true;
131937         
131938         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131939         animateEl.stopAnimation();
131940         animateEl.slideOut('t', {
131941             duration: me.collapseDuration,
131942             listeners: {
131943                 scope: me,
131944                 lastframe: function() {
131945                     animWrap.el.remove();
131946                     delete animWrap.record.animWrap;
131947                     me.resetScrollers();
131948                     delete queue[id];
131949                 }             
131950             }
131951         });
131952         animWrap.isAnimating = true;
131953     },
131954     
131955     /**
131956      * Checks if a node is currently undergoing animation
131957      * @private
131958      * @param {Ext.data.Model} node The node
131959      * @return {Boolean} True if the node is animating
131960      */
131961     isAnimating: function(node) {
131962         return !!this.animQueue[node.getId()];    
131963     },
131964     
131965     collectData: function(records) {
131966         var data = this.callParent(arguments),
131967             rows = data.rows,
131968             len = rows.length,
131969             i = 0,
131970             row, record;
131971             
131972         for (; i < len; i++) {
131973             row = rows[i];
131974             record = records[i];
131975             if (record.get('qtip')) {
131976                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
131977                 if (record.get('qtitle')) {
131978                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
131979                 }
131980             }
131981             if (record.isExpanded()) {
131982                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
131983             }
131984             if (record.isLoading()) {
131985                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
131986             }
131987         }
131988         
131989         return data;
131990     },
131991     
131992     /**
131993      * Expands a record that is loaded in the view.
131994      * @param {Ext.data.Model} record The record to expand
131995      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
131996      * @param {Function} callback (optional) The function to run after the expand is completed
131997      * @param {Object} scope (optional) The scope of the callback function.
131998      */
131999     expand: function(record, deep, callback, scope) {
132000         return record.expand(deep, callback, scope);
132001     },
132002     
132003     /**
132004      * Collapses a record that is loaded in the view.
132005      * @param {Ext.data.Model} record The record to collapse
132006      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
132007      * @param {Function} callback (optional) The function to run after the collapse is completed
132008      * @param {Object} scope (optional) The scope of the callback function.
132009      */
132010     collapse: function(record, deep, callback, scope) {
132011         return record.collapse(deep, callback, scope);
132012     },
132013     
132014     /**
132015      * Toggles a record between expanded and collapsed.
132016      * @param {Ext.data.Model} recordInstance
132017      */
132018     toggle: function(record) {
132019         this[record.isExpanded() ? 'collapse' : 'expand'](record);
132020     },
132021     
132022     onItemDblClick: function(record, item, index) {
132023         this.callParent(arguments);
132024         if (this.toggleOnDblClick) {
132025             this.toggle(record);
132026         }
132027     },
132028     
132029     onBeforeItemMouseDown: function(record, item, index, e) {
132030         if (e.getTarget(this.expanderSelector, item)) {
132031             return false;
132032         }
132033         return this.callParent(arguments);
132034     },
132035     
132036     onItemClick: function(record, item, index, e) {
132037         if (e.getTarget(this.expanderSelector, item)) {
132038             this.toggle(record);
132039             return false;
132040         }
132041         return this.callParent(arguments);
132042     },
132043     
132044     onExpanderMouseOver: function(e, t) {
132045         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
132046     },
132047     
132048     onExpanderMouseOut: function(e, t) {
132049         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
132050     },
132051     
132052     /**
132053      * Gets the base TreeStore from the bound TreePanel.
132054      */
132055     getTreeStore: function() {
132056         return this.panel.store;
132057     },    
132058     
132059     ensureSingleExpand: function(node) {
132060         var parent = node.parentNode;
132061         if (parent) {
132062             parent.eachChild(function(child) {
132063                 if (child !== node && child.isExpanded()) {
132064                     child.collapse();
132065                 }
132066             });
132067         }
132068     }
132069 });
132070 /**
132071  * The TreePanel provides tree-structured UI representation of tree-structured data.
132072  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
132073  * multiple columns through the {@link #columns} configuration.
132074  *
132075  * Simple TreePanel using inline data:
132076  *
132077  *     @example
132078  *     var store = Ext.create('Ext.data.TreeStore', {
132079  *         root: {
132080  *             expanded: true,
132081  *             children: [
132082  *                 { text: "detention", leaf: true },
132083  *                 { text: "homework", expanded: true, children: [
132084  *                     { text: "book report", leaf: true },
132085  *                     { text: "alegrbra", leaf: true}
132086  *                 ] },
132087  *                 { text: "buy lottery tickets", leaf: true }
132088  *             ]
132089  *         }
132090  *     });
132091  *
132092  *     Ext.create('Ext.tree.Panel', {
132093  *         title: 'Simple Tree',
132094  *         width: 200,
132095  *         height: 150,
132096  *         store: store,
132097  *         rootVisible: false,
132098  *         renderTo: Ext.getBody()
132099  *     });
132100  *
132101  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
132102  * {@link Ext.data.NodeInterface NodeInterface} config options.
132103  */
132104 Ext.define('Ext.tree.Panel', {
132105     extend: 'Ext.panel.Table',
132106     alias: 'widget.treepanel',
132107     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
132108     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
132109     viewType: 'treeview',
132110     selType: 'treemodel',
132111
132112     treeCls: Ext.baseCSSPrefix + 'tree-panel',
132113
132114     deferRowRender: false,
132115
132116     /**
132117      * @cfg {Boolean} lines False to disable tree lines.
132118      */
132119     lines: true,
132120
132121     /**
132122      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
132123      */
132124     useArrows: false,
132125
132126     /**
132127      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
132128      */
132129     singleExpand: false,
132130
132131     ddConfig: {
132132         enableDrag: true,
132133         enableDrop: true
132134     },
132135
132136     /**
132137      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
132138      */
132139
132140     /**
132141      * @cfg {Boolean} rootVisible False to hide the root node.
132142      */
132143     rootVisible: true,
132144
132145     /**
132146      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
132147      */
132148     displayField: 'text',
132149
132150     /**
132151      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
132152      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
132153      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
132154      * to that store. For example:
132155      *
132156      *     Ext.create('Ext.tree.Panel', {
132157      *         title: 'Simple Tree',
132158      *         root: {
132159      *             text: "Root node",
132160      *             expanded: true,
132161      *             children: [
132162      *                 { text: "Child 1", leaf: true },
132163      *                 { text: "Child 2", leaf: true }
132164      *             ]
132165      *         },
132166      *         renderTo: Ext.getBody()
132167      *     });
132168      */
132169     root: null,
132170
132171     // Required for the Lockable Mixin. These are the configurations which will be copied to the
132172     // normal and locked sub tablepanels
132173     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
132174     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
132175
132176     /**
132177      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
132178      */
132179
132180     /**
132181      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
132182      */
132183
132184     constructor: function(config) {
132185         config = config || {};
132186         if (config.animate === undefined) {
132187             config.animate = Ext.enableFx;
132188         }
132189         this.enableAnimations = config.animate;
132190         delete config.animate;
132191
132192         this.callParent([config]);
132193     },
132194
132195     initComponent: function() {
132196         var me = this,
132197             cls = [me.treeCls];
132198
132199         if (me.useArrows) {
132200             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
132201             me.lines = false;
132202         }
132203
132204         if (me.lines) {
132205             cls.push(Ext.baseCSSPrefix + 'tree-lines');
132206         } else if (!me.useArrows) {
132207             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
132208         }
132209
132210         if (Ext.isString(me.store)) {
132211             me.store = Ext.StoreMgr.lookup(me.store);
132212         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
132213             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
132214                 root: me.root,
132215                 fields: me.fields,
132216                 model: me.model,
132217                 folderSort: me.folderSort
132218             }));
132219         } else if (me.root) {
132220             me.store = Ext.data.StoreManager.lookup(me.store);
132221             me.store.setRootNode(me.root);
132222             if (me.folderSort !== undefined) {
132223                 me.store.folderSort = me.folderSort;
132224                 me.store.sort();
132225             }
132226         }
132227
132228         // I'm not sure if we want to this. It might be confusing
132229         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
132230         //     me.rootVisible = false;
132231         // }
132232
132233         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
132234             rootVisible: me.rootVisible,
132235             animate: me.enableAnimations,
132236             singleExpand: me.singleExpand,
132237             node: me.store.getRootNode(),
132238             hideHeaders: me.hideHeaders
132239         });
132240
132241         me.mon(me.store, {
132242             scope: me,
132243             rootchange: me.onRootChange,
132244             clear: me.onClear
132245         });
132246
132247         me.relayEvents(me.store, [
132248             /**
132249              * @event beforeload
132250              * @alias Ext.data.Store#beforeload
132251              */
132252             'beforeload',
132253
132254             /**
132255              * @event load
132256              * @alias Ext.data.Store#load
132257              */
132258             'load'
132259         ]);
132260
132261         me.store.on({
132262             /**
132263              * @event itemappend
132264              * @alias Ext.data.TreeStore#append
132265              */
132266             append: me.createRelayer('itemappend'),
132267
132268             /**
132269              * @event itemremove
132270              * @alias Ext.data.TreeStore#remove
132271              */
132272             remove: me.createRelayer('itemremove'),
132273
132274             /**
132275              * @event itemmove
132276              * @alias Ext.data.TreeStore#move
132277              */
132278             move: me.createRelayer('itemmove'),
132279
132280             /**
132281              * @event iteminsert
132282              * @alias Ext.data.TreeStore#insert
132283              */
132284             insert: me.createRelayer('iteminsert'),
132285
132286             /**
132287              * @event beforeitemappend
132288              * @alias Ext.data.TreeStore#beforeappend
132289              */
132290             beforeappend: me.createRelayer('beforeitemappend'),
132291
132292             /**
132293              * @event beforeitemremove
132294              * @alias Ext.data.TreeStore#beforeremove
132295              */
132296             beforeremove: me.createRelayer('beforeitemremove'),
132297
132298             /**
132299              * @event beforeitemmove
132300              * @alias Ext.data.TreeStore#beforemove
132301              */
132302             beforemove: me.createRelayer('beforeitemmove'),
132303
132304             /**
132305              * @event beforeiteminsert
132306              * @alias Ext.data.TreeStore#beforeinsert
132307              */
132308             beforeinsert: me.createRelayer('beforeiteminsert'),
132309
132310             /**
132311              * @event itemexpand
132312              * @alias Ext.data.TreeStore#expand
132313              */
132314             expand: me.createRelayer('itemexpand'),
132315
132316             /**
132317              * @event itemcollapse
132318              * @alias Ext.data.TreeStore#collapse
132319              */
132320             collapse: me.createRelayer('itemcollapse'),
132321
132322             /**
132323              * @event beforeitemexpand
132324              * @alias Ext.data.TreeStore#beforeexpand
132325              */
132326             beforeexpand: me.createRelayer('beforeitemexpand'),
132327
132328             /**
132329              * @event beforeitemcollapse
132330              * @alias Ext.data.TreeStore#beforecollapse
132331              */
132332             beforecollapse: me.createRelayer('beforeitemcollapse')
132333         });
132334
132335         // If the user specifies the headers collection manually then dont inject our own
132336         if (!me.columns) {
132337             if (me.initialConfig.hideHeaders === undefined) {
132338                 me.hideHeaders = true;
132339             }
132340             me.columns = [{
132341                 xtype    : 'treecolumn',
132342                 text     : 'Name',
132343                 flex     : 1,
132344                 dataIndex: me.displayField
132345             }];
132346         }
132347
132348         if (me.cls) {
132349             cls.push(me.cls);
132350         }
132351         me.cls = cls.join(' ');
132352         me.callParent();
132353
132354         me.relayEvents(me.getView(), [
132355             /**
132356              * @event checkchange
132357              * Fires when a node with a checkbox's checked property changes
132358              * @param {Ext.data.Model} node The node who's checked property was changed
132359              * @param {Boolean} checked The node's new checked state
132360              */
132361             'checkchange'
132362         ]);
132363
132364         // If the root is not visible and there is no rootnode defined, then just lets load the store
132365         if (!me.getView().rootVisible && !me.getRootNode()) {
132366             me.setRootNode({
132367                 expanded: true
132368             });
132369         }
132370     },
132371
132372     onClear: function(){
132373         this.view.onClear();
132374     },
132375
132376     /**
132377      * Sets root node of this tree.
132378      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
132379      * @return {Ext.data.NodeInterface} The new root
132380      */
132381     setRootNode: function() {
132382         return this.store.setRootNode.apply(this.store, arguments);
132383     },
132384
132385     /**
132386      * Returns the root node for this tree.
132387      * @return {Ext.data.NodeInterface}
132388      */
132389     getRootNode: function() {
132390         return this.store.getRootNode();
132391     },
132392
132393     onRootChange: function(root) {
132394         this.view.setRootNode(root);
132395     },
132396
132397     /**
132398      * Retrieve an array of checked records.
132399      * @return {Ext.data.Model[]} An array containing the checked records
132400      */
132401     getChecked: function() {
132402         return this.getView().getChecked();
132403     },
132404
132405     isItemChecked: function(rec) {
132406         return rec.get('checked');
132407     },
132408
132409     /**
132410      * Expand all nodes
132411      * @param {Function} callback (optional) A function to execute when the expand finishes.
132412      * @param {Object} scope (optional) The scope of the callback function
132413      */
132414     expandAll : function(callback, scope) {
132415         var root = this.getRootNode(),
132416             animate = this.enableAnimations,
132417             view = this.getView();
132418         if (root) {
132419             if (!animate) {
132420                 view.beginBulkUpdate();
132421             }
132422             root.expand(true, callback, scope);
132423             if (!animate) {
132424                 view.endBulkUpdate();
132425             }
132426         }
132427     },
132428
132429     /**
132430      * Collapse all nodes
132431      * @param {Function} callback (optional) A function to execute when the collapse finishes.
132432      * @param {Object} scope (optional) The scope of the callback function
132433      */
132434     collapseAll : function(callback, scope) {
132435         var root = this.getRootNode(),
132436             animate = this.enableAnimations,
132437             view = this.getView();
132438
132439         if (root) {
132440             if (!animate) {
132441                 view.beginBulkUpdate();
132442             }
132443             if (view.rootVisible) {
132444                 root.collapse(true, callback, scope);
132445             } else {
132446                 root.collapseChildren(true, callback, scope);
132447             }
132448             if (!animate) {
132449                 view.endBulkUpdate();
132450             }
132451         }
132452     },
132453
132454     /**
132455      * Expand the tree to the path of a particular node.
132456      * @param {String} path The path to expand. The path should include a leading separator.
132457      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132458      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132459      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
132460      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
132461      * @param {Object} scope (optional) The scope of the callback function
132462      */
132463     expandPath: function(path, field, separator, callback, scope) {
132464         var me = this,
132465             current = me.getRootNode(),
132466             index = 1,
132467             view = me.getView(),
132468             keys,
132469             expander;
132470
132471         field = field || me.getRootNode().idProperty;
132472         separator = separator || '/';
132473
132474         if (Ext.isEmpty(path)) {
132475             Ext.callback(callback, scope || me, [false, null]);
132476             return;
132477         }
132478
132479         keys = path.split(separator);
132480         if (current.get(field) != keys[1]) {
132481             // invalid root
132482             Ext.callback(callback, scope || me, [false, current]);
132483             return;
132484         }
132485
132486         expander = function(){
132487             if (++index === keys.length) {
132488                 Ext.callback(callback, scope || me, [true, current]);
132489                 return;
132490             }
132491             var node = current.findChild(field, keys[index]);
132492             if (!node) {
132493                 Ext.callback(callback, scope || me, [false, current]);
132494                 return;
132495             }
132496             current = node;
132497             current.expand(false, expander);
132498         };
132499         current.expand(false, expander);
132500     },
132501
132502     /**
132503      * Expand the tree to the path of a particular node, then select it.
132504      * @param {String} path The path to select. The path should include a leading separator.
132505      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132506      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132507      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
132508      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
132509      * @param {Object} scope (optional) The scope of the callback function
132510      */
132511     selectPath: function(path, field, separator, callback, scope) {
132512         var me = this,
132513             keys,
132514             last;
132515
132516         field = field || me.getRootNode().idProperty;
132517         separator = separator || '/';
132518
132519         keys = path.split(separator);
132520         last = keys.pop();
132521
132522         me.expandPath(keys.join(separator), field, separator, function(success, node){
132523             var doSuccess = false;
132524             if (success && node) {
132525                 node = node.findChild(field, last);
132526                 if (node) {
132527                     me.getSelectionModel().select(node);
132528                     Ext.callback(callback, scope || me, [true, node]);
132529                     doSuccess = true;
132530                 }
132531             } else if (node === me.getRootNode()) {
132532                 doSuccess = true;
132533             }
132534             Ext.callback(callback, scope || me, [doSuccess, node]);
132535         }, me);
132536     }
132537 });
132538
132539 /**
132540  * @class Ext.view.DragZone
132541  * @extends Ext.dd.DragZone
132542  * @private
132543  */
132544 Ext.define('Ext.view.DragZone', {
132545     extend: 'Ext.dd.DragZone',
132546     containerScroll: false,
132547
132548     constructor: function(config) {
132549         var me = this;
132550
132551         Ext.apply(me, config);
132552
132553         // Create a ddGroup unless one has been configured.
132554         // User configuration of ddGroups allows users to specify which
132555         // DD instances can interact with each other. Using one
132556         // based on the id of the View would isolate it and mean it can only
132557         // interact with a DropZone on the same View also using a generated ID.
132558         if (!me.ddGroup) {
132559             me.ddGroup = 'view-dd-zone-' + me.view.id;
132560         }
132561
132562         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
132563         // So a View's DragZone cannot use the View's main element because the DropZone must use that
132564         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
132565         // main element which handles scrolling.
132566         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
132567         // is transient; DataView's recreate the internal structure dynamically as data changes.
132568         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
132569         me.callParent([me.view.el.dom.parentNode]);
132570
132571         me.ddel = Ext.get(document.createElement('div'));
132572         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
132573     },
132574
132575     init: function(id, sGroup, config) {
132576         this.initTarget(id, sGroup, config);
132577         this.view.mon(this.view, {
132578             itemmousedown: this.onItemMouseDown,
132579             scope: this
132580         });
132581     },
132582
132583     onItemMouseDown: function(view, record, item, index, e) {
132584         if (!this.isPreventDrag(e, record, item, index)) {
132585             this.handleMouseDown(e);
132586
132587             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
132588             // if the mousedowned record is selected
132589             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
132590                 return false;
132591             }
132592         }
132593     },
132594
132595     // private template method
132596     isPreventDrag: function(e) {
132597         return false;
132598     },
132599
132600     getDragData: function(e) {
132601         var view = this.view,
132602             item = e.getTarget(view.getItemSelector()),
132603             record, selectionModel, records;
132604
132605         if (item) {
132606             record = view.getRecord(item);
132607             selectionModel = view.getSelectionModel();
132608             records = selectionModel.getSelection();
132609             return {
132610                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
132611                 event: new Ext.EventObjectImpl(e),
132612                 view: view,
132613                 ddel: this.ddel,
132614                 item: item,
132615                 records: records,
132616                 fromPosition: Ext.fly(item).getXY()
132617             };
132618         }
132619     },
132620
132621     onInitDrag: function(x, y) {
132622         var me = this,
132623             data = me.dragData,
132624             view = data.view,
132625             selectionModel = view.getSelectionModel(),
132626             record = view.getRecord(data.item),
132627             e = data.event;
132628
132629         // Update the selection to match what would have been selected if the user had
132630         // done a full click on the target node rather than starting a drag from it
132631         if (!selectionModel.isSelected(record) || e.hasModifier()) {
132632             selectionModel.selectWithEvent(record, e, true);
132633         }
132634         data.records = selectionModel.getSelection();
132635
132636         me.ddel.update(me.getDragText());
132637         me.proxy.update(me.ddel.dom);
132638         me.onStartDrag(x, y);
132639         return true;
132640     },
132641
132642     getDragText: function() {
132643         var count = this.dragData.records.length;
132644         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
132645     },
132646
132647     getRepairXY : function(e, data){
132648         return data ? data.fromPosition : false;
132649     }
132650 });
132651 Ext.define('Ext.tree.ViewDragZone', {
132652     extend: 'Ext.view.DragZone',
132653
132654     isPreventDrag: function(e, record) {
132655         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
132656     },
132657     
132658     afterRepair: function() {
132659         var me = this,
132660             view = me.view,
132661             selectedRowCls = view.selectedItemCls,
132662             records = me.dragData.records,
132663             fly = Ext.fly;
132664         
132665         if (Ext.enableFx && me.repairHighlight) {
132666             // Roll through all records and highlight all the ones we attempted to drag.
132667             Ext.Array.forEach(records, function(record) {
132668                 // anonymous fns below, don't hoist up unless below is wrapped in
132669                 // a self-executing function passing in item.
132670                 var item = view.getNode(record);
132671                 
132672                 // We must remove the selected row class before animating, because
132673                 // the selected row class declares !important on its background-color.
132674                 fly(item.firstChild).highlight(me.repairHighlightColor, {
132675                     listeners: {
132676                         beforeanimate: function() {
132677                             if (view.isSelected(item)) {
132678                                 fly(item).removeCls(selectedRowCls);
132679                             }
132680                         },
132681                         afteranimate: function() {
132682                             if (view.isSelected(item)) {
132683                                 fly(item).addCls(selectedRowCls);
132684                             }
132685                         }
132686                     }
132687                 });
132688             });
132689         }
132690         me.dragging = false;
132691     }
132692 });
132693 /**
132694  * @class Ext.tree.ViewDropZone
132695  * @extends Ext.view.DropZone
132696  * @private
132697  */
132698 Ext.define('Ext.tree.ViewDropZone', {
132699     extend: 'Ext.view.DropZone',
132700
132701     /**
132702      * @cfg {Boolean} allowParentInsert
132703      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
132704      * sibling of the parent when dropped.
132705      */
132706     allowParentInserts: false,
132707  
132708     /**
132709      * @cfg {String} allowContainerDrop
132710      * True if drops on the tree container (outside of a specific tree node) are allowed.
132711      */
132712     allowContainerDrops: false,
132713
132714     /**
132715      * @cfg {String} appendOnly
132716      * True if the tree should only allow append drops (use for trees which are sorted).
132717      */
132718     appendOnly: false,
132719
132720     /**
132721      * @cfg {String} expandDelay
132722      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
132723      * over the target.
132724      */
132725     expandDelay : 500,
132726
132727     indicatorCls: 'x-tree-ddindicator',
132728
132729     // private
132730     expandNode : function(node) {
132731         var view = this.view;
132732         if (!node.isLeaf() && !node.isExpanded()) {
132733             view.expand(node);
132734             this.expandProcId = false;
132735         }
132736     },
132737
132738     // private
132739     queueExpand : function(node) {
132740         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
132741     },
132742
132743     // private
132744     cancelExpand : function() {
132745         if (this.expandProcId) {
132746             clearTimeout(this.expandProcId);
132747             this.expandProcId = false;
132748         }
132749     },
132750
132751     getPosition: function(e, node) {
132752         var view = this.view,
132753             record = view.getRecord(node),
132754             y = e.getPageY(),
132755             noAppend = record.isLeaf(),
132756             noBelow = false,
132757             region = Ext.fly(node).getRegion(),
132758             fragment;
132759
132760         // If we are dragging on top of the root node of the tree, we always want to append.
132761         if (record.isRoot()) {
132762             return 'append';
132763         }
132764
132765         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
132766         if (this.appendOnly) {
132767             return noAppend ? false : 'append';
132768         }
132769
132770         if (!this.allowParentInsert) {
132771             noBelow = record.hasChildNodes() && record.isExpanded();
132772         }
132773
132774         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
132775         if (y >= region.top && y < (region.top + fragment)) {
132776             return 'before';
132777         }
132778         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
132779             return 'after';
132780         }
132781         else {
132782             return 'append';
132783         }
132784     },
132785
132786     isValidDropPoint : function(node, position, dragZone, e, data) {
132787         if (!node || !data.item) {
132788             return false;
132789         }
132790
132791         var view = this.view,
132792             targetNode = view.getRecord(node),
132793             draggedRecords = data.records,
132794             dataLength = draggedRecords.length,
132795             ln = draggedRecords.length,
132796             i, record;
132797
132798         // No drop position, or dragged records: invalid drop point
132799         if (!(targetNode && position && dataLength)) {
132800             return false;
132801         }
132802
132803         // If the targetNode is within the folder we are dragging
132804         for (i = 0; i < ln; i++) {
132805             record = draggedRecords[i];
132806             if (record.isNode && record.contains(targetNode)) {
132807                 return false;
132808             }
132809         }
132810         
132811         // Respect the allowDrop field on Tree nodes
132812         if (position === 'append' && targetNode.get('allowDrop') === false) {
132813             return false;
132814         }
132815         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
132816             return false;
132817         }
132818
132819         // If the target record is in the dragged dataset, then invalid drop
132820         if (Ext.Array.contains(draggedRecords, targetNode)) {
132821              return false;
132822         }
132823
132824         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
132825         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
132826         return true;
132827     },
132828
132829     onNodeOver : function(node, dragZone, e, data) {
132830         var position = this.getPosition(e, node),
132831             returnCls = this.dropNotAllowed,
132832             view = this.view,
132833             targetNode = view.getRecord(node),
132834             indicator = this.getIndicator(),
132835             indicatorX = 0,
132836             indicatorY = 0;
132837
132838         // auto node expand check
132839         this.cancelExpand();
132840         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
132841             this.queueExpand(targetNode);
132842         }
132843             
132844             
132845         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
132846             this.valid = true;
132847             this.currentPosition = position;
132848             this.overRecord = targetNode;
132849
132850             indicator.setWidth(Ext.fly(node).getWidth());
132851             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
132852
132853             /*
132854              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
132855              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
132856              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
132857              */
132858             if (position == 'before') {
132859                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132860                 indicator.showAt(0, indicatorY);
132861                 dragZone.proxy.show();
132862             } else if (position == 'after') {
132863                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132864                 indicatorY += Ext.fly(node).getHeight();
132865                 indicator.showAt(0, indicatorY);
132866                 dragZone.proxy.show();
132867             } else {
132868                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
132869                 // @TODO: set a class on the parent folder node to be able to style it
132870                 indicator.hide();
132871             }
132872         } else {
132873             this.valid = false;
132874         }
132875
132876         this.currentCls = returnCls;
132877         return returnCls;
132878     },
132879
132880     onContainerOver : function(dd, e, data) {
132881         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
132882     },
132883     
132884     notifyOut: function() {
132885         this.callParent(arguments);
132886         this.cancelExpand();
132887     },
132888
132889     handleNodeDrop : function(data, targetNode, position) {
132890         var me = this,
132891             view = me.view,
132892             parentNode = targetNode.parentNode,
132893             store = view.getStore(),
132894             recordDomNodes = [],
132895             records, i, len,
132896             insertionMethod, argList,
132897             needTargetExpand,
132898             transferData,
132899             processDrop;
132900
132901         // If the copy flag is set, create a copy of the Models with the same IDs
132902         if (data.copy) {
132903             records = data.records;
132904             data.records = [];
132905             for (i = 0, len = records.length; i < len; i++) {
132906                 data.records.push(Ext.apply({}, records[i].data));
132907             }
132908         }
132909
132910         // Cancel any pending expand operation
132911         me.cancelExpand();
132912
132913         // Grab a reference to the correct node insertion method.
132914         // Create an arg list array intended for the apply method of the
132915         // chosen node insertion method.
132916         // Ensure the target object for the method is referenced by 'targetNode'
132917         if (position == 'before') {
132918             insertionMethod = parentNode.insertBefore;
132919             argList = [null, targetNode];
132920             targetNode = parentNode;
132921         }
132922         else if (position == 'after') {
132923             if (targetNode.nextSibling) {
132924                 insertionMethod = parentNode.insertBefore;
132925                 argList = [null, targetNode.nextSibling];
132926             }
132927             else {
132928                 insertionMethod = parentNode.appendChild;
132929                 argList = [null];
132930             }
132931             targetNode = parentNode;
132932         }
132933         else {
132934             if (!targetNode.isExpanded()) {
132935                 needTargetExpand = true;
132936             }
132937             insertionMethod = targetNode.appendChild;
132938             argList = [null];
132939         }
132940
132941         // A function to transfer the data into the destination tree
132942         transferData = function() {
132943             var node;
132944             for (i = 0, len = data.records.length; i < len; i++) {
132945                 argList[0] = data.records[i];
132946                 node = insertionMethod.apply(targetNode, argList);
132947                 
132948                 if (Ext.enableFx && me.dropHighlight) {
132949                     recordDomNodes.push(view.getNode(node));
132950                 }
132951             }
132952             
132953             // Kick off highlights after everything's been inserted, so they are
132954             // more in sync without insertion/render overhead.
132955             if (Ext.enableFx && me.dropHighlight) {
132956                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
132957                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
132958                 Ext.Array.forEach(recordDomNodes, function(n) {
132959                     if (n) {
132960                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
132961                     }
132962                 });
132963             }
132964         };
132965
132966         // If dropping right on an unexpanded node, transfer the data after it is expanded.
132967         if (needTargetExpand) {
132968             targetNode.expand(false, transferData);
132969         }
132970         // Otherwise, call the data transfer function immediately
132971         else {
132972             transferData();
132973         }
132974     }
132975 });
132976 /**
132977  * This plugin provides drag and/or drop functionality for a TreeView.
132978  *
132979  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
132980  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
132981  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
132982  *
132983  *   - copy : Boolean
132984  *
132985  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
132986  *     the control key was pressed when the drag operation was begun.
132987  *
132988  *   - view : TreeView
132989  *
132990  *     The source TreeView from which the drag originated.
132991  *
132992  *   - ddel : HtmlElement
132993  *
132994  *     The drag proxy element which moves with the mouse
132995  *
132996  *   - item : HtmlElement
132997  *
132998  *     The TreeView node upon which the mousedown event was registered.
132999  *
133000  *   - records : Array
133001  *
133002  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
133003  *
133004  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
133005  * members of the same ddGroup which processes such data objects.
133006  *
133007  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
133008  * {@link #drop}.
133009  *
133010  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
133011  *
133012  *     viewConfig: {
133013  *         plugins: { ptype: 'treeviewdragdrop' }
133014  *     }
133015  */
133016 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
133017     extend: 'Ext.AbstractPlugin',
133018     alias: 'plugin.treeviewdragdrop',
133019
133020     uses: [
133021         'Ext.tree.ViewDragZone',
133022         'Ext.tree.ViewDropZone'
133023     ],
133024
133025     /**
133026      * @event beforedrop
133027      *
133028      * **This event is fired through the TreeView. Add listeners to the TreeView object**
133029      *
133030      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
133031      *
133032      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133033      *
133034      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
133035      * back to the point from which the drag began.
133036      *
133037      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
133038      * was valid, and that the repair operation should not take place.
133039      *
133040      * Any other return value continues with the data transfer operation.
133041      *
133042      * @param {Object} data The data object gathered at mousedown time by the cooperating
133043      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133044      * properties:
133045      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133046      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133047      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133048      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133049      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133050      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133051      * dragged from the source TreeView.
133052      *
133053      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133054      *
133055      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133056      * the midline of the node, or the node is a branch node which accepts new child nodes.
133057      *
133058      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
133059      * Model instances from the source View's Store to the destination View's Store.
133060      *
133061      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
133062      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
133063      *
133064      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
133065      */
133066
133067     /**
133068      * @event drop
133069      *
133070      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
133071      * has been completed and the data has been moved or copied.
133072      *
133073      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133074      *
133075      * @param {Object} data The data object gathered at mousedown time by the cooperating
133076      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133077      * properties:
133078      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133079      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133080      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133081      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133082      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133083      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133084      * dragged from the source TreeView.
133085      *
133086      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133087      *
133088      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133089      * the midline of the node, or the node is a branch node which accepts new child nodes.
133090      */
133091
133092     dragText : '{0} selected node{1}',
133093
133094     /**
133095      * @cfg {Boolean} allowParentInsert
133096      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
133097      * the parent when dropped.
133098      */
133099     allowParentInserts: false,
133100
133101     /**
133102      * @cfg {String} allowContainerDrop
133103      * True if drops on the tree container (outside of a specific tree node) are allowed.
133104      */
133105     allowContainerDrops: false,
133106
133107     /**
133108      * @cfg {String} appendOnly
133109      * True if the tree should only allow append drops (use for trees which are sorted).
133110      */
133111     appendOnly: false,
133112
133113     /**
133114      * @cfg {String} ddGroup
133115      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
133116      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
133117      */
133118     ddGroup : "TreeDD",
133119
133120     /**
133121      * @cfg {String} dragGroup
133122      * The ddGroup to which the DragZone will belong.
133123      *
133124      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
133125      * Drag/DropZones which are members of the same ddGroup.
133126      */
133127
133128     /**
133129      * @cfg {String} dropGroup
133130      * The ddGroup to which the DropZone will belong.
133131      *
133132      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
133133      * Drag/DropZones which are members of the same ddGroup.
133134      */
133135
133136     /**
133137      * @cfg {String} expandDelay
133138      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
133139      * target.
133140      */
133141     expandDelay : 1000,
133142
133143     /**
133144      * @cfg {Boolean} enableDrop
133145      * Set to `false` to disallow the View from accepting drop gestures.
133146      */
133147     enableDrop: true,
133148
133149     /**
133150      * @cfg {Boolean} enableDrag
133151      * Set to `false` to disallow dragging items from the View.
133152      */
133153     enableDrag: true,
133154
133155     /**
133156      * @cfg {String} nodeHighlightColor
133157      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
133158      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
133159      * {@link #nodeHighlightOnRepair}.
133160      */
133161     nodeHighlightColor: 'c3daf9',
133162
133163     /**
133164      * @cfg {Boolean} nodeHighlightOnDrop
133165      * Whether or not to highlight any nodes after they are
133166      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
133167      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
133168      */
133169     nodeHighlightOnDrop: Ext.enableFx,
133170
133171     /**
133172      * @cfg {Boolean} nodeHighlightOnRepair
133173      * Whether or not to highlight any nodes after they are
133174      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
133175      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
133176      */
133177     nodeHighlightOnRepair: Ext.enableFx,
133178
133179     init : function(view) {
133180         view.on('render', this.onViewRender, this, {single: true});
133181     },
133182
133183     /**
133184      * @private
133185      * AbstractComponent calls destroy on all its plugins at destroy time.
133186      */
133187     destroy: function() {
133188         Ext.destroy(this.dragZone, this.dropZone);
133189     },
133190
133191     onViewRender : function(view) {
133192         var me = this;
133193
133194         if (me.enableDrag) {
133195             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
133196                 view: view,
133197                 ddGroup: me.dragGroup || me.ddGroup,
133198                 dragText: me.dragText,
133199                 repairHighlightColor: me.nodeHighlightColor,
133200                 repairHighlight: me.nodeHighlightOnRepair
133201             });
133202         }
133203
133204         if (me.enableDrop) {
133205             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
133206                 view: view,
133207                 ddGroup: me.dropGroup || me.ddGroup,
133208                 allowContainerDrops: me.allowContainerDrops,
133209                 appendOnly: me.appendOnly,
133210                 allowParentInserts: me.allowParentInserts,
133211                 expandDelay: me.expandDelay,
133212                 dropHighlightColor: me.nodeHighlightColor,
133213                 dropHighlight: me.nodeHighlightOnDrop
133214             });
133215         }
133216     }
133217 });
133218 /**
133219  * @class Ext.util.Cookies
133220
133221 Utility class for setting/reading values from browser cookies.
133222 Values can be written using the {@link #set} method.
133223 Values can be read using the {@link #get} method.
133224 A cookie can be invalidated on the client machine using the {@link #clear} method.
133225
133226  * @markdown
133227  * @singleton
133228  */
133229 Ext.define('Ext.util.Cookies', {
133230     singleton: true,
133231     
133232     /**
133233      * Create a cookie with the specified name and value. Additional settings
133234      * for the cookie may be optionally specified (for example: expiration,
133235      * access restriction, SSL).
133236      * @param {String} name The name of the cookie to set. 
133237      * @param {Object} value The value to set for the cookie.
133238      * @param {Object} expires (Optional) Specify an expiration date the
133239      * cookie is to persist until.  Note that the specified Date object will
133240      * be converted to Greenwich Mean Time (GMT). 
133241      * @param {String} path (Optional) Setting a path on the cookie restricts
133242      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
133243      * @param {String} domain (Optional) Setting a domain restricts access to
133244      * pages on a given domain (typically used to allow cookie access across
133245      * subdomains). For example, "sencha.com" will create a cookie that can be
133246      * accessed from any subdomain of sencha.com, including www.sencha.com,
133247      * support.sencha.com, etc.
133248      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
133249      * should only be accessible via SSL on a page using the HTTPS protocol.
133250      * Defaults to <tt>false</tt>. Note that this will only work if the page
133251      * calling this code uses the HTTPS protocol, otherwise the cookie will be
133252      * created with default options.
133253      */
133254     set : function(name, value){
133255         var argv = arguments,
133256             argc = arguments.length,
133257             expires = (argc > 2) ? argv[2] : null,
133258             path = (argc > 3) ? argv[3] : '/',
133259             domain = (argc > 4) ? argv[4] : null,
133260             secure = (argc > 5) ? argv[5] : false;
133261             
133262         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
133263     },
133264
133265     /**
133266      * Retrieves cookies that are accessible by the current page. If a cookie
133267      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
133268      * example retrieves the cookie called "valid" and stores the String value
133269      * in the variable <tt>validStatus</tt>.
133270      * <pre><code>
133271      * var validStatus = Ext.util.Cookies.get("valid");
133272      * </code></pre>
133273      * @param {String} name The name of the cookie to get
133274      * @return {Object} Returns the cookie value for the specified name;
133275      * null if the cookie name does not exist.
133276      */
133277     get : function(name){
133278         var arg = name + "=",
133279             alen = arg.length,
133280             clen = document.cookie.length,
133281             i = 0,
133282             j = 0;
133283             
133284         while(i < clen){
133285             j = i + alen;
133286             if(document.cookie.substring(i, j) == arg){
133287                 return this.getCookieVal(j);
133288             }
133289             i = document.cookie.indexOf(" ", i) + 1;
133290             if(i === 0){
133291                 break;
133292             }
133293         }
133294         return null;
133295     },
133296
133297     /**
133298      * Removes a cookie with the provided name from the browser
133299      * if found by setting its expiration date to sometime in the past. 
133300      * @param {String} name The name of the cookie to remove
133301      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
133302      */
133303     clear : function(name, path){
133304         if(this.get(name)){
133305             path = path || '/';
133306             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
133307         }
133308     },
133309     
133310     /**
133311      * @private
133312      */
133313     getCookieVal : function(offset){
133314         var endstr = document.cookie.indexOf(";", offset);
133315         if(endstr == -1){
133316             endstr = document.cookie.length;
133317         }
133318         return unescape(document.cookie.substring(offset, endstr));
133319     }
133320 });
133321
133322 /**
133323  * @class Ext.util.CSS
133324  * Utility class for manipulating CSS rules
133325  * @singleton
133326  */
133327 Ext.define('Ext.util.CSS', function() {
133328     var rules = null;
133329     var doc = document;
133330
133331     var camelRe = /(-[a-z])/gi;
133332     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
133333
133334     return {
133335
133336         singleton: true,
133337
133338         constructor: function() {
133339             this.rules = {};
133340             this.initialized = false;
133341         },
133342
133343         /**
133344          * Creates a stylesheet from a text blob of rules.
133345          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
133346          * @param {String} cssText The text containing the css rules
133347          * @param {String} id An id to add to the stylesheet for later removal
133348          * @return {CSSStyleSheet}
133349          */
133350         createStyleSheet : function(cssText, id) {
133351             var ss,
133352                 head = doc.getElementsByTagName("head")[0],
133353                 styleEl = doc.createElement("style");
133354
133355             styleEl.setAttribute("type", "text/css");
133356             if (id) {
133357                styleEl.setAttribute("id", id);
133358             }
133359
133360             if (Ext.isIE) {
133361                head.appendChild(styleEl);
133362                ss = styleEl.styleSheet;
133363                ss.cssText = cssText;
133364             } else {
133365                 try{
133366                     styleEl.appendChild(doc.createTextNode(cssText));
133367                 } catch(e) {
133368                    styleEl.cssText = cssText;
133369                 }
133370                 head.appendChild(styleEl);
133371                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
133372             }
133373             this.cacheStyleSheet(ss);
133374             return ss;
133375         },
133376
133377         /**
133378          * Removes a style or link tag by id
133379          * @param {String} id The id of the tag
133380          */
133381         removeStyleSheet : function(id) {
133382             var existing = document.getElementById(id);
133383             if (existing) {
133384                 existing.parentNode.removeChild(existing);
133385             }
133386         },
133387
133388         /**
133389          * Dynamically swaps an existing stylesheet reference for a new one
133390          * @param {String} id The id of an existing link tag to remove
133391          * @param {String} url The href of the new stylesheet to include
133392          */
133393         swapStyleSheet : function(id, url) {
133394             var doc = document;
133395             this.removeStyleSheet(id);
133396             var ss = doc.createElement("link");
133397             ss.setAttribute("rel", "stylesheet");
133398             ss.setAttribute("type", "text/css");
133399             ss.setAttribute("id", id);
133400             ss.setAttribute("href", url);
133401             doc.getElementsByTagName("head")[0].appendChild(ss);
133402         },
133403
133404         /**
133405          * Refresh the rule cache if you have dynamically added stylesheets
133406          * @return {Object} An object (hash) of rules indexed by selector
133407          */
133408         refreshCache : function() {
133409             return this.getRules(true);
133410         },
133411
133412         // private
133413         cacheStyleSheet : function(ss) {
133414             if(!rules){
133415                 rules = {};
133416             }
133417             try {// try catch for cross domain access issue
133418                 var ssRules = ss.cssRules || ss.rules,
133419                     selectorText,
133420                     i = ssRules.length - 1,
133421                     j,
133422                     selectors;
133423
133424                 for (; i >= 0; --i) {
133425                     selectorText = ssRules[i].selectorText;
133426                     if (selectorText) {
133427
133428                         // Split in case there are multiple, comma-delimited selectors
133429                         selectorText = selectorText.split(',');
133430                         selectors = selectorText.length;
133431                         for (j = 0; j < selectors; j++) {
133432                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
133433                         }
133434                     }
133435                 }
133436             } catch(e) {}
133437         },
133438
133439         /**
133440         * Gets all css rules for the document
133441         * @param {Boolean} refreshCache true to refresh the internal cache
133442         * @return {Object} An object (hash) of rules indexed by selector
133443         */
133444         getRules : function(refreshCache) {
133445             if (rules === null || refreshCache) {
133446                 rules = {};
133447                 var ds = doc.styleSheets,
133448                     i = 0,
133449                     len = ds.length;
133450
133451                 for (; i < len; i++) {
133452                     try {
133453                         if (!ds[i].disabled) {
133454                             this.cacheStyleSheet(ds[i]);
133455                         }
133456                     } catch(e) {}
133457                 }
133458             }
133459             return rules;
133460         },
133461
133462         /**
133463          * Gets an an individual CSS rule by selector(s)
133464          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
133465          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
133466          * @return {CSSStyleRule} The CSS rule or null if one is not found
133467          */
133468         getRule: function(selector, refreshCache) {
133469             var rs = this.getRules(refreshCache);
133470             if (!Ext.isArray(selector)) {
133471                 return rs[selector.toLowerCase()];
133472             }
133473             for (var i = 0; i < selector.length; i++) {
133474                 if (rs[selector[i]]) {
133475                     return rs[selector[i].toLowerCase()];
133476                 }
133477             }
133478             return null;
133479         },
133480
133481         /**
133482          * Updates a rule property
133483          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
133484          * @param {String} property The css property
133485          * @param {String} value The new value for the property
133486          * @return {Boolean} true If a rule was found and updated
133487          */
133488         updateRule : function(selector, property, value){
133489             if (!Ext.isArray(selector)) {
133490                 var rule = this.getRule(selector);
133491                 if (rule) {
133492                     rule.style[property.replace(camelRe, camelFn)] = value;
133493                     return true;
133494                 }
133495             } else {
133496                 for (var i = 0; i < selector.length; i++) {
133497                     if (this.updateRule(selector[i], property, value)) {
133498                         return true;
133499                     }
133500                 }
133501             }
133502             return false;
133503         }
133504     };
133505 }());
133506 /**
133507  * @class Ext.util.History
133508  *
133509  * History management component that allows you to register arbitrary tokens that signify application
133510  * history state on navigation actions.  You can then handle the history {@link #change} event in order
133511  * to reset your application UI to the appropriate state when the user navigates forward or backward through
133512  * the browser history stack.
133513  *
133514  * ## Initializing
133515  * The {@link #init} method of the History object must be called before using History. This sets up the internal
133516  * state and must be the first thing called before using History.
133517  *
133518  * ## Setup
133519  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
133520  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
133521  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
133522  * to support all browsers:
133523  *
133524  *     <form id="history-form" class="x-hide-display">
133525  *         <input type="hidden" id="x-history-field" />
133526  *         <iframe id="x-history-frame"></iframe>
133527  *     </form>
133528  *
133529  * @singleton
133530  */
133531 Ext.define('Ext.util.History', {
133532     singleton: true,
133533     alternateClassName: 'Ext.History',
133534     mixins: {
133535         observable: 'Ext.util.Observable'
133536     },
133537
133538     constructor: function() {
133539         var me = this;
133540         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
133541         me.iframe = null;
133542         me.hiddenField = null;
133543         me.ready = false;
133544         me.currentToken = null;
133545     },
133546
133547     getHash: function() {
133548         var href = window.location.href,
133549             i = href.indexOf("#");
133550
133551         return i >= 0 ? href.substr(i + 1) : null;
133552     },
133553
133554     doSave: function() {
133555         this.hiddenField.value = this.currentToken;
133556     },
133557
133558
133559     handleStateChange: function(token) {
133560         this.currentToken = token;
133561         this.fireEvent('change', token);
133562     },
133563
133564     updateIFrame: function(token) {
133565         var html = '<html><body><div id="state">' +
133566                     Ext.util.Format.htmlEncode(token) +
133567                     '</div></body></html>';
133568
133569         try {
133570             var doc = this.iframe.contentWindow.document;
133571             doc.open();
133572             doc.write(html);
133573             doc.close();
133574             return true;
133575         } catch (e) {
133576             return false;
133577         }
133578     },
133579
133580     checkIFrame: function () {
133581         var me = this,
133582             contentWindow = me.iframe.contentWindow;
133583
133584         if (!contentWindow || !contentWindow.document) {
133585             Ext.Function.defer(this.checkIFrame, 10, this);
133586             return;
133587         }
133588
133589         var doc = contentWindow.document,
133590             elem = doc.getElementById("state"),
133591             oldToken = elem ? elem.innerText : null,
133592             oldHash = me.getHash();
133593
133594         Ext.TaskManager.start({
133595             run: function () {
133596                 var doc = contentWindow.document,
133597                     elem = doc.getElementById("state"),
133598                     newToken = elem ? elem.innerText : null,
133599                     newHash = me.getHash();
133600
133601                 if (newToken !== oldToken) {
133602                     oldToken = newToken;
133603                     me.handleStateChange(newToken);
133604                     window.top.location.hash = newToken;
133605                     oldHash = newToken;
133606                     me.doSave();
133607                 } else if (newHash !== oldHash) {
133608                     oldHash = newHash;
133609                     me.updateIFrame(newHash);
133610                 }
133611             },
133612             interval: 50,
133613             scope: me
133614         });
133615         me.ready = true;
133616         me.fireEvent('ready', me);
133617     },
133618
133619     startUp: function () {
133620         var me = this;
133621
133622         me.currentToken = me.hiddenField.value || this.getHash();
133623
133624         if (me.oldIEMode) {
133625             me.checkIFrame();
133626         } else {
133627             var hash = me.getHash();
133628             Ext.TaskManager.start({
133629                 run: function () {
133630                     var newHash = me.getHash();
133631                     if (newHash !== hash) {
133632                         hash = newHash;
133633                         me.handleStateChange(hash);
133634                         me.doSave();
133635                     }
133636                 },
133637                 interval: 50,
133638                 scope: me
133639             });
133640             me.ready = true;
133641             me.fireEvent('ready', me);
133642         }
133643
133644     },
133645
133646     /**
133647      * The id of the hidden field required for storing the current history token.
133648      * @type String
133649      * @property
133650      */
133651     fieldId: Ext.baseCSSPrefix + 'history-field',
133652     /**
133653      * The id of the iframe required by IE to manage the history stack.
133654      * @type String
133655      * @property
133656      */
133657     iframeId: Ext.baseCSSPrefix + 'history-frame',
133658
133659     /**
133660      * Initialize the global History instance.
133661      * @param {Boolean} onReady (optional) A callback function that will be called once the history
133662      * component is fully initialized.
133663      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
133664      */
133665     init: function (onReady, scope) {
133666         var me = this;
133667
133668         if (me.ready) {
133669             Ext.callback(onReady, scope, [me]);
133670             return;
133671         }
133672
133673         if (!Ext.isReady) {
133674             Ext.onReady(function() {
133675                 me.init(onReady, scope);
133676             });
133677             return;
133678         }
133679
133680         me.hiddenField = Ext.getDom(me.fieldId);
133681
133682         if (me.oldIEMode) {
133683             me.iframe = Ext.getDom(me.iframeId);
133684         }
133685
133686         me.addEvents(
133687             /**
133688              * @event ready
133689              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
133690              * @param {Ext.util.History} The Ext.util.History singleton.
133691              */
133692             'ready',
133693             /**
133694              * @event change
133695              * Fires when navigation back or forwards within the local page's history occurs.
133696              * @param {String} token An identifier associated with the page state at that point in its history.
133697              */
133698             'change'
133699         );
133700
133701         if (onReady) {
133702             me.on('ready', onReady, scope, {single: true});
133703         }
133704         me.startUp();
133705     },
133706
133707     /**
133708      * Add a new token to the history stack. This can be any arbitrary value, although it would
133709      * commonly be the concatenation of a component id and another id marking the specific history
133710      * state of that component. Example usage:
133711      *
133712      *     // Handle tab changes on a TabPanel
133713      *     tabPanel.on('tabchange', function(tabPanel, tab){
133714      *          Ext.History.add(tabPanel.id + ':' + tab.id);
133715      *     });
133716      *
133717      * @param {String} token The value that defines a particular application-specific history state
133718      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
133719      * it will not save a new history step. Set to false if the same state can be saved more than once
133720      * at the same history stack location.
133721      */
133722     add: function (token, preventDup) {
133723         var me = this;
133724
133725         if (preventDup !== false) {
133726             if (me.getToken() === token) {
133727                 return true;
133728             }
133729         }
133730
133731         if (me.oldIEMode) {
133732             return me.updateIFrame(token);
133733         } else {
133734             window.top.location.hash = token;
133735             return true;
133736         }
133737     },
133738
133739     /**
133740      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
133741      */
133742     back: function() {
133743         window.history.go(-1);
133744     },
133745
133746     /**
133747      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
133748      */
133749     forward: function(){
133750         window.history.go(1);
133751     },
133752
133753     /**
133754      * Retrieves the currently-active history token.
133755      * @return {String} The token
133756      */
133757     getToken: function() {
133758         return this.ready ? this.currentToken : this.getHash();
133759     }
133760 });
133761 /**
133762  * @class Ext.view.TableChunker
133763  * 
133764  * Produces optimized XTemplates for chunks of tables to be
133765  * used in grids, trees and other table based widgets.
133766  *
133767  * @singleton
133768  */
133769 Ext.define('Ext.view.TableChunker', {
133770     singleton: true,
133771     requires: ['Ext.XTemplate'],
133772     metaTableTpl: [
133773         '{[this.openTableWrap()]}',
133774         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
133775             '<tbody>',
133776             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
133777             '<tpl for="columns">',
133778                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
133779             '</tpl>',
133780             '</tr>',
133781             '{[this.openRows()]}',
133782                 '{row}',
133783                 '<tpl for="features">',
133784                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
133785                 '</tpl>',
133786             '{[this.closeRows()]}',
133787             '</tbody>',
133788         '</table>',
133789         '{[this.closeTableWrap()]}'
133790     ],
133791
133792     constructor: function() {
133793         Ext.XTemplate.prototype.recurse = function(values, reference) {
133794             return this.apply(reference ? values[reference] : values);
133795         };
133796     },
133797
133798     embedFeature: function(values, parent, x, xcount) {
133799         var tpl = '';
133800         if (!values.disabled) {
133801             tpl = values.getFeatureTpl(values, parent, x, xcount);
133802         }
133803         return tpl;
133804     },
133805
133806     embedFullWidth: function() {
133807         return 'style="width: {fullWidth}px;"';
133808     },
133809
133810     openRows: function() {
133811         return '<tpl for="rows">';
133812     },
133813
133814     closeRows: function() {
133815         return '</tpl>';
133816     },
133817
133818     metaRowTpl: [
133819         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
133820             '<tpl for="columns">',
133821                 '<td class="{cls} ' + Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-cell-{columnId} {{id}-modified} {{id}-tdCls} {[this.firstOrLastCls(xindex, xcount)]}" {{id}-tdAttr}><div unselectable="on" class="' + Ext.baseCSSPrefix + 'grid-cell-inner ' + Ext.baseCSSPrefix + 'unselectable" style="{{id}-style}; text-align: {align};">{{id}}</div></td>',
133822             '</tpl>',
133823         '</tr>'
133824     ],
133825     
133826     firstOrLastCls: function(xindex, xcount) {
133827         var cssCls = '';
133828         if (xindex === 1) {
133829             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
133830         } else if (xindex === xcount) {
133831             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
133832         }
133833         return cssCls;
133834     },
133835     
133836     embedRowCls: function() {
133837         return '{rowCls}';
133838     },
133839     
133840     embedRowAttr: function() {
133841         return '{rowAttr}';
133842     },
133843     
133844     openTableWrap: function() {
133845         return '';
133846     },
133847     
133848     closeTableWrap: function() {
133849         return '';
133850     },
133851
133852     getTableTpl: function(cfg, textOnly) {
133853         var tpl,
133854             tableTplMemberFns = {
133855                 openRows: this.openRows,
133856                 closeRows: this.closeRows,
133857                 embedFeature: this.embedFeature,
133858                 embedFullWidth: this.embedFullWidth,
133859                 openTableWrap: this.openTableWrap,
133860                 closeTableWrap: this.closeTableWrap
133861             },
133862             tplMemberFns = {},
133863             features = cfg.features || [],
133864             ln = features.length,
133865             i  = 0,
133866             memberFns = {
133867                 embedRowCls: this.embedRowCls,
133868                 embedRowAttr: this.embedRowAttr,
133869                 firstOrLastCls: this.firstOrLastCls
133870             },
133871             // copy the default
133872             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
133873             metaTableTpl;
133874             
133875         for (; i < ln; i++) {
133876             if (!features[i].disabled) {
133877                 features[i].mutateMetaRowTpl(metaRowTpl);
133878                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
133879                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
133880                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
133881             }
133882         }
133883         
133884         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
133885         cfg.row = metaRowTpl.applyTemplate(cfg);
133886         
133887         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
133888         
133889         tpl = metaTableTpl.applyTemplate(cfg);
133890         
133891         // TODO: Investigate eliminating.
133892         if (!textOnly) {
133893             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
133894         }
133895         return tpl;
133896         
133897     }
133898 });
133899
133900
133901
133902 })(this.Ext4 || (this.Ext4 = {}));
133903
133904